#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 } } }