#include "rsmcore/session.hh" #include "radixbug/bug.hh" #include #include #include #include #include #include #include namespace rsm { void assert_ssh_session(ssh_session session, const char* error_message) { if (session == nullptr) { throw std::runtime_error(error_message); } } void assert_ssh_channel(ssh_channel channel, const char* error_message) { if (channel == nullptr) { throw std::runtime_error(error_message); } } class ChannelImpl { public: ssh_channel channel = nullptr; explicit ChannelImpl(ssh_session session) { channel = ssh_channel_new(session); } ~ChannelImpl() { if (channel != nullptr) { ssh_channel_close(channel); ssh_channel_free(channel); } } }; // class ChannelImpl Channel::Channel() { radix_tagged_line("Channel::Channel()"); p = nullptr; } Channel::~Channel() { radix_tagged_line("Channel::~Channel()"); if (p != nullptr) delete p; } bool Channel::open() { assert_ssh_channel(p->channel, "open() -- Channel is not allocated."); int rc = ssh_channel_open_session(p->channel); return (rc > -1); } bool Channel::isOpen() const { // Perhaps we should just return false assert_ssh_channel(p->channel, "isOpen() -- Channel is not allocated."); int rc = ssh_channel_is_open(p->channel); return (rc != 0); } void Channel::close() { if (p->channel != nullptr) { ssh_channel_close(p->channel); ssh_channel_free(p->channel); p->channel = nullptr; } } bool Channel::exec(QString command) { assert_ssh_channel(p->channel, "exec() -- Channel is not allocated."); int rc = ssh_channel_request_exec(p->channel, command.toStdString().c_str()); return !(rc < 0); } QString Channel::readExecOut() { assert_ssh_channel(p->channel, "readExecOut() -- Channel is not allocated."); char buffer[256]; int nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 0); if (nbytes > 0) { buffer[nbytes] = '\0'; QString result = buffer; return result; } return QString(""); } QString Channel::readExecErr() { assert_ssh_channel(p->channel, "readExecErr() -- Channel is not allocated."); char buffer[256]; int nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 1); if (nbytes > 0) { buffer[nbytes] = '\0'; QString result = buffer; return result; } return QString(""); } // ---------------------------------------------------------------------------- class SFTPSessionImpl { public: sftp_session sftpSession = nullptr; int errorCode = SSH_OK; const char* errorMsg = nullptr; explicit SFTPSessionImpl(ssh_session sshSession) { sftpSession = sftp_new(sshSession); if (nullptr == sftpSession) { errorCode = SSH_ERROR; errorMsg = ssh_get_error(sshSession); return; } // SSH_OK, SSH_ERROR, etc; see libssh.h int rc = sftp_init(sftpSession); if (rc != SSH_OK) { errorCode = sftp_get_error(sftpSession); sftp_free(sftpSession); sftpSession = nullptr; } } ~SFTPSessionImpl() { if (sftpSession != nullptr) sftp_free(sftpSession); } }; // class SFTPSessionImpl SFTPSession::SFTPSession() { p = nullptr; } SFTPSession::~SFTPSession() { if (p != nullptr) delete p; } bool SFTPSession::mkdir(QString dirName) { // S_IRWXU: create dir with read/write/exec by owner only int rc = sftp_mkdir(p->sftpSession, dirName.toStdString().c_str(), S_IRWXU); if (rc != SSH_OK) { if (sftp_get_error(p->sftpSession) != SSH_FX_FILE_ALREADY_EXISTS) { p->errorCode = SSH_ERROR; p->errorMsg = ssh_get_error(p->sftpSession); return false; } } return true; } bool SFTPSession::rmdir(QString dirName) { int rc = sftp_rmdir(p->sftpSession, dirName.toStdString().c_str()); if (rc != SSH_OK) { if (sftp_get_error(p->sftpSession) != SSH_FX_FILE_ALREADY_EXISTS) { p->errorCode = SSH_ERROR; p->errorMsg = ssh_get_error(p->sftpSession); return false; } } return true; } SFTPFile* SFTPSession::openFile(QString filename, int accessType) { return new SFTPFile(*this, filename.toStdString().c_str(), accessType); } SFTPDir* SFTPSession::openDir(QString dirName) { SFTPDir* sftpDir = new SFTPDir(this, dirName.toStdString().c_str()); return sftpDir; } QString SFTPSession::error() { return p->errorMsg; } QString SFTPSession::canonicalize(QString path) { char* str = sftp_canonicalize_path(p->sftpSession, path.toStdString().c_str()); if (nullptr == str) { ssh_string_free_char(str); return ""; } QString retVal = QString(str); ssh_string_free_char(str); return retVal; } // ---------------------------------------------------------------------------- class SFTPFileImpl { public: sftp_file file = nullptr; explicit SFTPFileImpl(sftp_session sftpSession, QString filename, int accessType) { file = sftp_open(sftpSession, filename.toStdString().c_str(), accessType, S_IRWXU); } ~SFTPFileImpl() { if (file != nullptr) sftp_close(file); } }; // class SFTPFileImpl SFTPFile::SFTPFile(SFTPSession& sftpSession, QString filename, int accessType) { p = new SFTPFileImpl(sftpSession.p->sftpSession, filename, accessType); } SFTPFile::~SFTPFile() { if (p != nullptr) delete p; } bool SFTPFile::isOpen() { return (p->file != nullptr); } // returns number of bytes written on success // on error, returns < 0 and ssh and sftp session errors are set ssize_t SFTPFile::write(QString str) { const char* buf = str.toStdString().c_str(); size_t count = strlen(buf); ssize_t nwritten = sftp_write(p->file, buf, count); return nwritten; } // returns empty string and closes file on error; isOpen() can be used to verify // success QString SFTPFile::read(size_t nBytes) { char* buf = new char[nBytes + 1]; buf[nBytes] = '\0'; ssize_t nBytesRead = sftp_read(p->file, buf, nBytes); if (nBytesRead < 0) { close(); return QString(); } QString str(buf); delete[] buf; return str; } bool SFTPFile::close() { int rc = sftp_close(p->file); if (SSH_NO_ERROR == rc) { p->file = nullptr; return true; } return false; } bool SFTPFile::seek(size_t offset) { int rc = sftp_seek64(p->file, offset); if (rc < 0) return false; return true; } // returns < 0 on error ssize_t SFTPFile::tell() { ssize_t result = static_cast(sftp_tell64(p->file)); return result; } // ---------------------------------------------------------------------------- class SFTPDirImpl { public: sftp_dir dir = nullptr; sftp_session sftp = nullptr; explicit SFTPDirImpl(sftp_session sftpSession, QString dirName) { dir = sftp_opendir(sftpSession, dirName.toStdString().c_str()); sftp = sftpSession; } ~SFTPDirImpl() { if (dir != nullptr) sftp_closedir(dir); } }; // class SFTPDirImpl SFTPDir::SFTPDir(SFTPSession* sftpSession, QString dirName) { p = new SFTPDirImpl(sftpSession->p->sftpSession, dirName); } SFTPDir::~SFTPDir() { if (p != nullptr) delete p; } bool SFTPDir::isOpen() { return (p->dir != nullptr); } bool SFTPDir::hasNext() { return (0 == sftp_dir_eof(p->dir)); } // returns SFTPAttributes structure on success or nullptr on failure // caller is responsible for deleting returned SFTPAttributes structure SFTPAttributes* SFTPDir::next() { sftp_attributes sftpResult = sftp_readdir(p->sftp, p->dir); if (nullptr == sftpResult) return nullptr; SFTPAttributes* retVal = new SFTPAttributes; retVal->name = QString(sftpResult->name); retVal->longname = QString(sftpResult->longname); retVal->flags = sftpResult->flags; retVal->type = sftpResult->type; retVal->size = sftpResult->size; retVal->uid = sftpResult->uid; retVal->gid = sftpResult->gid; retVal->owner = QString(sftpResult->owner); retVal->group = QString(sftpResult->group); retVal->permissions = sftpResult->permissions; retVal->atime64 = sftpResult->atime64; retVal->atime = sftpResult->atime; retVal->atime_nseconds = sftpResult->atime_nseconds; retVal->createtime = sftpResult->createtime; retVal->createtime_nseconds = sftpResult->createtime_nseconds; retVal->mtime64 = sftpResult->mtime64; retVal->mtime = sftpResult->mtime; retVal->mtime_nseconds = sftpResult->mtime_nseconds; retVal->acl = QString(ssh_string_get_char(sftpResult->acl)); retVal->extended_count = sftpResult->extended_count; retVal->extended_type = QString(ssh_string_get_char(sftpResult->extended_type)); retVal->extended_data = QString(ssh_string_get_char(sftpResult->extended_data)); return retVal; } bool SFTPDir::close() { int rc = sftp_closedir(p->dir); if (SSH_NO_ERROR == rc) { p->dir = nullptr; p->sftp = nullptr; return true; } return false; } // ---------------------------------------------------------------------------- class SessionImpl { public: ssh_session session = nullptr; SessionImpl() { session = ssh_new(); assert_ssh_session(session, "Failed to allocate new ssh session."); } ~SessionImpl() { if (session != nullptr) { ssh_free(session); ssh_finalize(); } } }; // class SessionImpl Session::Session() { p = new SessionImpl(); } Session::Session(QString host) { p = new SessionImpl(); setHost(host); } Session::~Session() { if (p != nullptr) delete p; } void Session::setHost(QString host) { assert_ssh_session(p->session, "setHost() -- Session is not allocated."); ssh_options_set(p->session, SSH_OPTIONS_HOST, host.toStdString().c_str()); } QString Session::host() const { // This may not be necessary assert_ssh_session(p->session, "host() -- Session is not allocated."); char* host_name; int rv = ssh_options_get(p->session, SSH_OPTIONS_HOST, &host_name); if (rv == SSH_OK) { QString host = host_name; delete host_name; return host; } else { return QString(""); } } void Session::setLogVerbosity(SessionVerbosity level) { assert_ssh_session(p->session, "setLogVerbosity() -- Session is not allocated."); int verbosity = static_cast(level); ssh_options_set(p->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); } void Session::setPort(int port) { assert_ssh_session(p->session, "setPort() -- Session is not allocated."); ssh_options_set(p->session, SSH_OPTIONS_PORT, &port); } void Session::setUser(QString name) { assert_ssh_session(p->session, "setName() -- Session is not allocated."); ssh_options_set(p->session, SSH_OPTIONS_USER, name.toStdString().c_str()); } QString Session::user() const { // This may not be necessary assert_ssh_session(p->session, "user() -- Session is not allocated."); char* user_name; int rv = ssh_options_get(p->session, SSH_OPTIONS_USER, &user_name); if (rv == SSH_OK) { QString user = user_name; delete user_name; return user; } else { return QString(""); } } void Session::setProxyCommand(QString command) { assert_ssh_session(p->session, "setProxyCommand() -- Session is not allocated."); ssh_options_set(p->session, SSH_OPTIONS_PROXYCOMMAND, command.toStdString().c_str()); } bool Session::connect() { assert_ssh_session(p->session, "connect() -- Session is not allocated."); radix_tagged_line("connect()"); if (isConnected()) { radix_tagged_line("session already connected. disconnectinng."); ssh_disconnect(p->session); } { radix_tagged_line("Attempting connection."); // attempt a connection int rc = ssh_connect(p->session); return (rc == SSH_OK); } } bool Session::isConnected() const { assert_ssh_session(p->session, "isConnected() -- Session is not allocated."); return (ssh_is_connected(p->session) == 1); } QString Session::getError() const { assert_ssh_session(p->session, "getError() -- Session is not allocated."); return ssh_get_error(p->session); } bool Session::disconnect() { assert_ssh_session(p->session, "disconnect() -- Session is not allocated."); radix_tagged_line("disconnect()"); if (ssh_is_connected(p->session) != 0) { radix_tagged_line("Disconnecting session."); ssh_disconnect(p->session); // copy options in preparation for deletion ssh_session new_session = ssh_new(); ssh_options_copy(p->session, &new_session); ssh_free(p->session); p->session = new_session; } return true; } QString Session::getHexa() const { assert_ssh_session(p->session, "getHexa() -- Session is not allocated."); unsigned char* hash = nullptr; ssh_key server_public_key = nullptr; char* hexa; size_t hlen; QString qhexa; int rc = ssh_get_server_publickey(p->session, &server_public_key); if (rc < 0) { return QString(); } // Probably should remove hardwired HASH_SHA1 rc = ssh_get_publickey_hash(server_public_key, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen); ssh_key_free(server_public_key); if (rc < 0) { return QString(); } hexa = ssh_get_hexa(hash, hlen); qhexa = hexa; ssh_clean_pubkey_hash(&hash); ssh_string_free_char(hexa); return qhexa; } SessionHostState Session::verifyKnownHost() const { assert_ssh_session(p->session, "verifyKnownHost() -- Session is not allocated."); enum ssh_known_hosts_e state; unsigned char* hash = nullptr; ssh_key server_public_key = nullptr; size_t hlen; QString qhexa; radix_tagged_line("verifyKnownHost()"); int rc = ssh_get_server_publickey(p->session, &server_public_key); if (rc < 0) { return SessionHostState::ERROR; } rc = ssh_get_publickey_hash(server_public_key, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen); ssh_clean_pubkey_hash(&hash); ssh_key_free(server_public_key); if (rc < 0) { return SessionHostState::ERROR; } state = ssh_session_is_known_server(p->session); return static_cast(state); } bool Session::updateKnownHosts() { assert_ssh_session(p->session, "updateKnownHosts() -- Session is not allocated."); radix_tagged_line("updateKnownHosts()"); int rc = ssh_session_update_known_hosts(p->session); return (rc == SSH_OK); } int Session::authenticationMethodList() const { assert_ssh_session(p->session, "authenticationMethodList() -- Session is not allocated."); // required method to be called before auth list is called int rc = ssh_userauth_none(p->session, nullptr); if (rc == SSH_AUTH_ERROR) { return SSH_AUTH_METHOD_UNKNOWN; } // get acceptable methods int method = ssh_userauth_list(p->session, nullptr); return method; } SessionAuthState Session::authenticateWithPublicKey() { assert_ssh_session( p->session, "authenticateWithPublicKey() -- Session is not allocated."); int rc = ssh_userauth_publickey_auto(p->session, nullptr, nullptr); return static_cast(rc); } SessionAuthState Session::authenticateInteractively() { assert_ssh_session( p->session, "authenticateInteractively() -- Session is not allocated."); int rc = ssh_userauth_kbdint(p->session, nullptr, nullptr); return static_cast(rc); } QString Session::keyboardInteractiveName() const { assert_ssh_session(p->session, "keyboardInteractiveName() -- Session is not allocated."); QString name = ssh_userauth_kbdint_getname(p->session); return name; } QString Session::keyboardInteractiveInstruction() const { assert_ssh_session( p->session, "keyboardInteractiveInstruction() -- Session is not allocated."); QString instruction = ssh_userauth_kbdint_getinstruction(p->session); return instruction; } QStringList Session::keyboardInteractivePrompts() const { int num_prompts = ssh_userauth_kbdint_getnprompts(p->session); // build list of prompts QStringList prompts; char echo; for (int i = 0; i < num_prompts; ++i) { QString prompt = ssh_userauth_kbdint_getprompt( p->session, static_cast(i), &echo); radix_tagged_line(i << ". " << prompt.toStdString()); if (prompt.isEmpty()) break; prompts << prompt; } return prompts; } SessionAuthState Session::authenticateWithPassword(QString pswd) { assert_ssh_session(p->session, "authenticateWithPassword() -- Session is not allocated."); radix_tagged_line("Authenticate with password."); int rc = ssh_userauth_password(p->session, nullptr, pswd.toStdString().c_str()); return static_cast(rc); } QString Session::loginIssueBanner() const { char* banner = ssh_get_issue_banner(p->session); if (banner) { QString qbanner = banner; delete banner; return qbanner; } return QString(""); } SessionAuthState Session::authenticatePrompts(QStringList responses) { int err; radix_tagged_line("authenticatePrompts()"); for (int i = 0; i < responses.size(); ++i) { radix_tagged_line("Setting response " << i); const char* answer = responses.at(i).toStdString().c_str(); err = ssh_userauth_kbdint_setanswer(p->session, static_cast(i), answer); if (err < 0) { ssh_disconnect(p->session); return SessionAuthState::ERROR; } } // check status err = ssh_userauth_kbdint(p->session, nullptr, nullptr); SessionAuthState state = static_cast(err); if (state == SessionAuthState::DENIED) { ssh_disconnect(p->session); } return state; } Channel Session::newChannel() { assert_ssh_session(p->session, "newChannel() -- Session is not allocated."); Channel channel; channel.p = new ChannelImpl(p->session); return channel; } SFTPSession* Session::newSFTPSession() { assert_ssh_session(p->session, "newSFTPSession() -- SSH Session is not allocated."); SFTPSession* sftpSession = new SFTPSession; sftpSession->p = new SFTPSessionImpl(p->session); return sftpSession; } } // namespace rsm