diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 22d8b69a..f6729d88 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -72,7 +72,6 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h - ${CMAKE_CURRENT_LIST_DIR}/core/sshsession.h ) if(NOT IOS) @@ -96,7 +95,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp - ${CMAKE_CURRENT_LIST_DIR}/core/sshsession.cpp ) if(NOT IOS) diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 85f5cee5..4c22dc62 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -76,8 +76,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath); QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; e = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index eafc5279..3b84938e 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -41,14 +41,10 @@ ServerController::~ServerController() ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) { + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) { - std::shared_ptr session = m_sshClient.getSession(); - if (!session) { - return ErrorCode::SshInternalError; - } - auto error = session->initChannel(credentials); + auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { return error; } @@ -83,7 +79,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr qDebug().noquote() << "EXEC" << lineToExec; Debug::appendSshLog("Run command:" + lineToExec); - error = session->writeToChannel(lineToExec, cbReadStdOut, cbReadStdErr); + error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); if (error != ErrorCode::NoError) { return error; } @@ -96,8 +92,8 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; Debug::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + QStringLiteral(":\n") + script); @@ -126,8 +122,9 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, if (e) return e; QString stdOut; - auto cbReadStd = [&](const QString &data, libssh::Session &) { + auto cbReadStd = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; // mkdir @@ -188,8 +185,9 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data; + return ErrorCode::NoError; }; *errorCode = runScript(credentials, script, cbReadStdOut); @@ -220,11 +218,7 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { - std::shared_ptr session = m_sshClient.getSession(); - if (!session) { - return ErrorCode::SshInternalError; - } - auto error = session->initSftp(credentials); + auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { return error; } @@ -236,7 +230,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential qDebug() << "remotePath" << remotePath; - error = session->sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); + error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); if (error != ErrorCode::NoError) { return error; } @@ -367,15 +361,17 @@ bool ServerController::isReinstallContainerRequred(DockerContainer container, co ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container) { QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &session) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &client) { stdOut += data + "\n"; if (data.contains("Automatically restart Docker daemon?")) { - session.writeToChannel("yes"); + return client.writeResponse("yes"); } + return ErrorCode::NoError; }; - auto cbReadStdErr = [&](const QString &data, libssh::Session &) { + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; ErrorCode e = runScript(credentials, @@ -404,8 +400,9 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden if (e) return e; QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { // stdOut += data + "\n"; @@ -422,8 +419,9 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) { QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { // stdOut += data + "\n"; @@ -448,11 +446,13 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) { QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; - auto cbReadStdErr = [&](const QString &data, libssh::Session &) { + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; @@ -584,11 +584,13 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode) { QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Session &) { + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; - auto cbReadStdErr = [&](const QString &data, libssh::Session &) { + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; + return ErrorCode::NoError; }; ErrorCode e = runScript(credentials, @@ -601,8 +603,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential void ServerController::disconnectFromHost(const ServerCredentials &credentials) { -// SshConnection *client = acquireConnection(sshParams(credentials)); -// if (client) client->disconnectFromHost(); + m_sshClient.disconnectFromHost(); } ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 8b5f7c8d..74e5f931 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -59,12 +59,12 @@ public: QString replaceVars(const QString &script, const Vars &vars); ErrorCode runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index c746eb29..a1a55538 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -1,5 +1,14 @@ #include "sshclient.h" +#include +#include + +#include + +#ifdef Q_OS_WINDOWS +#define S_IRWXU 0 +#endif + namespace libssh { Client::Client(QObject *parent) : QObject(parent) { @@ -11,8 +20,304 @@ namespace libssh { ssh_finalize(); } - std::shared_ptr Client::getSession() + ErrorCode Client::connectToHost(const ServerCredentials &credentials) { - return std::make_shared(); +// if (is_ssh_initialized()) { +// qDebug() << "Failed to initialize ssh"; +// return ErrorCode::InternalError; +// } + if (m_session != NULL) { + m_session = ssh_new(); + + if (m_session == NULL) { + qDebug() << "Failed to create ssh session"; + return ErrorCode::InternalError; + } + + int port = credentials.port; + int logVerbosity = SSH_LOG_NOLOG; + std::string hostIp = credentials.hostName.toStdString(); + std::string hostUsername = credentials.userName.toStdString() + "@" + hostIp; + + ssh_options_set(m_session, SSH_OPTIONS_HOST, hostIp.c_str()); + ssh_options_set(m_session, SSH_OPTIONS_PORT, &port); + ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); + ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); + + int connectionResult = ssh_connect(m_session); + + if (connectionResult != SSH_OK) { + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + std::string authUsername = credentials.userName.toStdString(); + + int authResult = SSH_ERROR; + if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { + ssh_key privateKey; + ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, nullptr, nullptr, &privateKey); + authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey); + } + else { + authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); + } + + if (authResult != SSH_OK) { + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + return ErrorCode::NoError; + } + + void Client::disconnectFromHost() + { + if (m_session != NULL) { + if (ssh_is_connected(m_session)) { + ssh_disconnect(m_session); + } + ssh_free(m_session); + } + } + + ErrorCode Client::executeCommand(const QString &data, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) + { + m_channel = ssh_channel_new(m_session); + + if (m_channel == NULL) { + return closeChannel(); + } + + int result = ssh_channel_open_session(m_channel); + + if (result == SSH_OK && ssh_channel_is_open(m_channel)) { + qDebug() << "SSH chanel opened"; + } else { + return closeChannel(); + } + + QFutureWatcher watcher; + connect(&watcher, &QFutureWatcher::finished, this, &Client::writeToChannelFinished); + + QFuture future = QtConcurrent::run([this, &data, &cbReadStdOut, &cbReadStdErr]() { + const size_t bufferSize = 2048; + + int bytesRead = 0; + char buffer[bufferSize]; + + int result = ssh_channel_request_exec(m_channel, data.toUtf8()); + if (result == SSH_OK) { + std::string output; + auto readOutput = [&](bool isStdErr) { + bytesRead = ssh_channel_read(m_channel, buffer, sizeof(buffer), isStdErr); + while (bytesRead > 0) + { + output = std::string(buffer, bytesRead); + if (!output.empty()) { + qDebug().noquote() << (isStdErr ? "stdErr" : "stdOut") << QString(output.c_str()); + + if (cbReadStdOut && !isStdErr){ + auto error = cbReadStdOut(output.c_str(), *this); + if (error != ErrorCode::NoError) { + return error; + } + } + if (cbReadStdErr && isStdErr){ + auto error = cbReadStdErr(output.c_str(), *this); + if (error != ErrorCode::NoError) { + return error; + } + } + } + bytesRead = ssh_channel_read(m_channel, buffer, sizeof(buffer), isStdErr); + } + return ErrorCode::NoError; + }; + + auto error = readOutput(false); + if (error != ErrorCode::NoError) { + return error; + } + error = readOutput(true); + if (error != ErrorCode::NoError) { + return error; + } + } else { + return closeChannel(); + } + return closeChannel(); + }); + watcher.setFuture(future); + + QEventLoop wait; + QObject::connect(this, &Client::writeToChannelFinished, &wait, &QEventLoop::quit); + wait.exec(); + + return watcher.result(); + } + + ErrorCode Client::writeResponse(const QString &data) + { + if (m_channel == NULL) { + qDebug() << "ssh channel not initialized"; + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size()); + if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) { + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + ErrorCode Client::closeChannel() + { + if (m_channel != NULL) { + if (ssh_channel_is_eof(m_channel)) { + ssh_channel_send_eof(m_channel); + } + if (ssh_channel_is_open(m_channel)) { + ssh_channel_close(m_channel); + } + ssh_channel_free(m_channel); + } + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } + + ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) + { + m_sftpSession = sftp_new(m_session); + + if (m_sftpSession == NULL) { + return closeSftpSession(); + } + + int result = sftp_init(m_sftpSession); + + if (result != SSH_OK) { + return closeSftpSession(); + } + + QFutureWatcher watcher; + connect(&watcher, &QFutureWatcher::finished, this, &Client::sftpFileCopyFinished); + + QFuture future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() { + int accessType = O_WRONLY | O_CREAT | overwriteMode; + sftp_file file; + const size_t bufferSize = 16384; + char buffer[bufferSize]; + + file = sftp_open(m_sftpSession, remotePath.c_str(), accessType, S_IRWXU); + + if (file == NULL) { + return closeSftpSession(); + } + + int localFileSize = std::filesystem::file_size(localPath); + int chunksCount = localFileSize / (bufferSize); + + std::ifstream fin(localPath, std::ios::binary | std::ios::in); + + if (fin.is_open()) { + for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) { + fin.read(buffer, bufferSize); + + int bytesWritten = sftp_write(file, buffer, bufferSize); + + std::string chunk(buffer, bufferSize); + qDebug() << "sftp write: " << QString(chunk.c_str()); + + if (bytesWritten != bufferSize) { + fin.close(); + sftp_close(file); + return closeSftpSession(); + } + } + + int lastChunkSize = localFileSize % (bufferSize); + + if (lastChunkSize != 0) { + fin.read(buffer, lastChunkSize); + + std::string chunk(buffer, lastChunkSize); + qDebug() << "sftp write: " << QString(chunk.c_str()); + + int bytesWritten = sftp_write(file, buffer, lastChunkSize); + + if (bytesWritten != lastChunkSize) { + fin.close(); + sftp_close(file); + return closeSftpSession(); + } + } + } else { + sftp_close(file); + return closeSftpSession(); + } + + fin.close(); + + int result = sftp_close(file); + if (result != SSH_OK) { + return closeSftpSession(); + } + + return closeSftpSession(); + }); + watcher.setFuture(future); + + QEventLoop wait; + QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit); + wait.exec(); + + return watcher.result(); + } + + ErrorCode Client::closeSftpSession() + { + auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); + if (m_sftpSession != NULL) { + sftp_free(m_sftpSession); + } + qDebug() << ssh_get_error(m_session); + return errorCode; + } + + ErrorCode Client::fromLibsshErrorCode(int errorCode) + { + switch (errorCode) { + case(SSH_NO_ERROR): return ErrorCode::NoError; + case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError; + case(SSH_EINTR): return ErrorCode::SshInterruptedError; + case(SSH_FATAL): return ErrorCode::SshInternalError; + default: return ErrorCode::SshInternalError; + } + } + ErrorCode Client::fromLibsshSftpErrorCode(int errorCode) + { + switch (errorCode) { + case(SSH_FX_OK): return ErrorCode::NoError; + case(SSH_FX_EOF): return ErrorCode::SshSftpEofError; + case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError; + case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError; + case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError; + case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError; + case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError; + case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError; + case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError; + case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError; + case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError; + case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError; + case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError; + case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError; + default: return ErrorCode::SshSftpFailureError; + } } } diff --git a/client/core/sshclient.h b/client/core/sshclient.h index 548f4312..a54adab8 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -3,11 +3,22 @@ #include -#include "sshsession.h" +#include + +#include +#include + +#include "defs.h" using namespace amnezia; namespace libssh { + enum SftpOverwriteMode { + /*! Overwrite any existing files */ + SftpOverwriteExisting = O_TRUNC, + /*! Append new content if the file already exists */ + SftpAppendToExisting = O_APPEND + }; class Client : public QObject { Q_OBJECT @@ -15,7 +26,28 @@ namespace libssh { Client(QObject *parent = nullptr); ~Client(); - std::shared_ptr getSession(); + ErrorCode connectToHost(const ServerCredentials &credentials); + void disconnectFromHost(); + ErrorCode executeCommand(const QString &data, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr); + ErrorCode writeResponse(const QString &data); + ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode, + const std::string& localPath, + const std::string& remotePath, + const std::string& fileDesc); + private: + ErrorCode closeChannel(); + ErrorCode closeSftpSession(); + ErrorCode fromLibsshErrorCode(int errorCode); + ErrorCode fromLibsshSftpErrorCode(int errorCode); + + ssh_session m_session; + ssh_channel m_channel; + sftp_session m_sftpSession; + signals: + void writeToChannelFinished(); + void sftpFileCopyFinished(); }; } diff --git a/client/core/sshsession.cpp b/client/core/sshsession.cpp deleted file mode 100644 index ba1fa6a5..00000000 --- a/client/core/sshsession.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include "sshsession.h" - -#include -#include - -#include - -#ifdef Q_OS_WINDOWS -#define S_IRWXU 0 -#endif - -namespace libssh { - Session::Session(QObject *parent) : QObject(parent) - { - - } - - Session::~Session() - { - if (m_isSftpInitialized) { - sftp_free(m_sftpSession); - } - if (m_isSessionConnected) { - ssh_disconnect(m_session); - } - ssh_free(m_session); - } - - ErrorCode Session::connectToHost(const ServerCredentials &credentials) - { - if (m_session == NULL) { - return ErrorCode::InternalError; - } - - int port = credentials.port; - int logVerbosity = SSH_LOG_NOLOG; - std::string hostIp = credentials.hostName.toStdString(); - std::string hostUsername = credentials.userName.toStdString() + "@" + hostIp; - - ssh_options_set(m_session, SSH_OPTIONS_HOST, hostIp.c_str()); - ssh_options_set(m_session, SSH_OPTIONS_PORT, &port); - ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); - ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); - - int connectionResult = ssh_connect(m_session); - - if (connectionResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - m_isSessionConnected = true; - - std::string authUsername = credentials.userName.toStdString(); - - int authResult = SSH_ERROR; - if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { - ssh_key privateKey; - ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, nullptr, nullptr, &privateKey); - authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey); - } - else { - authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); - } - - if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - ErrorCode Session::initChannel(const ServerCredentials &credentials) - { - m_session = ssh_new(); - - ErrorCode error = connectToHost(credentials); - if (error) { - return error; - } - - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - ErrorCode Session::writeToChannel(const QString &data, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) - { - m_channel = ssh_channel_new(m_session); - - if (m_channel == NULL) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - int result = ssh_channel_open_session(m_channel); - - if (result == SSH_OK && ssh_channel_is_open(m_channel)) { - qDebug() << "SSH chanel opened"; - } else { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - QFutureWatcher watcher; - connect(&watcher, &QFutureWatcher::finished, this, &Session::writeToChannelFinished); - - QFuture future = QtConcurrent::run([this, &data, &cbReadStdOut, &cbReadStdErr]() { - const size_t bufferSize = 2048; - - int bytesRead = 0; - char buffer[bufferSize]; - - int bytesWritten = ssh_channel_request_exec(m_channel, data.toUtf8()); - if (bytesWritten == SSH_OK) { - auto readOutput = [&](bool isStdErr) { - std::string output; - bytesRead = ssh_channel_read(m_channel, buffer, sizeof(buffer), isStdErr); - while (bytesRead > 0) - { - output = std::string(buffer, bytesRead); - if (!output.empty()) { - qDebug().noquote() << (isStdErr ? "stdErr" : "stdOut") << QString(output.c_str()); - - if (cbReadStdOut && !isStdErr){ - cbReadStdOut(output.c_str(), *this); - } - if (cbReadStdErr && isStdErr){ - cbReadStdErr(output.c_str(), *this); - } - } - bytesRead = ssh_channel_read(m_channel, buffer, sizeof(buffer), isStdErr); - } - return output; - }; - - readOutput(false); - readOutput(true); - } else { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - - ssh_channel_send_eof(m_channel); - ssh_channel_close(m_channel); - ssh_channel_free(m_channel); - }); - watcher.setFuture(future); - - QEventLoop wait; - QObject::connect(this, &Session::writeToChannelFinished, &wait, &QEventLoop::quit); - wait.exec(); - - return watcher.result(); - } - - ErrorCode Session::writeToChannel(const QString &data) - { - if (m_channel == NULL) { - qDebug() << "ssh channel not initialized"; - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size()); - if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) { - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - ErrorCode Session::initSftp(const ServerCredentials &credentials) - { - m_session = ssh_new(); - - ErrorCode error = connectToHost(credentials); - if (error) { - return error; - } - - m_sftpSession = sftp_new(m_session); - - if (m_sftpSession == NULL) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - int result = sftp_init(m_sftpSession); - - if (result != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - - return ErrorCode::NoError; - } - - ErrorCode Session::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) - { - if (m_sftpSession == NULL) { - qDebug() << "ssh sftp session not initialized"; - return ErrorCode::SshInternalError; - } - - QFutureWatcher watcher; - connect(&watcher, &QFutureWatcher::finished, this, &Session::sftpFileCopyFinished); - - QFuture future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() { - int accessType = O_WRONLY | O_CREAT | overwriteMode; - sftp_file file; - const size_t bufferSize = 16384; - char buffer[bufferSize]; - - file = sftp_open(m_sftpSession, remotePath.c_str(), accessType, S_IRWXU); - - if (file == NULL) { - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - - int localFileSize = std::filesystem::file_size(localPath); - int chunksCount = localFileSize / (bufferSize); - - std::ifstream fin(localPath, std::ios::binary | std::ios::in); - - if (fin.is_open()) { - for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) { - fin.read(buffer, bufferSize); - - int bytesWritten = sftp_write(file, buffer, bufferSize); - - std::string chunk(buffer, bufferSize); - qDebug() << "sftp write: " << QString(chunk.c_str()); - - if (bytesWritten != bufferSize) { - fin.close(); - sftp_close(file); - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - } - - int lastChunkSize = localFileSize % (bufferSize); - - if (lastChunkSize != 0) { - fin.read(buffer, lastChunkSize); - - std::string chunk(buffer, lastChunkSize); - qDebug() << "sftp write: " << QString(chunk.c_str()); - - int bytesWritten = sftp_write(file, buffer, lastChunkSize); - - if (bytesWritten != lastChunkSize) { - fin.close(); - sftp_close(file); - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - } - } else { - sftp_close(file); - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - - fin.close(); - - int result = sftp_close(file); - if (result != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); - } - - return ErrorCode::NoError; - }); - watcher.setFuture(future); - - QEventLoop wait; - QObject::connect(this, &Session::sftpFileCopyFinished, &wait, &QEventLoop::quit); - wait.exec(); - - return watcher.result(); - } - - ErrorCode Session::fromLibsshErrorCode(int errorCode) - { - switch (errorCode) { - case(SSH_NO_ERROR): return ErrorCode::NoError; - case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError; - case(SSH_EINTR): return ErrorCode::SshInterruptedError; - case(SSH_FATAL): return ErrorCode::SshInternalError; - default: return ErrorCode::SshInternalError; - } - } - ErrorCode Session::fromLibsshSftpErrorCode(int errorCode) - { - switch (errorCode) { - case(SSH_FX_OK): return ErrorCode::NoError; - case(SSH_FX_EOF): return ErrorCode::SshSftpEofError; - case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError; - case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError; - case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError; - case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError; - case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError; - case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError; - case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError; - case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError; - case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError; - case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError; - case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError; - case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError; - default: return ErrorCode::SshSftpFailureError; - } - } -} diff --git a/client/core/sshsession.h b/client/core/sshsession.h deleted file mode 100644 index b3e1199c..00000000 --- a/client/core/sshsession.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef SSHSESSION_H -#define SSHSESSION_H - -#include - -#include - -#include -#include - -#include "defs.h" - -using namespace amnezia; - -namespace libssh { - enum SftpOverwriteMode { - /*! Overwrite any existing files */ - SftpOverwriteExisting = O_TRUNC, - /*! Append new content if the file already exists */ - SftpAppendToExisting = O_APPEND - }; - - class Session : public QObject - { - Q_OBJECT - public: - Session(QObject *parent = nullptr); - ~Session(); - - ErrorCode initChannel(const ServerCredentials &credentials); - ErrorCode initSftp(const ServerCredentials &credentials); - ErrorCode writeToChannel(const QString &data, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr); - ErrorCode writeToChannel(const QString &data); - ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc); - private: - ErrorCode connectToHost(const ServerCredentials &credentials); - ErrorCode fromLibsshErrorCode(int errorCode); - ErrorCode fromLibsshSftpErrorCode(int errorCode); - - ssh_session m_session; - ssh_channel m_channel; - sftp_session m_sftpSession; - - bool m_isChannelCreated = false; - bool m_isChannelOpened = false; - bool m_isSessionConnected = false; - bool m_isNeedSendChannelEof = false; - bool m_isSftpInitialized = false; - signals: - void writeToChannelFinished(); - void sftpFileCopyFinished(); - }; -} - -#endif // SSHSESSION_H