mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
244 lines
8.2 KiB
C++
244 lines
8.2 KiB
C++
#include "sshSession.h"
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QDir>
|
|
#include <QEventLoop>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QLoggingCategory>
|
|
#include <QPointer>
|
|
#include <QTemporaryFile>
|
|
#include <QThread>
|
|
#include <QTimer>
|
|
#include <QtConcurrent>
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sys/stat.h>
|
|
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
#include "core/utils/containerEnum.h"
|
|
#include "core/utils/containers/containerUtils.h"
|
|
#include "core/utils/protocolEnum.h"
|
|
#include "core/utils/networkUtilities.h"
|
|
#include "core/utils/selfhosted/scriptsRegistry.h"
|
|
#include "logger.h"
|
|
#include "core/utils/utilities.h"
|
|
|
|
namespace
|
|
{
|
|
Logger logger("SshSession");
|
|
}
|
|
|
|
SshSession::SshSession(QObject *parent) : QObject(parent)
|
|
{
|
|
}
|
|
|
|
SshSession::~SshSession()
|
|
{
|
|
m_sshClient.disconnectFromHost();
|
|
}
|
|
|
|
ErrorCode SshSession::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", "");
|
|
|
|
qDebug() << "SshSession::Run script";
|
|
|
|
QString totalLine;
|
|
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
|
|
for (int i = 0; i < lines.count(); i++) {
|
|
QString currentLine = lines.at(i);
|
|
|
|
if (totalLine.isEmpty()) {
|
|
totalLine = currentLine;
|
|
} else {
|
|
totalLine = totalLine + "\n" + currentLine;
|
|
}
|
|
|
|
QString lineToExec;
|
|
if (currentLine.endsWith("\\")) {
|
|
continue;
|
|
} else {
|
|
lineToExec = totalLine;
|
|
totalLine.clear();
|
|
}
|
|
|
|
if (lineToExec.startsWith("#")) {
|
|
continue;
|
|
}
|
|
|
|
qDebug().noquote() << lineToExec;
|
|
|
|
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
|
if (error != ErrorCode::NoError) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
qDebug().noquote() << "SshSession::runScript finished\n";
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
|
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";
|
|
|
|
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
|
if (e)
|
|
return e;
|
|
|
|
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
|
|
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
|
|
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
|
|
|
|
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
|
runScript(credentials, replaceVars(remover, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
|
|
|
|
return e;
|
|
}
|
|
|
|
ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
|
const QString &path, libssh::ScpOverwriteMode overwriteMode)
|
|
{
|
|
ErrorCode e = ErrorCode::NoError;
|
|
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
|
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
|
|
if (e)
|
|
return e;
|
|
|
|
QString stdOut;
|
|
auto cbReadStd = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
// mkdir
|
|
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
|
|
|
|
e = runScript(credentials, replaceVars(mkdir, amnezia::genBaseVars(credentials, container, QString(), QString())));
|
|
if (e)
|
|
return e;
|
|
|
|
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
|
e = runScript(credentials,
|
|
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStd, cbReadStd);
|
|
|
|
if (e)
|
|
return e;
|
|
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
|
e = runScript(credentials,
|
|
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStd, cbReadStd);
|
|
|
|
if (e)
|
|
return e;
|
|
|
|
e = runScript(credentials,
|
|
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStd, cbReadStd);
|
|
|
|
if (e)
|
|
return e;
|
|
} else
|
|
return ErrorCode::NotImplementedError;
|
|
|
|
if (stdOut.contains("Error") && stdOut.contains("No such container")) {
|
|
return ErrorCode::ServerContainerMissingError;
|
|
}
|
|
|
|
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), amnezia::genBaseVars(credentials, container, QString(), QString())));
|
|
return e;
|
|
}
|
|
|
|
QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
|
ErrorCode &errorCode)
|
|
{
|
|
|
|
errorCode = ErrorCode::NoError;
|
|
|
|
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerUtils::containerToString(container), path);
|
|
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data;
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
errorCode = runScript(credentials, script, cbReadStdOut);
|
|
return QByteArray::fromHex(stdOut.toUtf8());
|
|
}
|
|
|
|
ErrorCode SshSession::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
|
libssh::ScpOverwriteMode overwriteMode)
|
|
{
|
|
auto error = m_sshClient.connectToHost(credentials);
|
|
if (error != ErrorCode::NoError) {
|
|
return error;
|
|
}
|
|
|
|
QTemporaryFile localFile;
|
|
localFile.open();
|
|
localFile.write(data);
|
|
localFile.close();
|
|
|
|
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
|
|
|
if (error != ErrorCode::NoError) {
|
|
return error;
|
|
}
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
QString SshSession::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
|
|
|
return stdOut;
|
|
}
|
|
|
|
QString SshSession::replaceVars(const QString &script, const Vars &vars)
|
|
{
|
|
QString s = script;
|
|
for (const QPair<QString, QString> &var : vars) {
|
|
s.replace(var.first, var.second);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
ErrorCode SshSession::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
|
const std::function<QString()> &callback)
|
|
{
|
|
auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback);
|
|
return error;
|
|
}
|