From 2b26a51b6c196409b3a31ac76e33aabee9c0ef9e Mon Sep 17 00:00:00 2001 From: Vikram Rajkumar Date: Fri, 13 Jan 2017 14:29:22 -0600 Subject: [PATCH] Remove unused ssh code --- include/fc/ssh/client.hpp | 193 ---------- include/fc/ssh/error.hpp | 25 -- include/fc/ssh/process.hpp | 58 --- src/ssh/client.cpp | 717 ------------------------------------- src/ssh/client_impl.hpp | 280 --------------- src/ssh/process.cpp | 334 ----------------- 6 files changed, 1607 deletions(-) delete mode 100644 include/fc/ssh/client.hpp delete mode 100644 include/fc/ssh/error.hpp delete mode 100644 include/fc/ssh/process.hpp delete mode 100644 src/ssh/client.cpp delete mode 100644 src/ssh/client_impl.hpp delete mode 100644 src/ssh/process.cpp diff --git a/include/fc/ssh/client.hpp b/include/fc/ssh/client.hpp deleted file mode 100644 index eb24a8e..0000000 --- a/include/fc/ssh/client.hpp +++ /dev/null @@ -1,193 +0,0 @@ -#pragma once -#include -#include -#include - -namespace fc { - class path; - class logger; - namespace ssh { - namespace detail { - class client_impl; - class process_impl; - }; - - enum sftp_file_type { - named_pipe = 0010000, - directory = 0040000, - regular = 0100000, - symlink = 0120000 - }; - - - enum sftp_file_mode { - owner_mask = 0000700, /* RWX mask for owner */ - owner_read = 0000400, /* R for owner */ - owner_write = 0000200, /* W for owner */ - owner_exec = 0000100, /* X for owner */ - group_mask = 0000070, /* RWX mask for group */ - group_read = 0000040, /* R for group */ - group_write = 0000020, /* W for group */ - group_exec = 0000010, /* X for group */ - other_mask = 0000007, /* RWX mask for other */ - other_read = 0000004, /* R for other */ - other_write = 0000002, /* W for other */ - other_exec = 0000001 /* X for other */ - }; - - struct file_attrib { - file_attrib(); - - uint64_t size; - uint32_t uid; - uint32_t gid; - uint32_t permissions; - uint32_t atime; - uint32_t mtime; - - bool exists(); - bool is_file(); - bool is_directory(); - }; - - - /** - * @brief Enables communication over ssh using libssh2. - * - * Because the client creates other resources that depend upon - * it, it can only be created as a std::shared_ptr (aka client::ptr) - * via client::create(); - * - */ - class client - { - public: - enum trace_level { - TRACE_NONE = 0, - TRACE_TRANS = (1<<1), - TRACE_KEX = (1<<2), - TRACE_AUTH = (1<<3), - TRACE_CONN = (1<<4), - TRACE_SCP = (1<<5), - TRACE_SFTP = (1<<6), - TRACE_ERROR = (1<<7), - TRACE_PUBLICKEY = (1<<8), - TRACE_SOCKET = (1<<9) - }; - /** - * Everything but TRACE_ERROR will be logged at fc::log_level::debug, while - * TRACE_ERROR will be logged at fc::log_level::error - * - * @param bitmask comprised of values from trace_level - **/ - void set_trace_level( int bitmask ); - int get_trace_level()const; - - /** - * Override the default logger used by fc::ssh::client - */ - void set_logger( const logger& lgr ); - const logger& get_logger()const; - - /** - * Connect, with no password specified. Authentication will try public key, - * (via agent or explicitly-set key), empty password, then keyboard-interactive - */ - void connect( const fc::string& user, const fc::string& host, uint16_t port = 22); - - /** - * Connect, specifying a password to be used for password authentication - */ - void connect( const fc::string& user, const fc::string& pass, const fc::string& host, uint16_t port = 22); - - /** - * @note THIS METHOD IS DEPRECATED and should be replace with: - * - * ssh::client_ptr sshc = std::make_shared(); - * sshc->connect( ... ) - * ssh::process_ptr proc = std::make_shared( sshc ); - * proc->exec( ... ) - * - * - * @brief execute command on remote machine - * @param pty_type - whether or not to request a PTY when executing this process, this is necessary - * for interactive (non-buffered) IO with the remote process, if left empty no pty will be - * requested - * - * @note Processes launched in this manner will fully buffer stdin and stdout regardless of whether - * the process calls flush(). If you need unbuffered (streaming, realtime) access to standard - * out then you must launch the process via a shell. - ssh::process exec( const fc::string& cmd, const fc::string& pty_type = "" ); - */ - - - /** - * @brief upload a file to remote host - * @param progress a callback to report / cancel upload. - * The callback takes two parameters, bytes sent and file size. To continue the - * transfer, the callback should return true. To cancel the callback should return false. - */ - void scp_send( const fc::path& local_path, const fc::path& remote_path, - std::function progress = [](uint64_t,uint64_t){return true;} ); - - /** - * @brief recursively sends the contents of local_dir to the remote_path - * - * If remote_path ends in '/' then a new directory at remote_path/local_dir.filename() will - * be created, otherwise local_dir / * will be copied to remote_path / * - * - * Progress will be reported as total bytes transferred for all files. - */ - void scp_send_dir( const fc::path& local_dir, const fc::path& remote_path, - std::function progress = [](uint64_t,uint64_t){return true;} ); - - /** - * @pre remote_path is not a directory - * @post remote file is removed from the remote filesystem - */ - void rm( const fc::path& remote_path ); - - /** - * @pre remote_path is a directory - * @post remote directory is removed from the remote filesystem - */ - void rmdir( const fc::path& remote_path ); - - void rmdir_recursive( const fc::path& remote_path ); - - file_attrib stat( const fc::path& remote_path ); - - /** - * @pre all parent directories already exist. - * @pre remote_dir is not exist or is already a directory - * @post remote_dir exists. - */ - void mkdir( const fc::path& remote_dir, int mode = owner_read|owner_write|owner_exec ); - - /** - * Create all parent directories for remote_dir if they do not exist. - * - * @post remote_dir exists. - */ - void create_directories( const fc::path& remote_dir, int mode = owner_read|owner_write|owner_exec ); - - /** - * Sets whether the remote system is believed to be a Windows box (by default, it's - * assumed to be running UNIX. This alters how command-line arguments are quoted - * and possibly how filenames are altered when copying files - */ - void set_remote_system_is_windows(bool is_windows = true); - - void close(); - - client(); - ~client(); - - private: - friend class process; - friend class detail::process_impl; - std::unique_ptr my; - }; - typedef std::shared_ptr client_ptr; - -} } // namespace fc::ssh diff --git a/include/fc/ssh/error.hpp b/include/fc/ssh/error.hpp deleted file mode 100644 index 55eba7c..0000000 --- a/include/fc/ssh/error.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MACE_SSH_ERROR_HPP -#define MACE_SSH_ERROR_HPP -#include -#include - -namespace mace { namespace ssh { -typedef boost::error_info err_msg; - -struct exception : public virtual boost::exception, public virtual std::exception { - const char* what()const throw() { return "exception"; } - virtual void rethrow()const { BOOST_THROW_EXCEPTION(*this); } - const std::string& message()const { return *boost::get_error_info(*this); } -}; - -} } // mace::ssh - -/** - * Helper macro for throwing exceptions with a message: THROW( "Hello World %1%, %2%", %"Hello" %"World" ) - */ -#define MACE_SSH_THROW( MSG, ... ) \ - do { \ - BOOST_THROW_EXCEPTION( mace::ssh::exception() << mace::ssh::err_msg( (boost::format( MSG ) __VA_ARGS__ ).str() ) );\ - } while(0) - -#endif diff --git a/include/fc/ssh/process.hpp b/include/fc/ssh/process.hpp deleted file mode 100644 index 08eb089..0000000 --- a/include/fc/ssh/process.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include - -namespace fc { namespace ssh -{ - - class client; - - namespace detail { - class process_impl; - }; - - /** - * Enables communication with a process executed via - * client::exec(). - * - * Process can only be created by mace::ssh::client. - */ - class process : public iprocess - { - public: - virtual iprocess& exec( const fc::path& exe, std::vector args, - const fc::path& work_dir = fc::path(), exec_opts opts = open_all ); - - /** - * Blocks until the result code of the process has been returned. - */ - virtual int result(); - - - /** - * Not supported. libssh2 does not support sending signals to remote processes. - * closing in_stream() is the best you can do - */ - virtual void kill(); - - - /** - * @brief returns a stream that writes to the process' stdin - */ - virtual fc::buffered_ostream_ptr in_stream(); - - /** - * @brief returns a stream that reads from the process' stdout - */ - virtual fc::buffered_istream_ptr out_stream(); - /** - * @brief returns a stream that reads from the process' stderr - */ - virtual fc::buffered_istream_ptr err_stream(); - - process( fc::ssh::client_ptr c ); - ~process(); - private: - std::unique_ptr my; - }; - -} } // fc::ssh diff --git a/src/ssh/client.cpp b/src/ssh/client.cpp deleted file mode 100644 index 80d8788..0000000 --- a/src/ssh/client.cpp +++ /dev/null @@ -1,717 +0,0 @@ -#define NOMINMAX // prevent windows from defining min and max macros -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "client_impl.hpp" - -namespace fc { namespace ssh { - - namespace detail { - static int ssh_init = libssh2_init(0); - } - - client::client():my( new detail::client_impl() ){ (void)detail::ssh_init; /* fix unused warning...*/ } - client::~client() { my->close(); } - - void client::set_trace_level( int bitmask ) { my->_trace_level = bitmask; } - int client::get_trace_level()const { return my->_trace_level; } - const logger& client::get_logger()const { return my->logr; } - void client::set_logger( const logger& l ) { my->logr = l; } - - void client::connect( const fc::string& user, const fc::string& host, uint16_t port ) { - my->hostname = host; - my->uname = user; - my->port = port; - my->connect(); - } - void client::connect( const fc::string& user, const fc::string& pass, - const fc::string& host, uint16_t port ) { - my->hostname = host; - my->uname = user; - my->upass = pass; - my->port = port; - - my->connect(); - } - - void client::close() { my->close(); } - - -// ssh::process client::exec( const fc::string& cmd, const fc::string& pty_type ) { -// return ssh::process( *this, cmd, pty_type ); -// } - - /** - * @todo implement progress reporting. - */ - void client::scp_send_dir( const fc::path& local_dir, const fc::path& remote_path, - std::function progress ) - { - fc::path remote_dir = remote_path; - if( remote_dir.filename() == fc::path(".") ) - remote_dir /= local_dir.filename(); - - fc_dlog( my->logr, "scp -r ${local} ${remote}", ("local",local_dir)("remote",remote_dir) ); - create_directories( remote_dir ); - - directory_iterator ditr(local_dir); - directory_iterator dend; - - while( ditr != dend ) { - if( (*ditr).filename() == "." || - (*ditr).filename() == ".." ) - { } - else if( fc::is_directory(*ditr) ) - { - scp_send_dir( (*ditr), remote_dir / (*ditr).filename() ); - } else if( fc::is_regular_file(*ditr) ) { - scp_send( *ditr, remote_dir / (*ditr).filename() ); - } else { - fc_wlog( my->logr, "Skipping '${path}", ("path",fc::canonical(*ditr)) ); - } - ++ditr; - } - } - - void client::scp_send( const fc::path& local_path, const fc::path& remote_path, - std::function progress ) { - fc_wlog( my->logr, "scp ${local} ${remote}", ("local",local_path)("remote",remote_path ) ); - if( !fc::exists(local_path) ) { - FC_THROW( "Source file '${file}' does not exist", ("file",local_path) ) ; - } - if( is_directory( local_path ) ) { - FC_THROW( "Source file '${file}' is a directory, expected a file", ("file",local_path) ) ; - } - - // memory map the file - uint64_t fsize = file_size(local_path); - if( fsize == 0 ) { - elog( "file size ${file_size}", ("file_size", fsize) ); - // TODO: handle empty file case - if( progress ) progress(0,0); - return; - } - file_mapping fmap( local_path.string().c_str(), read_only ); - - LIBSSH2_CHANNEL* chan = 0; - time_t now; - memset( &now, 0, sizeof(now) ); - - // TODO: preserve creation / modification date - // TODO: perserve permissions / exec bit? - try { - // libssh2_scp_send64 stores state data in the session object, and it calls channel_open which - // stores its own state data, so lock both. - fc::scoped_lock channel_open_lock(my->channel_open_mutex); - fc::scoped_lock scp_send_lock(my->scp_send_mutex); - chan = my->call_ssh2_ptr_function_throw(boost::bind(libssh2_scp_send64, my->session, remote_path.generic_string().c_str(), 0700, fsize, now, now )); - } catch (fc::exception& er) { - FC_RETHROW_EXCEPTION(er, error, "scp ${local_file} to ${remote_file} failed", ("local_file", local_path)("remote_file",remote_path)); - } - uint64_t total_bytes_written = 0; - try { - const size_t max_mapping_size = 1024*1024*1024; // 1GB - for (uint64_t current_offset = 0; current_offset < fsize; current_offset += max_mapping_size) { - uint64_t total_bytes_left_to_send = fsize - current_offset; - size_t bytes_to_send_this_iteration = (size_t)std::min(total_bytes_left_to_send, max_mapping_size); - mapped_region mr( fmap, fc::read_only, current_offset, bytes_to_send_this_iteration); - size_t bytes_written_this_iteration = 0; - char* pos = reinterpret_cast(mr.get_address()); - while( progress(total_bytes_written, fsize) && bytes_written_this_iteration < bytes_to_send_this_iteration) { - int r = my->call_ssh2_function_throw(boost::bind(libssh2_channel_write_ex, chan, 0, pos, - bytes_to_send_this_iteration - bytes_written_this_iteration), - "scp failed ${code} - ${message}"); - bytes_written_this_iteration += r; - total_bytes_written += r; - pos += r; - // fc_wlog( my->logr, "wrote ${bytes} bytes", ("bytes",r) ); - } - } - my->call_ssh2_function(boost::bind(libssh2_channel_send_eof, chan)); - my->call_ssh2_function(boost::bind(libssh2_channel_wait_eof, chan)); - my->call_ssh2_function(boost::bind(libssh2_channel_close, chan)); - } catch ( fc::exception& er ) { - // clean up chan - my->call_ssh2_function(boost::bind(libssh2_channel_free, chan)); - throw er; - } - my->call_ssh2_function_throw(boost::bind(libssh2_channel_free, chan), - "scp failed ${code} - ${message}"); - } - - - void client::rm( const fc::path& remote_path ) { - try { - auto s = stat(remote_path); - if( s.is_directory() ) { - FC_THROW( "sftp cannot remove directory ${path}", ("path",remote_path) ); - } - else if( !s.exists() ) { - return; // nothing to do - } - - { - fc::scoped_lock scp_unlink_lock(my->scp_unlink_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_unlink_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()), - "sftp rm failed ${code}"); - } - } catch ( fc::exception& er ) { - FC_RETHROW_EXCEPTION( er, error, "sftp remove '${remote_path}' failed", ("remote_path",remote_path) ); - } - } - - void client::rmdir( const fc::path& remote_path ) { - try { - auto s = stat(remote_path); - if( !s.is_directory() ) - FC_THROW( "sftp cannot rmdir non-directory ${path}", ("path",remote_path) ); - else if( !s.exists() ) - return; // nothing to do - - { - fc::scoped_lock scp_rmdir_lock(my->scp_rmdir_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_rmdir_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()), - "sftp rmdir failed ${code}"); - } - } catch ( fc::exception& er ) { - FC_RETHROW_EXCEPTION( er, error, "sftp rmdir '${remote_path}' failed", ("remote_path",remote_path) ); - } - } - - void client::rmdir_recursive( const fc::path& remote_path ) { - try { - auto s = stat(remote_path); - if( !s.is_directory() ) - FC_THROW( "sftp cannot rmdir non-directory ${path}", ("path",remote_path) ); - else if( !s.exists() ) - return; // nothing to do - - LIBSSH2_SFTP_HANDLE *dir_handle; - { - fc::scoped_lock scp_open_lock(my->scp_open_mutex); - dir_handle = - my->call_ssh2_ptr_function_throw(boost::bind(libssh2_sftp_open_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size(), 0, 0, LIBSSH2_SFTP_OPENDIR), - "sftp libssh2_sftp_opendir failed ${code}"); - } - do { - char mem[512]; - LIBSSH2_SFTP_ATTRIBUTES attrs; - - int rc; - { - fc::scoped_lock scp_readdir_lock(my->scp_readdir_mutex); - rc = my->call_ssh2_function_throw(boost::bind(libssh2_sftp_readdir_ex, dir_handle, mem, sizeof(mem), (char*)NULL, 0, &attrs), - "sftp readdir failed ${code}"); - } - if (rc > 0) { - fc::string file_or_dir_name(mem, rc); - if (file_or_dir_name == "." || file_or_dir_name == "..") - continue; - fc::path full_remote_path = remote_path / file_or_dir_name; - if (LIBSSH2_SFTP_S_ISDIR(attrs.permissions)) - rmdir_recursive(full_remote_path); - else if (LIBSSH2_SFTP_S_ISREG(attrs.permissions)) { - fc::scoped_lock scp_unlink_lock(my->scp_unlink_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_unlink_ex, my->sftp, full_remote_path.generic_string().c_str(), full_remote_path.generic_string().size()), - "sftp rm failed ${code}"); - } - } else - break; - } while (1); - - { - fc::scoped_lock scp_close_lock(my->scp_close_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_close_handle, dir_handle), "sftp libssh2_sftp_closedir failed ${code}"); - } - { - fc::scoped_lock scp_rmdir_lock(my->scp_rmdir_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_rmdir_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()), - "sftp rmdir failed ${code}"); - } - } catch ( fc::exception& er ) { - FC_RETHROW_EXCEPTION( er, error, "sftp rmdir recursive '${remote_path}' failed", ("remote_path",remote_path) ); - } - } - - file_attrib client::stat( const fc::path& remote_path ){ - my->init_sftp(); - LIBSSH2_SFTP_ATTRIBUTES att; - int ec; - { - fc::scoped_lock scp_stat_lock(my->scp_stat_mutex); - ec = my->call_ssh2_function(boost::bind(libssh2_sftp_stat_ex, my->sftp, - remote_path.generic_string().c_str(), remote_path.generic_string().size(), - LIBSSH2_SFTP_STAT, &att)); - } - if( ec ) - return file_attrib(); - file_attrib ft; - ft.size = att.filesize; - ft.permissions = att.permissions; - return ft; - } - void client::create_directories( const fc::path& rdir, int mode ) { - boost::filesystem::path dir = rdir; - boost::filesystem::path p; - auto pitr = dir.begin(); - while( pitr != dir.end() ) { - p /= *pitr; - if( !stat( p ).exists() ) { - mkdir(p,mode); - } - ++pitr; - } - } - - void client::mkdir( const fc::path& rdir, int mode ) { - try { - auto s = stat(rdir); - if( s.is_directory() ) - return; - else if( s.exists() ) - FC_THROW( "File already exists at path ${path}", ("path",rdir) ); - - { - fc::scoped_lock scp_mkdir_lock(my->scp_mkdir_mutex); - my->call_ssh2_function_throw(boost::bind(libssh2_sftp_mkdir_ex, my->sftp, - rdir.generic_string().c_str(), rdir.generic_string().size(), mode), - "sftp mkdir error"); - } - } catch ( fc::exception& er ) { - FC_RETHROW_EXCEPTION( er, error, "sftp failed to create directory '${directory}'", ( "directory", rdir ) ); - } - } - - void client::set_remote_system_is_windows(bool is_windows /* = true */) { - my->remote_system_is_windows = is_windows; - } - - - file_attrib::file_attrib() - :size(0),uid(0),gid(0),permissions(0),atime(0),mtime(0) - { } - - bool file_attrib::is_directory() { - return LIBSSH2_SFTP_S_ISDIR(permissions); - } - bool file_attrib::is_file() { - return LIBSSH2_SFTP_S_ISREG(permissions); - } - bool file_attrib::exists() { - return 0 != permissions; - } - - detail::client_impl::client_impl() : - session(nullptr), - knownhosts(nullptr), - sftp(nullptr), - agent(nullptr), - _trace_level(0), // was LIBSSH2_TRACE_ERROR - logr(fc::logger::get( "fc::ssh::client" )), - remote_system_is_windows(false) { - logr.set_parent( fc::logger::get( "default" ) ); - } - - detail::client_impl::~client_impl() { - close(); - } - - /* static */ - void detail::client_impl::kbd_callback(const char *name, int name_len, - const char *instruction, int instruction_len, int num_prompts, - const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, - LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, - void **abstract) { - detail::client_impl* self = (client_impl*)*abstract; - - - for (int i = 0; i < num_prompts; i++) { - fwrite(prompts[i].text, 1, prompts[i].length, stdout); - - if( self->upass.size() == 0 ) { - /** TODO: add keyboard callback here... - fgets(buf, sizeof(buf), stdin); - n = strlen(buf); - while (n > 0 && strchr("\r\n", buf[n - 1])) - n--; - buf[n] = 0; - -#ifdef WIN32 // fix warning -# define strdup _strdup -#endif - responses[i].text = strdup(buf); - responses[i].length = n; - */ - responses[i].text = nullptr; - responses[i].length = 0; - } else { - responses[i].text = strdup(self->upass.c_str()); - responses[i].length = self->upass.size(); - } - } - } - - void detail::client_impl::connect() { - try { - if( libssh2_init(0) < 0 ) - FC_THROW( "Unable to init libssh2" ); - - auto eps = fc::asio::tcp::resolve( hostname, boost::lexical_cast(port) ); - if( eps.size() == 0 ) - FC_THROW( "Unable to resolve host '${host}'", ("host",hostname) ); - - sock.reset( new boost::asio::ip::tcp::socket( fc::asio::default_io_service() ) ); - - bool resolved = false; - for( uint32_t i = 0; i < eps.size(); ++i ) { - std::stringstream ss; ss << eps[i]; - try { - boost::system::error_code ec; - fc_ilog( logr, "Attempting to connect to ${endpoint}", ("endpoint",ss.str().c_str()) ); - fc::asio::tcp::connect( *sock, eps[i] ); - endpt = eps[i]; - resolved = true; - break; - } catch ( fc::exception& er ) { - fc_ilog( logr, "Failed to connect to ${endpoint}\n${error_reprot}", - ("endpoint",ss.str().c_str())("error_report", er.to_detail_string()) ); - sock->close(); - } - } - if( !resolved ) - FC_THROW( "Unable to connect to any resolved endpoint for ${host}:${port}", - ("host", hostname).set("port",port) ); - - session = libssh2_session_init(); - libssh2_trace( session, _trace_level ); - libssh2_trace_sethandler( session, this, client_impl::handle_trace ); - - *libssh2_session_abstract(session) = this; - - libssh2_session_set_blocking( session, 0 ); - try { - call_ssh2_function_throw(boost::bind(libssh2_session_handshake, session, sock->native()), - "SSH Handshake error: ${code} - ${message}"); - } catch (fc::exception& er) { - FC_RETHROW_EXCEPTION( er, error, "Error during SSH handshake" );; - } - //const char* fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); - //slog( "fingerprint: %s", fingerprint ); - - // try to authenticate, throw on error. - try { - authenticate(); - } catch (fc::exception& er) { - FC_RETHROW_EXCEPTION( er, error, "Error during SSH authentication" );; - } - //slog("."); - } catch ( fc::exception& er ) { - elog( "Unable to connect to ssh server: ${detail}", ("detail", er.to_detail_string().c_str()) ); - close(); - FC_RETHROW_EXCEPTION( er, error, "Unable to connect to ssh server" );; - } catch ( ... ) { - close(); - FC_THROW( "Unable to connect to ssh server", ("exception", fc::except_str() ) ); - } - } - - /* static */ - void detail::client_impl::handle_trace( LIBSSH2_SESSION* session, void* context, const char* data, size_t length ) { - client_impl* my = (client_impl*)context; - fc::string str(data,length); - fc_wlog( my->logr, "${message}", ("message",str) ); - } - - void detail::client_impl::close() { - if( session ) { - if( sftp ) { - try { - call_ssh2_function(boost::bind(libssh2_sftp_shutdown, sftp)); - }catch(...){ - fc_wlog( logr, "caught closing sftp session" ); - } - sftp = 0; - } - - if (agent) { - libssh2_agent_disconnect(agent); - libssh2_agent_free(agent); - agent = nullptr; - } - - try { - call_ssh2_function(boost::bind(libssh2_session_disconnect_ex, session, SSH_DISCONNECT_BY_APPLICATION, "exit cleanly", "")); - call_ssh2_function(boost::bind(libssh2_session_free, session), false); - } catch ( ... ){ - fc_wlog( logr, "caught freeing session" ); - } - session = 0; - try { - if( sock ) - sock->close(); - } catch ( ... ){ - fc_wlog( logr, "caught error closing socket" ); - } - sock.reset(0); - try { - if( read_prom ) - read_prom->wait(); - } catch ( ... ){ - fc_wlog( logr, "caught error waiting on read" ); - } - try { - if( write_prom ) - write_prom->wait(); - } catch ( ... ){ - fc_wlog( logr, "caught error waiting on write" ); - } - } - } - - void detail::client_impl::authenticate() { - try { - char * alist = NULL; - // libssh2_userauth_list has strange enough behavior that we can't use the - // call_blocking_libssh2_function-type functions to wait and retry, so we must - // explicitly lock around the libssh2_userauth_list calls. - // hence, this anonymous scope: - { - fc::unique_lock lock(ssh_session_mutex); - - alist = libssh2_userauth_list(session, uname.c_str(),uname.size()); - - if(alist==NULL) { - char * msg = 0; - int ec = 0; - if(libssh2_userauth_authenticated(session)) - return; // CONNECTED! - ec = libssh2_session_last_error(session,&msg,NULL,0); - - while( !alist && (ec == LIBSSH2_ERROR_EAGAIN) ) { - wait_on_socket(); - alist = libssh2_userauth_list(session, uname.c_str(), uname.size()); - ec = libssh2_session_last_error(session,&msg,NULL,0); - } - if( !alist ) { - FC_THROW( "Error getting authorization list: ${code} - ${message}", - ("code",ec).set("message",msg)); - } - } - } // end anonymous scope - - std::vector split_alist; - bool pubkey = false; - bool pass = false; - bool keybd = false; - boost::split( split_alist, alist, boost::is_any_of(",") ); - std::for_each( split_alist.begin(), split_alist.end(), [&](const std::string& s){ - if( s == "publickey" ) - pubkey = true; - else if( s == "password" ) - pass = true; - else if( s == "keyboard-interactive" ) - keybd = true; - else - fc_dlog( logr, "Unknown/unsupported authentication type '${auth_type}'", ("auth_type",s.c_str())); - }); - - if( pubkey && try_pub_key() ) - return; - if( pass && try_pass() ) - return; - if( keybd && try_keyboard() ) - return; - } catch ( fc::exception& er ) { - FC_RETHROW_EXCEPTION( er, error, "Unable to authenticate ssh connection" ); - } - FC_THROW( "Unable to authenticate ssh connection" ); - } // authenticate() - - bool detail::client_impl::try_pass() { - return !call_ssh2_function(boost::bind(libssh2_userauth_password_ex, session, uname.c_str(), uname.size(), - upass.c_str(), upass.size(), (LIBSSH2_PASSWD_CHANGEREQ_FUNC((*)))NULL)); - } - bool detail::client_impl::try_keyboard() { - return !call_ssh2_function(boost::bind(libssh2_userauth_keyboard_interactive_ex, session, - uname.c_str(), uname.size(), &client_impl::kbd_callback)); - } - bool detail::client_impl::try_pub_key() { - if (privkey.size()) { - if (!call_ssh2_function(boost::bind(libssh2_userauth_publickey_fromfile_ex, - session, - uname.c_str(), uname.size(), - pubkey.c_str(), - privkey.c_str(), - passphrase.c_str()))) - return true; // successful authentication from file - fc_ilog( logr, "failed to authenticate with private key from file '${privkey_filename}'", ("privkey_filename",privkey)); - } else - fc_ilog( logr, "no private key file set, skiping pubkey authorization from file"); - - agent = libssh2_agent_init(session); - if (!agent) { - fc_wlog( logr, "failed to initialize ssh-agent support"); - return false; - } - - if (call_ssh2_function(boost::bind(libssh2_agent_connect, agent))) { - fc_ilog( logr, "failed to connect to ssh-agent"); - return false; - } - - if (call_ssh2_function(boost::bind(libssh2_agent_list_identities, agent))) { - fc_ilog( logr, "failed requesting identities from ssh-agent"); - return false; - } - - struct libssh2_agent_publickey *prev_identity = NULL; - while (1) { - struct libssh2_agent_publickey *identity; - int ec = call_ssh2_function(boost::bind(libssh2_agent_get_identity, agent, &identity, prev_identity)); - if (ec == 1) - break; // done iterating over keys - if (ec < 0) { - fc_ilog( logr, "failed obtaining identity from ssh-agent"); - return false; - } - - if (call_ssh2_function(boost::bind(libssh2_agent_userauth, agent, uname.c_str(), identity))) - fc_ilog( logr, "unable to authenticate with public key '${key_comment}'", ("key_comment",identity->comment)); - else { - fc_ilog( logr, "authenticated with public key '${key_comment}'", ("key_comment",identity->comment)); - return true; - } - prev_identity = identity; - } - return false; - } - - void detail::client_impl::wait_on_socket(int additionalDirections /* = 0 */) { - int dir = libssh2_session_block_directions(session); - dir |= additionalDirections; - if( !dir ) - return; - - fc::promise::ptr rprom, wprom; - if( dir & LIBSSH2_SESSION_BLOCK_INBOUND ) { - fc::scoped_lock lock(this->_spin_lock); - if( !read_prom ) { - read_prom.reset( new fc::promise("read_prom") ); - sock->async_read_some( boost::asio::null_buffers(), - [=]( const boost::system::error_code& e, size_t ) { - fc::scoped_lock lock(this->_spin_lock); - this->read_prom->set_value(e); - this->read_prom.reset(nullptr); - } ); - } - rprom = read_prom; - } - - if( dir & LIBSSH2_SESSION_BLOCK_OUTBOUND ) { - fc::scoped_lock lock(this->_spin_lock); - if( !write_prom ) { - write_prom.reset( new fc::promise("write_prom") ); - sock->async_write_some( boost::asio::null_buffers(), - [=]( const boost::system::error_code& e, size_t ) { - fc::scoped_lock lock(this->_spin_lock); - this->write_prom->set_value(e); - this->write_prom.reset(0); - } ); - } - wprom = write_prom; - } - - boost::system::error_code ec; - if( rprom.get() && wprom.get() ) { - typedef fc::future fprom; - fprom fw(wprom); - fprom fr(rprom); -#if 0 - // EMF: at present there are known bugs in fc::wait_any, and it will fail to wake up - // when one of the futures is ready. - int r = fc::wait_any( fw, fr, fc::seconds(1) ); -#else - int r; - while (1) { - if (fw.ready()) { - r = 0; break; - } - if (fr.ready()) { - r = 1; break; - } - fc::usleep(fc::microseconds(5000)); - } -#endif - switch( r ) { - case 0: - if( wprom->wait() ) { - FC_THROW( "Socket Error ${message}", - ( "message", boost::system::system_error(rprom->wait() ).what() ) ); - } - break; - case 1: - if( rprom->wait() ) { - FC_THROW( "Socket Error ${message}", - ( "message", boost::system::system_error(rprom->wait() ).what() ) ); - } - break; - } - } else if( rprom ) { - if( rprom->wait() ) { - FC_THROW( "Socket Error ${message}", - ( "message", boost::system::system_error(rprom->wait() ).what() ) ); - } - } else if( wprom ) { - if( wprom->wait() ) { - FC_THROW( "Socket Error ${message}", - ( "message", boost::system::system_error(wprom->wait() ).what() ) ); - } - } - } - void detail::client_impl::init_sftp() { - if( !sftp ) - sftp = call_ssh2_ptr_function_throw(boost::bind(libssh2_sftp_init,session), - "init sftp error ${code} - ${message}"); - } - - - LIBSSH2_CHANNEL* detail::client_impl::open_channel( const fc::string& pty_type ) { - LIBSSH2_CHANNEL* chan = 0; - /* anonymous scope */ { - fc::scoped_lock channel_open_lock(channel_open_mutex); - - chan = call_ssh2_ptr_function_throw(boost::bind(libssh2_channel_open_ex, session, - "session", sizeof("session") - 1, - LIBSSH2_CHANNEL_WINDOW_DEFAULT, - LIBSSH2_CHANNEL_PACKET_DEFAULT, - (const char*)NULL, 0), - "libssh2_channel_open_session failed: ${message}"); - } - - if( pty_type.size() ) - call_ssh2_function_throw(boost::bind(libssh2_channel_request_pty_ex, chan, pty_type.c_str(), pty_type.size(), - (char *)NULL, 0, LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT, - LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX), - "libssh2_channel_req_pty failed: ${message}"); - return chan; - } - -} } diff --git a/src/ssh/client_impl.hpp b/src/ssh/client_impl.hpp deleted file mode 100644 index 606bd36..0000000 --- a/src/ssh/client_impl.hpp +++ /dev/null @@ -1,280 +0,0 @@ -#define NOMINMAX -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include - -// include this to get acess to the details of the LIBSSH2_SESSION structure, so -// we can verify that all data has really been sent when libssh2 says it has. -#include <../src/libssh2_priv.h> - -namespace fc { namespace ssh { - - namespace detail { - - class client_impl { - public: - client_impl(); - ~client_impl(); - - LIBSSH2_SESSION* session; - LIBSSH2_KNOWNHOSTS* knownhosts; - LIBSSH2_SFTP* sftp; - LIBSSH2_AGENT* agent; - - std::unique_ptr sock; - boost::asio::ip::tcp::endpoint endpt; - - fc::mutex ssh_session_mutex; - fc::mutex channel_open_mutex; - fc::mutex process_startup_mutex; - fc::mutex scp_send_mutex; - fc::mutex scp_stat_mutex; - fc::mutex scp_mkdir_mutex; - fc::mutex scp_rmdir_mutex; - fc::mutex scp_unlink_mutex; - fc::mutex scp_close_mutex; - fc::mutex scp_readdir_mutex; - fc::mutex scp_open_mutex; - - fc::string uname; - fc::string upass; - fc::string pubkey; - fc::string privkey; - fc::string passphrase; - fc::string hostname; - uint16_t port; - bool session_connected; - fc::promise::ptr read_prom; - fc::promise::ptr write_prom; - fc::spin_lock _spin_lock; - int _trace_level; - logger logr; - - bool remote_system_is_windows; // true if windows, false if unix, used for command-line quoting and maybe filename translation - - LIBSSH2_CHANNEL* open_channel( const fc::string& pty_type ); - static void kbd_callback(const char *name, int name_len, - const char *instruction, int instruction_len, int num_prompts, - const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, - LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, - void **abstract); - - void connect(); - - static void handle_trace( LIBSSH2_SESSION* session, void* context, const char* data, size_t length ); - - void close(); - void authenticate(); - - bool try_pass(); - bool try_keyboard(); - bool try_pub_key(); - - // don't call this "unlocked" version directly - template - int call_ssh2_function_unlocked(const T& lambda, bool check_for_errors = true); - - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - template - int call_ssh2_function(const T& lambda, bool check_for_errors = true); - - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - // if libssh2 returns an error, get extended info and throw a message with ${code} and ${message} - // set appropriately. - template - int call_ssh2_function_throw(const T& lambda, const char* message = "libssh2 call failed ${code} - ${message}", bool check_for_errors = true); - - // this version is a little different, it handles functions like libssh2_sftp_init which return - // a pointer instead of an int. These retry automatically if the result is NULL and the error - // is LIBSSH2_ERROR_EAGAIN - template - return_type call_ssh2_ptr_function_throw(std::function lambda, const char* message = "libssh2 call failed ${code} - ${message}", bool check_for_errors = true); - - void wait_on_socket(int additionalDirections = 0); - - void init_sftp(); - }; - - - // #define OLD_BLOCKING, - // the OLD_BLOCKING version of these functions will ensure that if a libssh2 function returns - // LIBSSH2_ERROR_EAGAIN, no other libssh2 functions will be called until that function has been - // called again and returned some other value. - // - // if you don't define this and use the new version of this, we will release the lock and let - // other libssh2 functions be called *unless* it appears that there was unwritten data. - // - // the OLD_BLOCKING version is too conservative -- if you try to read on a channel that doesn't - // have any data, you're likely to deadlock. The new version is not heavily tested and may be - // too lax, time will tell. -#ifdef OLD_BLOCKING - // don't call this "unlocked" version directly - template - int client_impl::call_ssh2_function_unlocked(const T& lambda, bool check_for_errors /* = true */) { - int ec = lambda(); - while (ec == LIBSSH2_ERROR_EAGAIN ) { - wait_on_socket(); - ec = lambda(); - } - - // this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN - // but the internal session data indicates a data write is still in progress - // set check_for_errors to false when closing the connection - assert(!check_for_errors || !session->packet.olen); - - return ec; - } - - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - template - int client_impl::call_ssh2_function(const T& lambda, bool check_for_errors /* = true */) { - fc::scoped_lock lock(ssh_session_mutex); - return call_ssh2_function_unlocked(lambda, check_for_errors); - } -#else - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - template - int client_impl::call_ssh2_function(const T& lambda, bool check_for_errors /* = true */) { - fc::unique_lock lock(ssh_session_mutex); - int ec = lambda(); - while (ec == LIBSSH2_ERROR_EAGAIN) { - bool unlock_to_wait = !session->packet.olen; - if (unlock_to_wait) - lock.unlock(); - wait_on_socket(); - if (unlock_to_wait) - lock.lock(); - ec = lambda(); - } - // this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN - // but the internal session data indicates a data write is still in progress - // set check_for_errors to false when closing the connection - assert(!check_for_errors || !session->packet.olen); - return ec; - } -#endif - -#ifdef OLD_BLOCKING - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - // if libssh2 returns an error, get extended info and throw a message with ${code} and ${message} - // set appropriately. - template - int client_impl::call_ssh2_function_throw(const T& lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) { - fc::scoped_lock lock(ssh_session_mutex); - int ec = call_ssh2_function_unlocked(lambda, check_for_errors); - - if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) { - ec = libssh2_sftp_last_error(sftp); - FC_THROW(message, ("code", ec).set("message", "SFTP protocol error")); - } else if( ec < 0 ) { - char* msg = 0; - ec = libssh2_session_last_error( session, &msg, 0, 0 ); - FC_THROW(message, ("code",ec).set("message",msg)); - } - return ec; - } -#else - // calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN - // if libssh2 returns an error, get extended info and throw a message with ${code} and ${message} - // set appropriately. - template - int client_impl::call_ssh2_function_throw(const T& lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) { - fc::unique_lock lock(ssh_session_mutex); - int ec = lambda(); - while (ec == LIBSSH2_ERROR_EAGAIN) { - bool unlock_to_wait = !session->packet.olen; - if (unlock_to_wait) - lock.unlock(); - wait_on_socket(); - if (unlock_to_wait) - lock.lock(); - ec = lambda(); - } - // this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN - // but the internal session data indicates a data write is still in progress - // set check_for_errors to false when closing the connection - assert(!check_for_errors || !session->packet.olen); - - if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) { - ec = libssh2_sftp_last_error(sftp); - FC_THROW(message, ("code", ec).set("message", "SFTP protocol error")); - } else if( ec < 0 ) { - char* msg = 0; - ec = libssh2_session_last_error( session, &msg, 0, 0 ); - FC_THROW(message, ("code",ec).set("message",msg)); - } - return ec; - } -#endif - -#ifdef OLD_BLOCKING - // this version is a little different, it handles functions like libssh2_sftp_init which return - // a pointer instead of an int. These retry automatically if the result is NULL and the error - // is LIBSSH2_ERROR_EAGAIN - template - return_type client_impl::call_ssh2_ptr_function_throw(std::function lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) { - fc::scoped_lock lock(ssh_session_mutex); - return_type ret = lambda(); - while (!ret) { - char* msg = 0; - int ec = libssh2_session_last_error(session,&msg,NULL,0); - if ( ec == LIBSSH2_ERROR_EAGAIN ) { - wait_on_socket(); - ret = lambda(); - } else if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) { - ec = libssh2_sftp_last_error(sftp); - FC_THROW(message, ("code", ec).set("message", "SFTP protocol error")); - } else { - ec = libssh2_session_last_error( session, &msg, 0, 0 ); - FC_THROW(message, ("code",ec).set("message",msg)); - } - } - assert(!check_for_errors || !session->packet.olen); - - return ret; - } -#else - // this version is a little different, it handles functions like libssh2_sftp_init which return - // a pointer instead of an int. These retry automatically if the result is NULL and the error - // is LIBSSH2_ERROR_EAGAIN - template - return_type client_impl::call_ssh2_ptr_function_throw(std::function lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) { - fc::unique_lock lock(ssh_session_mutex); - return_type ret = lambda(); - while (!ret) { - char* msg = 0; - int ec = libssh2_session_last_error(session,&msg,NULL,0); - if ( ec == LIBSSH2_ERROR_EAGAIN ) { - bool unlock_to_wait = !session->packet.olen; - if (unlock_to_wait) - lock.unlock(); - wait_on_socket(); - if (unlock_to_wait) - lock.lock(); - ret = lambda(); - } else if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) { - ec = libssh2_sftp_last_error(sftp); - FC_THROW(message, ("code", ec).set("message", "SFTP protocol error")); - } else { - ec = libssh2_session_last_error( session, &msg, 0, 0 ); - FC_THROW(message, ("code",ec).set("message",msg)); - } - } - assert(!check_for_errors || !session->packet.olen); - - return ret; - } -#endif - } - -} } diff --git a/src/ssh/process.cpp b/src/ssh/process.cpp deleted file mode 100644 index 8794c11..0000000 --- a/src/ssh/process.cpp +++ /dev/null @@ -1,334 +0,0 @@ -#define NOMINMAX // prevent windows from defining min and max macros -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "client_impl.hpp" - -#if defined (_MSC_VER) -#pragma warning (disable : 4355) -#endif - -namespace fc { namespace ssh { - - namespace detail { - class process_impl; - class process_istream : public fc::istream { - public: - process_istream( process_impl& p, int c ) - :proc(p),chan(c){} - - virtual size_t readsome( char* buf, size_t len ); - - virtual bool eof() const; - - process_impl& proc; - int chan; - }; - - class process_ostream : public fc::ostream { - public: - process_ostream( process_impl& p ) - :proc(p){} - - virtual size_t writesome( const char* buf, size_t len ); - virtual void close(); - virtual void flush(); - - process_impl& proc; - }; - - class process_impl { - public: - process_impl( client_ptr c ); - ~process_impl(); - //process_impl( const client& c, const fc::string& cmd, const fc::string& pty_type ); - void exec(const fc::path& exe, vector args, - const fc::path& work_dir /* = fc::path() */, fc::iprocess::exec_opts opts /* = open_all */); - - - int read_some( char* data, size_t len, int stream_id ); - int write_some( const char* data, size_t len, int stream_id ); - void flush(); - void send_eof(); - - LIBSSH2_CHANNEL* chan; - client_ptr sshc; - buffered_ostream_ptr buffered_std_in; - buffered_istream_ptr buffered_std_out; - buffered_istream_ptr buffered_std_err; - - fc::string command; - fc::promise::ptr result; - - fc::optional return_code; - fc::ostring return_signal; - fc::ostring return_signal_message; - private: - static fc::string windows_shell_escape(const fc::string& str); - static fc::string unix_shell_escape(const fc::string& str); - static fc::string windows_shell_escape_command(const fc::path& exe, const vector& args); - static fc::string unix_shell_escape_command(const fc::path& exe, const vector& args); - }; - - } // end namespace detail - - - process::process(client_ptr c) : - my(new detail::process_impl(c)) - {} - - process::~process() - {} - - iprocess& process::exec( const fc::path& exe, vector args, - const fc::path& work_dir /* = fc::path() */, exec_opts opts /* = open_all */ ) { - my->exec(exe, args, work_dir, opts); - return *this; - } - - /** - * Blocks until the result code of the process has been returned. - */ - int process::result() { - if (!my->return_code && !my->return_signal) { - // we don't have any cached exit status, so wait and obtain the values now - my->sshc->my->call_ssh2_function(boost::bind(libssh2_channel_wait_eof, my->chan)); - my->sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_wait_closed, my->chan), - "Error waiting on socket to close: ${message}"); - - char* exit_signal; - char* error_message; - libssh2_channel_get_exit_signal(my->chan, &exit_signal, NULL, &error_message, NULL, NULL, NULL); - if (exit_signal) { - // process terminated with a signal - my->return_signal = exit_signal; - libssh2_free(my->chan->session, exit_signal); - if (error_message) { - my->return_signal_message = error_message; - libssh2_free(my->chan->session, error_message); - } - } else - my->return_code = libssh2_channel_get_exit_status(my->chan); - } - if (my->return_signal) - FC_THROW("process terminated with signal ${signal}: ${signal_message}", ("signal", *my->return_signal) - ("signal_message", my->return_signal_message ? *my->return_signal_message : "")); - else - return *my->return_code; - } - - void process::kill() { - elog("error: fc::ssh::process::kill() not supported"); - } - - - /** - * @brief returns a stream that writes to the procss' stdin - */ - fc::buffered_ostream_ptr process::in_stream() { - return my->buffered_std_in; - } - - /** - * @brief returns a stream that reads from the process' stdout - */ - fc::buffered_istream_ptr process::out_stream() { - return my->buffered_std_out; - } - - /** - * @brief returns a stream that reads from the process' stderr - */ - fc::buffered_istream_ptr process::err_stream() { - return my->buffered_std_err; - } - - void detail::process_impl::flush() { - if( !chan ) return; - /* channel_flush deleates input buffer, and does not ensure writes go out - * - int ec = libssh2_channel_flush_ex( chan, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); - while( ec == LIBSSH2_ERROR_EAGAIN ) { - sshc.my->wait_on_socket(); - ec = libssh2_channel_flush_ex( chan, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA ); - } - ec = libssh2_channel_flush( chan ); - while( ec == LIBSSH2_ERROR_EAGAIN ) { - sshc.my->wait_on_socket(); - ec = libssh2_channel_flush( chan ); - } - if( ec < 0 ) { - FC_THROW( "ssh flush failed", ( "channel_error", ec) ); - } - */ - } - - int detail::process_impl::read_some( char* data, size_t len, int stream_id ){ - if( !sshc->my->session ) { FC_THROW( "Session closed" ); } - int rc; - char* buf = data; - size_t buflen = len; - do { - rc = sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_read_ex, chan, stream_id, buf, buflen), - "read failed: ${message}"); - if( rc > 0 ) { - buf += rc; - buflen -= rc; - return buf-data; - } else if( rc == 0 ) { - if( libssh2_channel_eof( chan ) ) - return -1; // eof - sshc->my->wait_on_socket(); - } - } while( rc >= 0 && buflen); - return buf-data; - } - - int detail::process_impl::write_some( const char* data, size_t len, int stream_id ) { - if( !sshc->my->session ) { FC_THROW( "Session closed" ); } - - int rc; - const char* buf = data; - size_t buflen = len; - do { - rc = sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_write_ex, chan, stream_id, buf, buflen), - "write failed: ${message}"); - if( rc > 0 ) { - buf += rc; - buflen -= rc; - return buf-data; - } else if( rc == 0 ) { - if( libssh2_channel_eof( chan ) ) { - FC_THROW( "EOF" ); - //return -1; // eof - } - } - } while( rc >= 0 && buflen); - return buf-data; - } - - void detail::process_impl::send_eof() { - if( sshc->my->session ) - sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_send_eof, chan), - "send eof failed: ${message}"); - } - - size_t detail::process_istream::readsome( char* buf, size_t len ) { - int bytesRead = proc.read_some(buf, len, chan); - if (bytesRead < 0) - FC_THROW("EOF"); - else - return bytesRead; - } - - bool detail::process_istream::eof()const { - return 0 != libssh2_channel_eof( proc.chan ); - } - - size_t detail::process_ostream::writesome( const char* buf, size_t len ) { - return proc.write_some(buf, len, 0); - } - - void detail::process_ostream::close(){ - proc.send_eof(); - } - - void detail::process_ostream::flush(){ - proc.flush(); - } - - detail::process_impl::process_impl( client_ptr c ) - :chan(nullptr), - sshc(c), - buffered_std_in(new buffered_ostream(ostream_ptr(new process_ostream(*this)))), - buffered_std_out(new buffered_istream(istream_ptr(new process_istream(*this, 0)))), - buffered_std_err(new buffered_istream(istream_ptr(new process_istream(*this, SSH_EXTENDED_DATA_STDERR)))) - { - } - - detail::process_impl::~process_impl() { - if (chan) { - sshc->my->call_ssh2_function(boost::bind(libssh2_channel_free, chan)); - chan = NULL; - } - } - - // these rules work pretty well for a standard bash shell on unix - fc::string detail::process_impl::unix_shell_escape(const fc::string& str) { - if (str.find_first_of(" ;&|><*?`$(){}[]!#'\"") == fc::string::npos) - return str; - fc::string escaped_quotes(str); - for (size_t start = escaped_quotes.find("'"); - start != fc::string::npos; - start = escaped_quotes.find("'", start + 5)) - escaped_quotes.replace(start, 1, "'\"'\"'"); - fc::string escaped_str("\'"); - escaped_str += escaped_quotes; - escaped_str += "\'"; - return escaped_str; - } - fc::string detail::process_impl::unix_shell_escape_command(const fc::path& exe, const vector& args) { - fc::stringstream command_line; - command_line << unix_shell_escape(exe.string()); - for (unsigned i = 0; i < args.size(); ++i) - command_line << " " << unix_shell_escape(args[i]); - return command_line.str(); - } - - // windows command-line escaping rules are a disaster, partly because how the command-line is - // parsed depends on what program you're running. In windows, the command line is passed in - // as a single string, and the process is left to interpret it as it sees fit. The standard - // C runtime uses one set of rules, the function CommandLineToArgvW usually used by - // GUI-mode programs uses a different set. - // Here we try to find a common denominator that works well for simple cases - // it's only minimally tested right now due to time constraints. - fc::string detail::process_impl::windows_shell_escape(const fc::string& str) { - if (str.find_first_of(" \"") == fc::string::npos) - return str; - fc::string escaped_quotes(str); - for (size_t start = escaped_quotes.find("\""); - start != fc::string::npos; - start = escaped_quotes.find("\"", start + 2)) - escaped_quotes.replace(start, 1, "\\\""); - fc::string escaped_str("\""); - escaped_str += escaped_quotes; - escaped_str += "\""; - return escaped_str; - } - fc::string detail::process_impl::windows_shell_escape_command(const fc::path& exe, const vector& args) { - fc::stringstream command_line; - command_line << windows_shell_escape(exe.string()); - for (unsigned i = 0; i < args.size(); ++i) - command_line << " " << windows_shell_escape(args[i]); - return command_line.str(); - } - - void detail::process_impl::exec(const fc::path& exe, vector args, - const fc::path& work_dir /* = fc::path() */, - fc::iprocess::exec_opts opts /* = open_all */) { - chan = sshc->my->open_channel(""); - - sshc->my->call_ssh2_function(boost::bind(libssh2_channel_handle_extended_data2, chan, LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL)); - - try { - fc::scoped_lock process_startup_lock(sshc->my->process_startup_mutex); - fc::string command_line = sshc->my->remote_system_is_windows ? windows_shell_escape_command(exe, args) : unix_shell_escape_command(exe, args); - sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_process_startup, chan, "exec", sizeof("exec") - 1, command_line.c_str(), command_line.size()), - "exec failed: ${message}"); // equiv to libssh2_channel_exec(chan, cmd) macro - } catch (fc::exception& er) { - elog( "error starting process" ); - FC_RETHROW_EXCEPTION(er, error, "error starting process"); - } - } - -} }