blob: 8f66cccf50e394b88039340c319621a27e2d7640 [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SFTPClient.h"
#include <memory>
#include <set>
#include <vector>
#include <string>
#include <exception>
#include <sstream>
#include <iomanip>
#include "utils/StringUtils.h"
#include "utils/ScopeGuard.h"
#include "utils/StringUtils.h"
namespace org {
namespace apache {
namespace nifi {
namespace minifi {
namespace utils {
#define SFTP_ERROR(CODE) case CODE: \
return #CODE
static const char* sftp_strerror(unsigned long err) {
switch (err) {
SFTP_ERROR(LIBSSH2_FX_OK);
SFTP_ERROR(LIBSSH2_FX_EOF);
SFTP_ERROR(LIBSSH2_FX_NO_SUCH_FILE);
SFTP_ERROR(LIBSSH2_FX_PERMISSION_DENIED);
SFTP_ERROR(LIBSSH2_FX_FAILURE);
SFTP_ERROR(LIBSSH2_FX_BAD_MESSAGE);
SFTP_ERROR(LIBSSH2_FX_NO_CONNECTION);
SFTP_ERROR(LIBSSH2_FX_CONNECTION_LOST);
SFTP_ERROR(LIBSSH2_FX_OP_UNSUPPORTED);
SFTP_ERROR(LIBSSH2_FX_INVALID_HANDLE);
SFTP_ERROR(LIBSSH2_FX_NO_SUCH_PATH);
SFTP_ERROR(LIBSSH2_FX_FILE_ALREADY_EXISTS);
SFTP_ERROR(LIBSSH2_FX_WRITE_PROTECT);
SFTP_ERROR(LIBSSH2_FX_NO_MEDIA);
SFTP_ERROR(LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM);
SFTP_ERROR(LIBSSH2_FX_QUOTA_EXCEEDED);
SFTP_ERROR(LIBSSH2_FX_UNKNOWN_PRINCIPAL);
SFTP_ERROR(LIBSSH2_FX_LOCK_CONFLICT);
SFTP_ERROR(LIBSSH2_FX_DIR_NOT_EMPTY);
SFTP_ERROR(LIBSSH2_FX_NOT_A_DIRECTORY);
SFTP_ERROR(LIBSSH2_FX_INVALID_FILENAME);
SFTP_ERROR(LIBSSH2_FX_LINK_LOOP);
default:
return "Unknown error";
}
}
static SFTPError libssh2_sftp_error_to_sftp_error(unsigned long libssh2_sftp_error) {
switch (libssh2_sftp_error) {
case LIBSSH2_FX_OK:
return SFTPError::SFTP_ERROR_OK;
case LIBSSH2_FX_NO_SUCH_FILE:
case LIBSSH2_FX_NO_SUCH_PATH:
return SFTPError::SFTP_ERROR_FILE_NOT_EXISTS;
case LIBSSH2_FX_FILE_ALREADY_EXISTS:
return SFTPError::SFTP_ERROR_FILE_ALREADY_EXISTS;
case LIBSSH2_FX_PERMISSION_DENIED:
case LIBSSH2_FX_WRITE_PROTECT:
case LIBSSH2_FX_LOCK_CONFLICT:
return SFTPError::SFTP_ERROR_PERMISSION_DENIED;
case LIBSSH2_FX_NO_CONNECTION:
case LIBSSH2_FX_CONNECTION_LOST:
return SFTPError::SFTP_ERROR_COMMUNICATIONS_FAILURE;
case LIBSSH2_FX_EOF:
case LIBSSH2_FX_FAILURE:
case LIBSSH2_FX_BAD_MESSAGE:
case LIBSSH2_FX_OP_UNSUPPORTED:
case LIBSSH2_FX_INVALID_HANDLE:
case LIBSSH2_FX_NO_MEDIA:
case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
case LIBSSH2_FX_QUOTA_EXCEEDED:
case LIBSSH2_FX_UNKNOWN_PRINCIPAL:
case LIBSSH2_FX_DIR_NOT_EMPTY:
case LIBSSH2_FX_NOT_A_DIRECTORY:
case LIBSSH2_FX_INVALID_FILENAME:
case LIBSSH2_FX_LINK_LOOP:
default:
return SFTPError::SFTP_ERROR_UNEXPECTED;
}
}
constexpr size_t SFTPClient::MAX_BUFFER_SIZE;
LastSFTPError::LastSFTPError()
: sftp_error_set_(false)
, libssh2_sftp_error_(LIBSSH2_FX_OK)
, sftp_error_(SFTPError::SFTP_ERROR_OK) {
}
LastSFTPError& LastSFTPError::operator=(unsigned long libssh2_sftp_error) {
sftp_error_set_ = false;
libssh2_sftp_error_ = libssh2_sftp_error;
return *this;
}
LastSFTPError& LastSFTPError::operator=(const SFTPError& sftp_error) {
sftp_error_set_ = true;
sftp_error_ = sftp_error;
return *this;
}
LastSFTPError::operator unsigned long() const {
if (sftp_error_set_) {
return LIBSSH2_FX_OK;
} else {
return libssh2_sftp_error_;
}
}
LastSFTPError::operator SFTPError() const {
if (sftp_error_set_) {
return sftp_error_;
} else {
return libssh2_sftp_error_to_sftp_error(libssh2_sftp_error_);
}
}
SFTPClient::SFTPClient(const std::string &hostname, uint16_t port, const std::string& username)
: logger_(logging::LoggerFactory<SFTPClient>::getLogger()),
hostname_(hostname),
port_(port),
username_(username),
ssh_known_hosts_(nullptr),
strict_host_checking_(false),
password_authentication_enabled_(false),
public_key_authentication_enabled_(false),
data_timeout_(0),
send_keepalive_(false),
curl_errorbuffer_(CURL_ERROR_SIZE, '\0'),
easy_(nullptr),
ssh_session_(nullptr),
sftp_session_(nullptr),
connected_(false),
last_error_() {
easy_ = curl_easy_init();
if (easy_ == nullptr) {
throw std::runtime_error("Cannot create curl easy handle");
}
ssh_session_ = libssh2_session_init();
if (ssh_session_ == nullptr) {
curl_easy_cleanup(easy_);
throw std::runtime_error("Cannot create ssh session handler");
}
}
SFTPClient::~SFTPClient() {
if (sftp_session_ != nullptr) {
libssh2_sftp_shutdown(sftp_session_);
}
if (ssh_known_hosts_ != nullptr) {
libssh2_knownhost_free(ssh_known_hosts_);
}
if (ssh_session_ != nullptr) {
libssh2_session_disconnect(ssh_session_, "Normal Shutdown");
libssh2_session_free(ssh_session_);
}
if (easy_ != nullptr) {
curl_easy_cleanup(easy_);
}
logger_->log_trace("Closing SFTPClient for %s:%hu", hostname_, port_);
}
bool SFTPClient::setVerbose() {
return curl_easy_setopt(easy_, CURLOPT_VERBOSE, 1L) == CURLE_OK;
}
bool SFTPClient::setHostKeyFile(const std::string& host_key_file_path, bool strict_host_checking) {
if (ssh_known_hosts_ != nullptr) {
return false;
}
ssh_known_hosts_ = libssh2_knownhost_init(ssh_session_);
if (ssh_known_hosts_ == nullptr) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to init knownhost structure, error: %s", err_msg);
return false;
}
if (libssh2_knownhost_readfile(ssh_known_hosts_, host_key_file_path.c_str(), LIBSSH2_KNOWNHOST_FILE_OPENSSH) <= 0) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to read host file %s, error: %s", host_key_file_path.c_str(), err_msg);
return false;
}
strict_host_checking_ = strict_host_checking;
return true;
}
void SFTPClient::setPasswordAuthenticationCredentials(const std::string& password) {
password_authentication_enabled_ = true;
password_ = password;
}
void SFTPClient::setPublicKeyAuthenticationCredentials(const std::string& private_key_file_path, const std::string& private_key_passphrase) {
public_key_authentication_enabled_ = true;
private_key_file_path_ = private_key_file_path;
private_key_passphrase_ = private_key_passphrase;
}
bool SFTPClient::setProxy(ProxyType type, const utils::HTTPProxy& proxy) {
switch (type) {
case ProxyType::Http:
if (curl_easy_setopt(easy_, CURLOPT_PROXYTYPE, CURLPROXY_HTTP) != CURLE_OK) {
return false;
}
if (curl_easy_setopt(easy_, CURLOPT_HTTPPROXYTUNNEL, 1L) != CURLE_OK) {
return false;
}
break;
case ProxyType::Socks:
if (curl_easy_setopt(easy_, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5) != CURLE_OK) {
return false;
}
break;
}
std::stringstream proxy_string;
proxy_string << proxy.host << ":" << proxy.port;
if (curl_easy_setopt(easy_, CURLOPT_PROXY, proxy_string.str().c_str()) != CURLE_OK) {
return false;
}
return true;
}
bool SFTPClient::setConnectionTimeout(int64_t timeout) {
return curl_easy_setopt(easy_, CURLOPT_CONNECTTIMEOUT_MS, timeout) == CURLE_OK;
}
void SFTPClient::setDataTimeout(int64_t timeout) {
data_timeout_ = timeout;
libssh2_session_set_timeout(ssh_session_, timeout);
}
void SFTPClient::setSendKeepAlive(bool send_keepalive) {
/*
* Some SSH servers don't like if we send keepalives before we're connected,
* but libssh2 sends keepalives before that, so we will set up keepalives after
* a successful connection.
*/
send_keepalive_ = send_keepalive;
}
bool SFTPClient::setUseCompression(bool use_compression) {
return libssh2_session_flag(ssh_session_, LIBSSH2_FLAG_COMPRESS, 1) == 0;
}
bool SFTPClient::connect() {
if (connected_) {
return true;
}
/* Setting up curl request */
std::stringstream uri_ss;
uri_ss << hostname_ << ":" << port_;
auto uri = uri_ss.str();
if (curl_easy_setopt(easy_, CURLOPT_URL, uri.c_str()) != CURLE_OK) {
return false;
}
if (curl_easy_setopt(easy_, CURLOPT_ERRORBUFFER, curl_errorbuffer_.data()) != CURLE_OK) {
return false;
}
if (curl_easy_setopt(easy_, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) {
return false;
}
if (curl_easy_setopt(easy_, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) {
return false;
}
/* Connecting to proxy, if needed, then to the host */
CURLcode curl_res = curl_easy_perform(easy_);
if (curl_res != CURLE_OK) {
logger_->log_error("Failed to connect to %s, curl error code: %s, detailed error message: %s",
uri.c_str(),
curl_easy_strerror(curl_res),
curl_errorbuffer_.data());
return false;
}
/* Getting socket from curl */
#ifdef WIN32
curl_socket_t sockfd;
/* Only CURLINFO_ACTIVESOCKET works on Win64 */
curl_res = curl_easy_getinfo(easy_, CURLINFO_ACTIVESOCKET, &sockfd);
#else
long sockfd;
/* Some older cURL versions only support CURLINFO_LASTSOCKET */
curl_res = curl_easy_getinfo(easy_, CURLINFO_LASTSOCKET, &sockfd);
#endif
if (curl_res != CURLE_OK) {
return false;
}
/* Establishing SSH connection */
if (libssh2_session_handshake(ssh_session_, sockfd) != 0) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_info("Failed to establish SSH connection, error: %s", err_msg);
return false;
}
/* Checking remote host */
if (ssh_known_hosts_ != nullptr) {
size_t hostkey_len = 0U;
int type = LIBSSH2_HOSTKEY_TYPE_UNKNOWN;
const char *hostkey = libssh2_session_hostkey(ssh_session_, &hostkey_len, &type);
if (hostkey == nullptr) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_info("Failed to get session hostkey, error: %s", err_msg);
return false;
}
int keybit = 0;
switch (type) {
case LIBSSH2_HOSTKEY_TYPE_RSA:
keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
break;
case LIBSSH2_HOSTKEY_TYPE_DSS:
keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
break;
default:
logger_->log_error("Unknown host key type: %d", type);
return false;
}
struct libssh2_knownhost* known_host = nullptr;
int keycheck_result = libssh2_knownhost_checkp(ssh_known_hosts_,
hostname_.c_str(),
-1 /*port*/,
hostkey, hostkey_len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN |
LIBSSH2_KNOWNHOST_KEYENC_RAW |
keybit,
&known_host);
bool host_match = false;
switch (keycheck_result) {
case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
logger_->log_warn("Failed to verify host key for %s", hostname_.c_str());
break;
case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
logger_->log_warn("Host %s not found in the host key file", hostname_.c_str());
break;
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: {
auto hostkey_b64 = utils::StringUtils::to_base64(reinterpret_cast<const uint8_t*>(hostkey), hostkey_len);
logger_->log_warn("Host key mismatch for %s, expected: %s, actual: %s", hostname_.c_str(),
known_host == nullptr ? "" : known_host->key, hostkey_b64.c_str());
break;
}
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
host_match = true;
logger_->log_debug("Host key verification succeeded for %s", hostname_.c_str());
break;
default:
logger_->log_error("Unknown libssh2_knownhost_checkp result: %d", keycheck_result);
break;
}
if (strict_host_checking_ && !host_match) {
return false;
}
} else {
const char* fingerprint = libssh2_hostkey_hash(ssh_session_, LIBSSH2_HOSTKEY_HASH_SHA1);
if (fingerprint == nullptr) {
logger_->log_warn("Cannot get remote server fingerprint");
} else {
auto fingerprint_hex = utils::StringUtils::to_hex(reinterpret_cast<const uint8_t*>(fingerprint), 20);
std::stringstream fingerprint_hex_colon;
for (size_t i = 0; i < 20; i++) {
fingerprint_hex_colon << fingerprint_hex.substr(i * 2, 2);
if (i != 19) {
fingerprint_hex_colon << ":";
}
}
logger_->log_debug("SHA1 host key fingerprint for %s is %s", hostname_.c_str(), fingerprint_hex_colon.str().c_str());
}
}
/* Getting possible authentication methods */
bool authenticated = false;
std::set<std::string> auth_methods;
char* userauthlist = libssh2_userauth_list(ssh_session_, username_.c_str(), strlen(username_.c_str()));
if (userauthlist == nullptr) {
if (libssh2_userauth_authenticated(ssh_session_) == 1) {
authenticated = true;
logger_->log_warn("SSH server authenticated with SSH_USERAUTH_NONE - this is unusual");
} else {
logger_->log_error("Failed to get supported SSH authentication methods");
return false;
}
} else {
auto methods_split = utils::StringUtils::split(userauthlist, ",");
auth_methods.insert(std::make_move_iterator(methods_split.begin()), std::make_move_iterator(methods_split.end()));
}
/* Authenticating */
if (!authenticated && public_key_authentication_enabled_ && auth_methods.count("publickey") == 1) {
if (libssh2_userauth_publickey_fromfile_ex(ssh_session_,
username_.c_str(),
username_.length(),
nullptr /*publickey*/,
private_key_file_path_.c_str(),
private_key_passphrase_.c_str()) == 0) {
authenticated = true;
logger_->log_debug("Successfully authenticated with publickey");
} else {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_info("Failed to authenticate with publickey, error: %s", err_msg);
}
}
if (!authenticated && password_authentication_enabled_ && auth_methods.count("password") == 1) {
if (libssh2_userauth_password(ssh_session_, username_.c_str(), password_.c_str()) == 0) {
authenticated = true;
logger_->log_debug("Successfully authenticated with password");
} else {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_info("Failed to authenticate with password, error: %s", err_msg);
}
}
if (!authenticated) {
logger_->log_error("Could not authenticate with any available method");
return false;
}
/* Initializing SFTP session */
sftp_session_ = libssh2_sftp_init(ssh_session_);
if (sftp_session_ == nullptr) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to initialize SFTP session, error: %s", err_msg);
return false;
}
connected_ = true;
/* Set up keepalive config if needed */
if (send_keepalive_) {
libssh2_keepalive_config(ssh_session_, 0 /*want_reply*/, 10U /*interval*/);
}
return true;
}
bool SFTPClient::sendKeepAliveIfNeeded(int &seconds_to_next) {
if (libssh2_keepalive_send(ssh_session_, &seconds_to_next) != 0) {
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to send keepalive to %s@%s:%hu, error: %s",
username_,
hostname_,
port_,
err_msg);
return false;
}
return true;
}
SFTPError SFTPClient::getLastError() const {
return last_error_;
}
bool SFTPClient::getFile(const std::string& path, io::BaseStream& output, int64_t expected_size /*= -1*/) {
/**
* SFTP servers should not set the mode of an existing file on open
* (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13, Page 33
* "The 'attrs' field is ignored if an existing file is opened."
* Unfortunately this is a later SFTP version specification than implemented by most servers.)
* But because this is the intuitively correct behaviour (especially when opening a file for read only),
* most servers (OpenSSH for example) implement it this way.
* mina-sshd, the server we use for testing, however did not until recently,
* causing all files we read to be set to 0000.
* The fix to make it behave correctly has been merged back to master, but not yet released:
* https://github.com/apache/mina-sshd/commit/19adb39e4706929b6e5a1b2df056a2b2a29fac4d
* If we encounter real servers that behave like this, a workaround would be to stat before opening the file
* and "re-setting" the mode we read earlier on open.
* An another option would be to patch libssh2 to not send permissions in attrs when opening a file for read only.
*/
LIBSSH2_SFTP_HANDLE *file_handle = libssh2_sftp_open(sftp_session_, path.c_str(), LIBSSH2_FXF_READ, 0 /*mode*/);
if (file_handle == nullptr) {
int ssh_errno = libssh2_session_last_errno(ssh_session_);
/* We can only get the sftp error in this case if the ssh error is a protocol error */
if (ssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to open remote file \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
} else {
last_error_ = SFTPError::SFTP_ERROR_IO_ERROR;
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to open remote file \"%s\" due to an underlying SSH error: %s", path.c_str(), err_msg);
}
return false;
}
utils::ScopeGuard guard([&file_handle]() {
libssh2_sftp_close(file_handle);
});
const size_t buf_size = expected_size < 0 ? MAX_BUFFER_SIZE : std::min<size_t>(expected_size, MAX_BUFFER_SIZE);
std::vector<uint8_t> buf(buf_size);
uint64_t total_read = 0U;
do {
ssize_t read_ret = libssh2_sftp_read(file_handle, reinterpret_cast<char*>(buf.data()), buf.size());
if (read_ret < 0) {
last_error_ = SFTPError::SFTP_ERROR_IO_ERROR;
logger_->log_error("Failed to read remote file \"%s\"", path.c_str());
return false;
} else if (read_ret == 0) {
logger_->log_trace("EOF while reading remote file \"%s\"", path.c_str());
break;
}
logger_->log_trace("Read %d bytes from remote file \"%s\"", read_ret, path.c_str());
total_read += read_ret;
int remaining = read_ret;
while (remaining > 0) {
int write_ret = output.writeData(buf.data() + (read_ret - remaining), remaining);
if (write_ret < 0) {
last_error_ = LIBSSH2_FX_OK;
logger_->log_error("Failed to write output");
return false;
}
remaining -= write_ret;
}
} while (true);
if (expected_size >= 0 && total_read != expected_size) {
last_error_ = LIBSSH2_FX_OK;
logger_->log_error("Remote file \"%s\" has unexpected size, expected: %ld, actual: %lu", path.c_str(), expected_size, total_read);
return false;
}
return true;
}
bool SFTPClient::putFile(const std::string& path, io::BaseStream& input, bool overwrite, int64_t expected_size /*= -1*/) {
int flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | (overwrite ? LIBSSH2_FXF_TRUNC : LIBSSH2_FXF_EXCL);
logger_->log_trace("Opening remote file \"%s\"", path.c_str());
LIBSSH2_SFTP_HANDLE *file_handle = libssh2_sftp_open(sftp_session_, path.c_str(), flags, 0644);
if (file_handle == nullptr) {
int ssh_errno = libssh2_session_last_errno(ssh_session_);
/* We can only get the sftp error in this case if the ssh error is a protocol error */
if (ssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to open remote file \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
} else {
last_error_ = SFTPError::SFTP_ERROR_IO_ERROR;
char *err_msg = nullptr;
libssh2_session_last_error(ssh_session_, &err_msg, nullptr, 0);
logger_->log_error("Failed to open remote file \"%s\" due to an underlying SSH error: %s", path.c_str(), err_msg);
}
}
utils::ScopeGuard guard([this, &file_handle, &path]() {
logger_->log_trace("Closing remote file \"%s\"", path.c_str());
libssh2_sftp_close(file_handle);
});
/* If they just want a zero byte file, we are done */
if (expected_size == 0) {
return true;
}
const size_t buf_size = expected_size < 0 ? MAX_BUFFER_SIZE : std::min<size_t>(expected_size, MAX_BUFFER_SIZE);
std::vector<uint8_t> buf(buf_size);
uint64_t total_read = 0U;
do {
int read_ret = input.readData(buf.data(), buf.size());
if (read_ret < 0) {
last_error_ = LIBSSH2_FX_OK;
logger_->log_error("Error while reading input");
return false;
} else if (read_ret == 0) {
logger_->log_trace("EOF while reading input");
break;
}
logger_->log_trace("Read %d bytes", read_ret);
total_read += read_ret;
ssize_t remaining = read_ret;
while (remaining > 0) {
int write_ret = libssh2_sftp_write(file_handle, reinterpret_cast<char*>(buf.data() + (read_ret - remaining)), remaining);
if (write_ret < 0) {
last_error_ = SFTPError::SFTP_ERROR_IO_ERROR;
logger_->log_error("Failed to write remote file \"%s\"", path.c_str());
return false;
}
logger_->log_trace("Wrote %d bytes to remote file \"%s\"", write_ret, path.c_str());
remaining -= write_ret;
}
} while (true);
if (expected_size >= 0 && total_read != expected_size) {
last_error_ = LIBSSH2_FX_OK;
logger_->log_error("Input has unexpected size, expected: %ld, actual: %lu", path.c_str(), expected_size, total_read);
return false;
}
return true;
}
bool SFTPClient::rename(const std::string& source_path, const std::string& target_path, bool overwrite) {
int flags = 0;
if (overwrite) {
flags = LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE | LIBSSH2_SFTP_RENAME_OVERWRITE;
} else {
flags = LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE;
}
bool tried_deleting = false;
while (libssh2_sftp_rename_ex(sftp_session_,
source_path.c_str(),
source_path.length(),
target_path.c_str(),
target_path.length(),
flags) != 0) {
auto err = libssh2_sftp_last_error(sftp_session_);
if (overwrite && err == LIBSSH2_FX_FILE_ALREADY_EXISTS && !tried_deleting) {
/* It couldn't overwrite the file, let's delete it and try again */
logger_->log_debug("Failed to overwrite file \"%s\" with rename, deleting instead", target_path.c_str());
tried_deleting = true;
if (!this->removeFile(target_path)) {
return false;
}
continue;
}
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to rename remote file \"%s\" to \"%s\", error: %s",
source_path.c_str(),
target_path.c_str(),
sftp_strerror(last_error_));
return false;
}
return true;
}
bool SFTPClient::createDirectoryHierarchy(const std::string& path) {
if (path.empty()) {
last_error_ = LIBSSH2_FX_OK;
return false;
}
bool absolute = path[0] == '/';
auto elements = utils::StringUtils::split(path, "/");
std::stringstream dir;
if (absolute) {
dir << "/";
}
for (const auto& element : elements) {
dir << element << "/";
auto current_dir = dir.str();
int res = libssh2_sftp_mkdir_ex(sftp_session_, current_dir.c_str(), current_dir.length(), 0755);
if (res < 0) {
auto err = libssh2_sftp_last_error(sftp_session_);
if (err != LIBSSH2_FX_FILE_ALREADY_EXISTS &&
err != LIBSSH2_FX_FAILURE &&
err != LIBSSH2_FX_PERMISSION_DENIED) {
last_error_ = err;
logger_->log_error("Failed to create remote directory \"%s\", error: %s", current_dir.c_str(), sftp_strerror(last_error_));
return false;
} else {
logger_->log_debug("Non-fatal failure to create remote directory \"%s\", error: %s", current_dir.c_str(), sftp_strerror(err));
}
}
}
return true;
}
bool SFTPClient::removeFile(const std::string& path) {
if (libssh2_sftp_unlink(sftp_session_, path.c_str()) != 0) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to remove remote file \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
}
return true;
}
bool SFTPClient::removeDirectory(const std::string& path) {
if (libssh2_sftp_rmdir(sftp_session_, path.c_str()) != 0) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to remove remote directory \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
}
return true;
}
bool SFTPClient::listDirectory(const std::string& path, bool follow_symlinks,
std::vector<std::tuple<std::string /* filename */, std::string /* longentry */, LIBSSH2_SFTP_ATTRIBUTES /* attrs */>>& children_result) {
LIBSSH2_SFTP_HANDLE *dir_handle = libssh2_sftp_open_ex(sftp_session_,
path.c_str(),
path.length(),
0 /* flags */,
0 /* mode */,
LIBSSH2_SFTP_OPENDIR);
if (dir_handle == nullptr) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to open remote directory \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
}
utils::ScopeGuard guard([&dir_handle]() {
libssh2_sftp_close(dir_handle);
});
LIBSSH2_SFTP_ATTRIBUTES attrs;
std::vector<char> filename(4096U);
std::vector<char> longentry(4096U);
do {
int ret = libssh2_sftp_readdir_ex(dir_handle,
filename.data(),
filename.size(),
longentry.data(),
longentry.size(),
&attrs);
if (ret < 0) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_error("Failed to read remote directory \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
} else if (ret == 0) {
break;
}
if (follow_symlinks && attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS && LIBSSH2_SFTP_S_ISLNK(attrs.permissions)) {
std::stringstream new_path;
new_path << path << "/" << filename.data();
auto orig_attrs = attrs;
if (!this->stat(new_path.str(), true /*follow_symlinks*/, attrs)) {
attrs = orig_attrs;
}
}
children_result.emplace_back(std::string(filename.data()), std::string(longentry.data()), std::move(attrs));
} while (true);
return true;
}
bool SFTPClient::stat(const std::string& path, bool follow_symlinks, LIBSSH2_SFTP_ATTRIBUTES& result) {
if (libssh2_sftp_stat_ex(sftp_session_,
path.c_str(),
path.length(),
follow_symlinks ? LIBSSH2_SFTP_STAT : LIBSSH2_SFTP_LSTAT,
&result) != 0) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_debug("Failed to stat remote path \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
}
return true;
}
bool SFTPClient::setAttributes(const std::string& path, const SFTPAttributes& input) {
LIBSSH2_SFTP_ATTRIBUTES attrs;
memset(&attrs, 0x00, sizeof(attrs));
if ((!!(input.flags & SFTP_ATTRIBUTE_UID) != !!(input.flags & SFTP_ATTRIBUTE_GID)) ||
(!!(input.flags & SFTP_ATTRIBUTE_MTIME) != !!(input.flags & SFTP_ATTRIBUTE_ATIME))) {
/* Because we can only set these attributes in pairs, we must stat first to learn the other */
if (!this->stat(path, false /*follow_symlinks*/, attrs)) {
return false;
}
}
attrs.flags = 0U;
if (input.flags & SFTP_ATTRIBUTE_PERMISSIONS) {
attrs.flags |= LIBSSH2_SFTP_ATTR_PERMISSIONS;
attrs.permissions = input.permissions;
}
if (input.flags & SFTP_ATTRIBUTE_UID) {
attrs.flags |= LIBSSH2_SFTP_ATTR_UIDGID;
attrs.uid = input.uid;
}
if (input.flags & SFTP_ATTRIBUTE_GID) {
attrs.flags |= LIBSSH2_SFTP_ATTR_UIDGID;
attrs.gid = input.gid;
}
if (input.flags & SFTP_ATTRIBUTE_MTIME) {
attrs.flags |= LIBSSH2_SFTP_ATTR_ACMODTIME;
attrs.mtime = input.mtime;
}
if (input.flags & SFTP_ATTRIBUTE_ATIME) {
attrs.flags |= LIBSSH2_SFTP_ATTR_ACMODTIME;
attrs.atime = input.atime;
}
if (libssh2_sftp_stat_ex(sftp_session_,
path.c_str(),
path.length(),
LIBSSH2_SFTP_SETSTAT,
&attrs) != 0) {
last_error_ = libssh2_sftp_last_error(sftp_session_);
logger_->log_debug("Failed to setstat on remote path \"%s\", error: %s", path.c_str(), sftp_strerror(last_error_));
return false;
}
return true;
}
} /* namespace utils */
} /* namespace minifi */
} /* namespace nifi */
} /* namespace apache */
} /* namespace org */