Files
amnezia-client/client/core/servercontroller.cpp

475 lines
16 KiB
C++
Raw Normal View History

2020-12-18 14:57:22 +03:00
#include "servercontroller.h"
2021-03-13 14:16:24 +03:00
#include <QCryptographicHash>
2020-12-18 14:57:22 +03:00
#include <QFile>
#include <QEventLoop>
#include <QLoggingCategory>
#include <QPointer>
#include <QTimer>
2021-01-15 23:36:35 +03:00
#include <QJsonObject>
#include <QJsonDocument>
#include <QApplication>
2020-12-18 14:57:22 +03:00
#include "sshconnectionmanager.h"
2021-01-06 17:12:24 +03:00
2020-12-18 14:57:22 +03:00
using namespace QSsh;
2021-01-15 23:36:35 +03:00
QString ServerController::getContainerName(DockerContainer container)
{
switch (container) {
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
default: return "";
}
}
ErrorCode ServerController::runScript(DockerContainer container,
const SshConnectionParameters &sshParams, QString script,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
2020-12-18 14:57:22 +03:00
{
SshConnection *client = connectToHost(sshParams);
2021-01-06 17:12:24 +03:00
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
}
2020-12-18 14:57:22 +03:00
script.replace("\r", "");
qDebug() << "Run script";
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
2021-01-15 23:36:35 +03:00
QString line = lines.at(i);
line.replace("$CONTAINER_NAME", getContainerName(container));
2020-12-18 14:57:22 +03:00
if (line.startsWith("#")) {
continue;
}
qDebug().noquote() << "EXEC" << line;
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
2021-01-06 17:12:24 +03:00
return ErrorCode::SshRemoteProcessCreationError;
2020-12-18 14:57:22 +03:00
}
QEventLoop wait;
2021-01-21 19:14:07 +03:00
int exitStatus = -1;
2020-12-18 14:57:22 +03:00
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
// qDebug() << "Command started";
// });
2020-12-18 14:57:22 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
2021-01-06 17:12:24 +03:00
exitStatus = status;
//qDebug() << "Remote process exited with status" << status;
2020-12-18 14:57:22 +03:00
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, &wait, [proc, cbReadStdOut](){
2021-01-21 19:14:07 +03:00
QString s = proc->readAllStandardOutput();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << "stdout" << s;
2021-01-21 19:14:07 +03:00
}
if (cbReadStdOut) cbReadStdOut(s, proc);
2021-01-21 19:14:07 +03:00
});
2020-12-18 14:57:22 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
2021-01-21 19:14:07 +03:00
QString s = proc->readAllStandardError();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << "stderr" << s;
2021-01-21 19:14:07 +03:00
}
if (cbReadStdErr) cbReadStdErr(s, proc);
2021-01-21 19:14:07 +03:00
});
2020-12-18 14:57:22 +03:00
proc->start();
2021-01-21 19:14:07 +03:00
if (i < lines.count() && exitStatus < 0) {
2020-12-18 14:57:22 +03:00
wait.exec();
}
2021-01-06 17:12:24 +03:00
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
return fromSshProcessExitStatus(exitStatus);
}
2020-12-18 14:57:22 +03:00
}
2021-01-03 15:37:03 +03:00
qDebug() << "ServerController::runScript finished\n";
2021-01-06 17:12:24 +03:00
return ErrorCode::NoError;
2020-12-18 14:57:22 +03:00
}
2021-01-15 23:36:35 +03:00
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, QString &file, const QString &path)
2020-12-18 14:57:22 +03:00
{
QString script = QString("sudo docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
2021-01-15 23:36:35 +03:00
arg(getContainerName(container)).arg(file).arg(path);
2020-12-18 14:57:22 +03:00
2021-03-13 14:16:24 +03:00
// qDebug().noquote() << "uploadTextFileToContainer\n" << script;
2020-12-18 14:57:22 +03:00
2021-01-06 17:12:24 +03:00
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
}
2020-12-18 14:57:22 +03:00
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
2021-01-06 17:12:24 +03:00
return ErrorCode::SshRemoteProcessCreationError;
2020-12-18 14:57:22 +03:00
}
QEventLoop wait;
2021-01-21 19:14:07 +03:00
int exitStatus = -1;
2020-12-18 14:57:22 +03:00
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
// qDebug() << "uploadTextFileToContainer started";
// });
2020-12-18 14:57:22 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
2021-01-06 17:12:24 +03:00
//qDebug() << "Remote process exited with status" << status;
exitStatus = status;
2020-12-18 14:57:22 +03:00
wait.quit();
});
2021-01-15 23:36:35 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
qDebug().noquote() << proc->readAllStandardOutput();
});
2020-12-18 14:57:22 +03:00
2021-01-15 23:36:35 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
qDebug().noquote() << proc->readAllStandardError();
});
2020-12-18 14:57:22 +03:00
proc->start();
2021-01-06 17:12:24 +03:00
2021-01-21 19:14:07 +03:00
if (exitStatus < 0) {
wait.exec();
}
2021-01-15 23:36:35 +03:00
2021-01-06 17:12:24 +03:00
return fromSshProcessExitStatus(exitStatus);
2020-12-18 14:57:22 +03:00
}
2021-01-15 23:36:35 +03:00
QString ServerController::getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
2020-12-18 14:57:22 +03:00
{
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
2021-01-15 23:36:35 +03:00
arg(getContainerName(container)).arg(path);
2020-12-18 14:57:22 +03:00
2021-01-03 15:37:03 +03:00
qDebug().noquote() << "Copy file from container\n" << script;
2021-01-06 17:12:24 +03:00
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState());
return QString();
}
2020-12-18 14:57:22 +03:00
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
2021-01-06 17:12:24 +03:00
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
if (errorCode) *errorCode = ErrorCode::SshRemoteProcessCreationError;
return QString();
}
2020-12-18 14:57:22 +03:00
QEventLoop wait;
2021-01-06 17:12:24 +03:00
int exitStatus = 0;
2020-12-18 14:57:22 +03:00
2021-01-06 17:12:24 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
exitStatus = status;
2020-12-18 14:57:22 +03:00
wait.quit();
});
2021-01-21 19:14:07 +03:00
QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [&](){
qDebug() << "ServerController::getTextFileFromContainer proc started";
exitStatus = -1;
});
2020-12-18 14:57:22 +03:00
proc->start();
wait.exec();
2021-01-21 19:14:07 +03:00
// if (exitStatus < 0) {
2021-01-15 23:36:35 +03:00
// wait.exec();
// }
2021-01-06 17:12:24 +03:00
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus);
}
2020-12-18 14:57:22 +03:00
return proc->readAllStandardOutput();
}
2021-01-15 23:36:35 +03:00
ErrorCode ServerController::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
2020-12-18 14:57:22 +03:00
{
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
2021-01-15 23:36:35 +03:00
"easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
.arg(getContainerName(container)).arg(clientId);
2020-12-18 14:57:22 +03:00
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
2021-01-15 23:36:35 +03:00
"easyrsa sign-req client %2\"")
.arg(getContainerName(container)).arg(clientId);
2020-12-18 14:57:22 +03:00
QStringList script {script_import, script_sign};
2021-01-15 23:36:35 +03:00
return runScript(container, sshParams(credentials), script.join("\n"));
2021-01-06 17:12:24 +03:00
}
2021-01-15 23:36:35 +03:00
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
2021-01-06 17:12:24 +03:00
{
2021-01-15 23:36:35 +03:00
QString caCert = ServerController::getTextFileFromContainer(container,
credentials, ServerController::caCertPath());
QString taKey = ServerController::getTextFileFromContainer(container,
credentials, ServerController::taKeyPath());
2021-01-06 17:12:24 +03:00
if (!caCert.isEmpty() && !taKey.isEmpty()) {
return ErrorCode::NoError;
}
else {
return ErrorCode::ServerCheckFailed;
}
}
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;
2021-01-15 23:36:35 +03:00
default: return ErrorCode::SshInternalError;
2021-01-06 17:12:24 +03:00
}
}
ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
{
2021-01-15 23:36:35 +03:00
qDebug() << exitStatus;
2021-01-06 17:12:24 +03:00
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;
2021-01-15 23:36:35 +03:00
default: return ErrorCode::SshInternalError;
2021-01-06 17:12:24 +03:00
}
}
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.password = credentials.password;
}
2021-01-06 17:12:24 +03:00
sshParams.host = credentials.hostName;
sshParams.userName = credentials.userName;
sshParams.timeout = 10;
sshParams.port = credentials.port;
sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone;
2021-03-09 18:45:41 +03:00
sshParams.options = SshIgnoreDefaultProxy;
2021-01-06 17:12:24 +03:00
return sshParams;
2020-12-18 14:57:22 +03:00
}
2021-01-06 17:12:24 +03:00
ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto)
2020-12-18 14:57:22 +03:00
{
QString scriptFileName;
2021-01-15 23:36:35 +03:00
DockerContainer container;
2020-12-18 14:57:22 +03:00
2021-01-15 23:36:35 +03:00
if (proto == Protocol::Any) {
ErrorCode e = removeServer(credentials, Protocol::OpenVpn);
if (e) {
return e;
}
return removeServer(credentials, Protocol::ShadowSocks);
2020-12-18 14:57:22 +03:00
}
2021-01-15 23:36:35 +03:00
else if (proto == Protocol::OpenVpn) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::OpenVpn;
}
else if (proto == Protocol::ShadowSocks) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::ShadowSocks;
}
else return ErrorCode::NotImplementedError;
2020-12-18 14:57:22 +03:00
QString scriptData;
QFile file(scriptFileName);
2021-01-06 17:12:24 +03:00
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
2020-12-18 14:57:22 +03:00
scriptData = file.readAll();
2021-01-06 17:12:24 +03:00
if (scriptData.isEmpty()) return ErrorCode::InternalError;
2020-12-18 14:57:22 +03:00
2021-01-15 23:36:35 +03:00
return runScript(container, sshParams(credentials), scriptData);
2020-12-18 14:57:22 +03:00
}
2021-01-06 17:12:24 +03:00
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
2020-12-18 14:57:22 +03:00
{
2021-01-06 17:12:24 +03:00
if (proto == Protocol::OpenVpn) {
return ErrorCode::NoError;
//return setupOpenVpnServer(credentials);
2021-01-06 17:12:24 +03:00
}
else if (proto == Protocol::ShadowSocks) {
return setupShadowSocksServer(credentials);
}
else if (proto == Protocol::Any) {
//return ErrorCode::NotImplementedError;
2021-01-15 23:36:35 +03:00
2021-01-06 17:12:24 +03:00
// TODO: run concurently
//setupOpenVpnServer(credentials);
2021-03-08 18:17:50 +03:00
return setupShadowSocksServer(credentials);
2020-12-18 14:57:22 +03:00
}
return ErrorCode::NoError;
2021-01-06 17:12:24 +03:00
}
2020-12-18 14:57:22 +03:00
2021-01-06 17:12:24 +03:00
ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials)
{
QString scriptData;
QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh";
2020-12-18 14:57:22 +03:00
QFile file(scriptFileName);
2021-01-06 17:12:24 +03:00
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
2020-12-18 14:57:22 +03:00
scriptData = file.readAll();
2021-01-06 17:12:24 +03:00
if (scriptData.isEmpty()) return ErrorCode::InternalError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
if (data.contains("Automatically restart Docker daemon?")) {
proc->write("yes\n");
}
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
2021-01-06 17:12:24 +03:00
if (e) return e;
QApplication::processEvents();
if (stdOut.contains("port is already allocated")) return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("Error response from daemon")) return ErrorCode::ServerCheckFailed;
2020-12-18 14:57:22 +03:00
2021-01-15 23:36:35 +03:00
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
2021-01-06 17:12:24 +03:00
}
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
{
2021-01-15 23:36:35 +03:00
// Setup openvpn part
QString scriptData;
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
QFile file(scriptFileName);
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
2021-01-15 23:36:35 +03:00
scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
if (data.contains("Automatically restart Docker daemon?")) {
proc->write("yes\n");
}
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
ErrorCode e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
2021-01-15 23:36:35 +03:00
if (e) return e;
// Create ss config
QJsonObject ssConfig;
ssConfig.insert("server", "0.0.0.0");
ssConfig.insert("server_port", ssRemotePort());
ssConfig.insert("local_port", ssContainerPort());
2021-03-13 14:16:24 +03:00
ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
2021-01-15 23:36:35 +03:00
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ssEncryption());
QString configData = QJsonDocument(ssConfig).toJson();
QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
configData.replace("\"", "\\\"");
2021-03-08 18:17:50 +03:00
//qDebug().noquote() << configData;
2021-01-15 23:36:35 +03:00
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
// Start ss
QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\"").
2021-01-15 23:36:35 +03:00
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
return e;
2020-12-18 14:57:22 +03:00
}
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
{
SshConnection *client = acquireConnection(sshParams);
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;
// });
2020-12-18 14:57:22 +03:00
//qDebug() << "Connection state" << client->state();
if (client->state() == SshConnection::State::Unconnected) {
client->connectToHost();
waitssh.exec();
}
// QObject::connect(&client, &SshClient::sshDataReceived, [&](){
// qDebug().noquote() << "Data received";
// });
2020-12-18 14:57:22 +03:00
// if(client.sshState() != SshClient::SshState::Ready) {
// qCritical() << "Can't connect to server";
// return false;
// }
// else {
// qDebug() << "SSh connection established";
// }
2020-12-18 14:57:22 +03:00
// QObject::connect(proc, &SshProcess::finished, &wait, &QEventLoop::quit);
// QObject::connect(proc, &SshProcess::failed, &wait, &QEventLoop::quit);
2020-12-18 14:57:22 +03:00
return client;
}
2021-01-21 19:14:07 +03:00
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
QFile file(":/server_scripts/setup_firewall.sh");
file.open(QIODevice::ReadOnly);
QString script = file.readAll();
return runScript(DockerContainer::OpenVpn, sshParams(credentials), script);
}