Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/qt6-client-management-panel

This commit is contained in:
vladimir.kuznetsov 2023-04-08 19:03:10 +03:00
commit ada8912a1f
208 changed files with 1860 additions and 1503607 deletions

View file

@ -34,14 +34,15 @@ enum ErrorCode
ServerCancelInstallation,
// Ssh connection errors
SshSocketError, SshTimeoutError, SshProtocolError,
SshHostKeyError, SshKeyFileError, SshAuthenticationError,
SshClosedByServerError, SshInternalError,
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
SshPrivateKeyError, SshPrivateKeyFormatError,
// Ssh remote process errors
SshRemoteProcessCreationError,
FailedToStartRemoteProcessError, RemoteProcessCrashError,
SshSftpError,
// Ssh sftp errors
SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError,
SshSftpFailureError, SshSftpBadMessageError, SshSftpNoConnectionError,
SshSftpConnectionLostError, SshSftpOpUnsupportedError, SshSftpInvalidHandleError,
SshSftpNoSuchPathError, SshSftpFileAlreadyExistsError, SshSftpWriteProtectError,
SshSftpNoMediaError,
// Local errors
FailedToSaveConfigData,

View file

@ -17,20 +17,27 @@ QString errorString(ErrorCode code){
case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed");
case(ServerCancelInstallation): return QObject::tr("Installation canceled by user");
// Ssh connection errors
case(SshSocketError): return QObject::tr("Ssh connection error");
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
case(SshProtocolError): return QObject::tr("Ssh protocol error");
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
case(SshKeyFileError): return QObject::tr("Ssh key file error");
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
// Libssh errors
case(SshRequsetDeniedError): return QObject::tr("Ssh request was denied");
case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
case(SshInternalError): return QObject::tr("Ssh internal error");
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types");
// Ssh remote process errors
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
// Libssh sftp errors
case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered");
case(SshSftpNoSuchFileError): return QObject::tr("Sftp error: File does not exist");
case(SshSftpPermissionDeniedError): return QObject::tr("Sftp error: Permission denied");
case(SshSftpFailureError): return QObject::tr("Sftp error: Generic failure");
case(SshSftpBadMessageError): return QObject::tr("Sftp error: Garbage received from server");
case(SshSftpNoConnectionError): return QObject::tr("Sftp error: No connection has been set up");
case(SshSftpConnectionLostError): return QObject::tr("Sftp error: There was a connection, but we lost it");
case(SshSftpOpUnsupportedError): return QObject::tr("Sftp error: Operation not supported by libssh yet");
case(SshSftpInvalidHandleError): return QObject::tr("Sftp error: Invalid file handle");
case(SshSftpNoSuchPathError): return QObject::tr("Sftp error: No such file or directory path exists");
case(SshSftpFileAlreadyExistsError): return QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made");
case(SshSftpWriteProtectError): return QObject::tr("Sftp error: Write-protected filesystem");
case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");

View file

@ -15,10 +15,16 @@
#include <QThread>
#include <QtConcurrent>
#include "sftpchannel.h"
#include "sshconnectionmanager.h"
#include <filesystem>
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <chrono>
#include <thread>
#include "containers/containers_defs.h"
#include "logger.h"
#include "server_defs.h"
#include "settings.h"
#include "scripts_registry.h"
@ -26,26 +32,24 @@
#include <configurators/vpn_configurator.h>
using namespace QSsh;
ServerController::ServerController(std::shared_ptr<Settings> settings, QObject *parent) :
m_settings(settings)
{
}
ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
ServerController::~ServerController()
{
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() == SshConnection::State::Connecting) {
qDebug() << "ServerController::runScript aborted, connectToHost in progress";
return ErrorCode::SshTimeoutError;
}
else if (client->state() != SshConnection::State::Connected) {
qDebug() << "ServerController::runScript connectToHost error: " << fromSshConnectionErrorCode(client->errorState());
return fromSshConnectionErrorCode(client->errorState());
m_sshClient.disconnectFromHost();
}
ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr) {
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
return error;
}
script.replace("\r", "");
@ -56,79 +60,32 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
QString currentLine = lines.at(i);
QString nextLine;
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
if (totalLine.isEmpty()) {
totalLine = currentLine;
}
else {
} else {
totalLine = totalLine + "\n" + currentLine;
}
QString lineToExec;
if (currentLine.endsWith("\\")) {
continue;
}
else {
} else {
lineToExec = totalLine;
totalLine.clear();
}
// Run collected line
if (totalLine.startsWith("#")) {
if (lineToExec.startsWith("#")) {
continue;
}
qDebug().noquote() << "EXEC" << lineToExec;
Logger::appendSshLog("Run command:" + lineToExec);
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
}
QEventLoop wait;
int exitStatus = -1;
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
// qDebug() << "Command started";
// });
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
exitStatus = status;
//qDebug() << "Remote process exited with status" << status;
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, &wait, [proc, cbReadStdOut](){
QString s = proc->readAllStandardOutput();
if (s != "." && !s.isEmpty()) {
Logger::appendSshLog("Output: " + s);
qDebug().noquote() << "stdout" << s;
}
if (cbReadStdOut) cbReadStdOut(s, proc);
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
QString s = proc->readAllStandardError();
if (s != "." && !s.isEmpty()) {
Logger::appendSshLog("Output: " + s);
qDebug().noquote() << "stderr" << s;
}
if (cbReadStdErr) cbReadStdErr(s, proc);
});
proc->start();
if (i < lines.count() && exitStatus < 0) {
wait.exec();
}
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
return fromSshProcessExitStatus(exitStatus);
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
if (error != ErrorCode::NoError) {
return error;
}
}
@ -138,8 +95,8 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials,
DockerContainer container, QString script,
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut,
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr)
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr)
{
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
@ -153,14 +110,14 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
runScript(credentials,
replaceVars(remover, genVarsForScript(credentials, container)));
replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
return e;
}
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &file, const QString &path,
QSsh::SftpOverwriteMode overwriteMode)
libssh::SftpOverwriteMode overwriteMode)
{
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
@ -168,8 +125,9 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) return e;
QString stdOut;
auto cbReadStd = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
auto cbReadStd = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
// mkdir
@ -181,14 +139,14 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) return e;
if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) {
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
if (e) return e;
}
else if (overwriteMode == QSsh::SftpOverwriteMode::SftpAppendToExisting) {
else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
@ -222,101 +180,35 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
{
if (errorCode) *errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").
arg(ContainerProps::containerToString(container)).arg(path);
qDebug().noquote() << "Copy file from container\n" << script;
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState());
return {};
}
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
if (errorCode) *errorCode = ErrorCode::SshRemoteProcessCreationError;
return {};
}
QEventLoop wait;
int exitStatus = 0;
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
exitStatus = status;
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [&](){
qDebug() << "ServerController::getTextFileFromContainer proc started";
exitStatus = -1;
});
proc->start();
wait.exec();
// if (exitStatus < 0) {
// wait.exec();
// }
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus);
}
if (errorCode) *errorCode = ErrorCode::NoError;
return QByteArray::fromHex(proc->readAllStandardOutput());
}
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
{
QString caCert = ServerController::getTextFileFromContainer(container,
credentials, protocols::openvpn::caCertPath);
QString taKey = ServerController::getTextFileFromContainer(container,
credentials, protocols::openvpn::taKeyPath);
if (!caCert.isEmpty() && !taKey.isEmpty()) {
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
}
else {
return ErrorCode::ServerCheckFailed;
}
};
*errorCode = runScript(credentials, script, cbReadStdOut);
qDebug().noquote() << "Copy file from container stdout : \n" << stdOut;
qDebug().noquote() << "Copy file from container END : \n" ;
return QByteArray::fromHex(stdOut.toUtf8());
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
QSsh::SftpOverwriteMode overwriteMode)
libssh::SftpOverwriteMode overwriteMode)
{
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
}
bool err = false;
QEventLoop wait;
QTimer timer;
timer.setSingleShot(true);
timer.start(3000);
QSharedPointer<SftpChannel> sftp = client->createSftpChannel();
sftp->initialize();
QObject::connect(sftp.data(), &SftpChannel::initialized, &wait, [&](){
timer.stop();
wait.quit();
});
QObject::connect(&timer, &QTimer::timeout, &wait, [&](){
err= true;
wait.quit();
});
wait.exec();
if (!sftp) {
qCritical() << "Failed to create SftpChannel, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
return error;
}
QTemporaryFile localFile;
@ -324,76 +216,13 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data);
localFile.close();
auto job = sftp->uploadFile(localFile.fileName(), remotePath, QSsh::SftpOverwriteMode::SftpOverwriteExisting);
QObject::connect(sftp.data(), &SftpChannel::finished, &wait, [&](QSsh::SftpJobId j, const SftpError errorType = SftpError::NoError, const QString &error = QString()){
if (job == j) {
qDebug() << "Sftp finished with status" << error;
wait.quit();
}
});
qDebug() << "remotePath" << remotePath;
QObject::connect(sftp.data(), &SftpChannel::channelError, &wait, [&](const QString &reason){
qDebug() << "Sftp finished with error" << reason;
err= true;
wait.quit();
});
wait.exec();
if (err) {
return ErrorCode::SshSftpError;
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc");
if (error != ErrorCode::NoError) {
return error;
}
else {
return ErrorCode::NoError;
}
}
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
{
switch (error) {
case(SshNoError): return ErrorCode::NoError;
case(QSsh::SshSocketError): return ErrorCode::SshSocketError;
case(QSsh::SshTimeoutError): return ErrorCode::SshTimeoutError;
case(QSsh::SshProtocolError): return ErrorCode::SshProtocolError;
case(QSsh::SshHostKeyError): return ErrorCode::SshHostKeyError;
case(QSsh::SshKeyFileError): return ErrorCode::SshKeyFileError;
case(QSsh::SshAuthenticationError): return ErrorCode::SshAuthenticationError;
case(QSsh::SshClosedByServerError): return ErrorCode::SshClosedByServerError;
case(QSsh::SshInternalError): return ErrorCode::SshInternalError;
default: return ErrorCode::SshInternalError;
}
}
ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
{
qDebug() << exitStatus;
switch (SshRemoteProcess::ExitStatus(exitStatus)) {
case(SshRemoteProcess::ExitStatus::NormalExit): return ErrorCode::NoError;
case(SshRemoteProcess::ExitStatus::FailedToStart): return ErrorCode::FailedToStartRemoteProcessError;
case(SshRemoteProcess::ExitStatus::CrashExit): return ErrorCode::RemoteProcessCrashError;
default: return ErrorCode::SshInternalError;
}
}
SshConnectionParameters ServerController::sshParams(const ServerCredentials &credentials)
{
QSsh::SshConnectionParameters sshParams;
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
sshParams.privateKeyFile = credentials.password;
}
else {
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
sshParams.setPassword(credentials.password);
}
sshParams.setHost(credentials.hostName);
sshParams.setUserName(credentials.userName);
sshParams.timeout = 10;
sshParams.setPort(credentials.port);
sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone;
sshParams.options = SshIgnoreDefaultProxy;
return sshParams;
return ErrorCode::NoError;
}
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
@ -432,6 +261,7 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
removeContainer(credentials, container);
qDebug().noquote() << "ServerController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
e = buildContainerWorker(credentials, container, config);
if (e) return e;
qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished";
@ -525,15 +355,17 @@ bool ServerController::isReinstallContainerRequred(DockerContainer container, co
ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &client) {
stdOut += data + "\n";
if (data.contains("Automatically restart Docker daemon?")) {
proc->write("yes\n");
return client.writeResponse("yes");
}
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QFutureWatcher<ErrorCode> watcher;
@ -591,33 +423,42 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
if (e) return e;
// QString stdOut;
// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
return runScript(credentials,
e = runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
genVarsForScript(credentials, container, config)));
genVarsForScript(credentials, container, config)), cbReadStdOut);
if (e) return e;
return e;
}
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
genVarsForScript(credentials, container, config)), cbReadStdOut);
qDebug() << "cbReadStdOut: " << stdOut;
if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError;
if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError;
@ -629,11 +470,13 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
@ -765,11 +608,13 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode e = runScript(credentials,
@ -780,71 +625,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
return stdOut;
}
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
{
SshConnection *client = acquireConnection(sshParams);
if (!client) return nullptr;
QEventLoop waitssh;
QObject::connect(client, &SshConnection::connected, &waitssh, [&]() {
qDebug() << "Server connected by ssh";
waitssh.quit();
});
QObject::connect(client, &SshConnection::disconnected, &waitssh, [&]() {
qDebug() << "Server disconnected by ssh";
waitssh.quit();
});
QObject::connect(client, &SshConnection::error, &waitssh, [&](QSsh::SshError error) {
qCritical() << "Ssh error:" << error << client->errorString();
waitssh.quit();
});
// QObject::connect(client, &SshConnection::dataAvailable, [&](const QString &message) {
// qCritical() << "Ssh message:" << message;
// });
//qDebug() << "Connection state" << client->state();
if (client->state() == SshConnection::State::Unconnected) {
client->connectToHost();
waitssh.exec();
}
// QObject::connect(&client, &SshClient::sshDataReceived, [&](){
// qDebug().noquote() << "Data received";
// });
// if(client.sshState() != SshClient::SshState::Ready) {
// qCritical() << "Can't connect to server";
// return false;
// }
// else {
// qDebug() << "SSh connection established";
// }
// QObject::connect(proc, &SshProcess::finished, &wait, &QEventLoop::quit);
// QObject::connect(proc, &SshProcess::failed, &wait, &QEventLoop::quit);
return client;
}
void ServerController::setCancelInstallation(const bool cancel)
{
m_cancelInstallation = cancel;
}
void ServerController::disconnectFromHost(const ServerCredentials &credentials)
{
SshConnection *client = acquireConnection(sshParams(credentials));
if (client) client->disconnectFromHost();
}
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
return runScript(credentials,
@ -865,29 +650,41 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
if (container == DockerContainer::Dns) {
return ErrorCode::NoError;
}
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString containerString = ProtocolProps::protoToString(ContainerProps::defaultProtocol(container));
const Proto protocol = ContainerProps::defaultProtocol(container);
const QString containerString = ProtocolProps::protoToString(protocol);
const QJsonObject containerConfig = config.value(containerString).toObject();
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
QString port = containerConfig.value(config_key::port).toString();
QString transportProto = containerConfig.value(config_key::transport_proto).toString();
QString defaultPort("%1");
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
QString script = QString("sudo lsof -i -P -n | grep -E ':%1").arg(port);
QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port);
for (auto &port : fixedPorts) {
script = script.append("|:%1").arg(port);
}
script = script.append("' | grep -i %1").arg(transportProto);
runScript(credentials,
ErrorCode errorCode = runScript(credentials,
replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (!stdOut.isEmpty()) {
return ErrorCode::ServerPortAlreadyAllocatedError;
@ -898,11 +695,13 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
@ -938,3 +737,9 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
return ErrorCode::NoError;
}
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &callback)
{
auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback);
return error;
}

View file

@ -3,14 +3,10 @@
#include <QJsonObject>
#include <QObject>
#include "sshconnection.h"
#include "sshremoteprocess.h"
#include "logger.h"
#include "defs.h"
#include "containers/containers_defs.h"
#include "sftpdefs.h"
#include "sshclient.h"
class Settings;
class VpnConfigurator;
@ -22,75 +18,64 @@ class ServerController : public QObject
Q_OBJECT
public:
ServerController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
~ServerController();
typedef QList<QPair<QString, QString>> Vars;
ErrorCode fromSshConnectionErrorCode(QSsh::SshError error);
// QSsh exitCode and exitStatus are different things
ErrorCode fromSshProcessExitStatus(int exitStatus);
QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
void disconnectFromHost(const ServerCredentials &credentials);
ErrorCode removeAllContainers(const ServerCredentials &credentials);
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container,
QJsonObject &config, bool isUpdate = false);
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, QJsonObject &newConfig);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
// create initial config - generate passwords, etc
QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, QSsh::SftpOverwriteMode overwriteMode = QSsh::SftpOverwriteMode::SftpOverwriteExisting);
ErrorCode uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &file, const QString &path,
QSsh::SftpOverwriteMode overwriteMode = QSsh::SftpOverwriteMode::SftpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode = nullptr);
QString replaceVars(const QString &script, const Vars &vars);
ErrorCode runScript(const ServerCredentials &credentials, QString script,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject());
ErrorCode runScript(const ServerCredentials &credentials, QString script,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
void setCancelInstallation(const bool cancel);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &callback);
private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
bool m_cancelInstallation = false;
libssh::Client m_sshClient;
signals:
void serverIsBusy(const bool isBusy);
};

378
client/core/sshclient.cpp Normal file
View file

@ -0,0 +1,378 @@
#include "sshclient.h"
#include <QEventLoop>
#include <QtConcurrent>
#include <fstream>
#ifdef Q_OS_WINDOWS
#define S_IRWXU 0
#endif
namespace libssh {
std::function<QString()> Client::m_passphraseCallback;
Client::Client(QObject *parent) : QObject(parent)
{ }
Client::~Client()
{ }
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
{
auto passphrase = m_passphraseCallback();
passphrase.toStdString().copy(buf, passphrase.size() + 1);
return 0;
}
ErrorCode Client::connectToHost(const ServerCredentials &credentials)
{
if (m_session == nullptr) {
m_session = ssh_new();
if (m_session == nullptr) {
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 = nullptr;
ssh_key publicKey = nullptr;
authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
if (authResult == SSH_OK) {
authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey);
}
if (authResult == SSH_OK) {
authResult = ssh_userauth_try_publickey(m_session, authUsername.c_str(), publicKey);
}
if (authResult == SSH_OK) {
authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey);
}
if (publicKey) {
ssh_key_free(publicKey);
}
if (privateKey) {
ssh_key_free(privateKey);
}
if (authResult != SSH_OK) {
qDebug() << ssh_get_error(m_session);
ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session));
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::SshPrivateKeyFormatError;
}
return errorCode;
}
} 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 ErrorCode::NoError;
}
void Client::disconnectFromHost()
{
if (m_session != nullptr) {
if (ssh_is_connected(m_session)) {
ssh_disconnect(m_session);
}
ssh_free(m_session);
m_session = nullptr;
}
}
ErrorCode Client::executeCommand(const QString &data,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr)
{
m_channel = ssh_channel_new(m_session);
if (m_channel == nullptr) {
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<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::writeToChannelFinished);
QFuture<ErrorCode> 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 == nullptr) {
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 != nullptr) {
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);
m_channel = nullptr;
}
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 == nullptr) {
return closeSftpSession();
}
int result = sftp_init(m_sftpSession);
if (result != SSH_OK) {
return closeSftpSession();
}
QFutureWatcher<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
QFuture<ErrorCode> 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 == nullptr) {
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 != nullptr) {
sftp_free(m_sftpSession);
m_sftpSession = nullptr;
}
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;
}
}
ErrorCode Client::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback)
{
int authResult = SSH_ERROR;
ErrorCode errorCode = ErrorCode::NoError;
ssh_key privateKey = nullptr;
m_passphraseCallback = passphraseCallback;
authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
if (authResult == SSH_OK) {
char* key = new char[65535];
authResult = ssh_pki_export_privkey_base64(privateKey, nullptr, nullptr, nullptr, &key);
decryptedPrivateKey = key;
delete[] key;
if (authResult != SSH_OK) {
qDebug() << "failed to export private key";
errorCode = ErrorCode::InternalError;
}
} else {
errorCode = ErrorCode::SshPrivateKeyError;
}
if (privateKey) {
ssh_key_free(privateKey);
}
return errorCode;
}
}

58
client/core/sshclient.h Normal file
View file

@ -0,0 +1,58 @@
#ifndef SSHCLIENT_H
#define SSHCLIENT_H
#include <QObject>
#include <fcntl.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#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
public:
Client(QObject *parent = nullptr);
~Client();
ErrorCode connectToHost(const ServerCredentials &credentials);
void disconnectFromHost();
ErrorCode executeCommand(const QString &data,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
ErrorCode writeResponse(const QString &data);
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
const std::string& localPath,
const std::string& remotePath,
const std::string& fileDesc);
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
private:
ErrorCode closeChannel();
ErrorCode closeSftpSession();
ErrorCode fromLibsshErrorCode(int errorCode);
ErrorCode fromLibsshSftpErrorCode(int errorCode);
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
ssh_session m_session = nullptr;
ssh_channel m_channel = nullptr;
sftp_session m_sftpSession = nullptr;
static std::function<QString()> m_passphraseCallback;
signals:
void writeToChannelFinished();
void sftpFileCopyFinished();
};
}
#endif // SSHCLIENT_H