diff --git a/client/core/openvpnconfigurator.cpp b/client/core/openvpnconfigurator.cpp new file mode 100644 index 00000000..38747a81 --- /dev/null +++ b/client/core/openvpnconfigurator.cpp @@ -0,0 +1,159 @@ +#include "openvpnconfigurator.h" +#include +#include +#include +#include +#include +#include + +QString OpenVpnConfigurator::getRandomString(int len) +{ + const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + + QString randomString; + for(int i=0; igenerate() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + return randomString; +} + +QString OpenVpnConfigurator::getEasyRsaShPath() +{ + QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa"; + easyRsaShPath.replace(":", ""); + easyRsaShPath.replace("\\", "/"); + easyRsaShPath.prepend("/"); + + return easyRsaShPath; +} + +QProcessEnvironment OpenVpnConfigurator::prepareEnv() +{ + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString pathEnvVar = env.value("PATH"); + pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\bin;"); + + env.insert("PATH", pathEnvVar); + return env; +} + +void OpenVpnConfigurator::initPKI(const QString &path) +{ +#ifdef Q_OS_WIN + QProcess p; + p.setProcessChannelMode(QProcess::MergedChannels); + p.setProcessEnvironment(prepareEnv()); + + QString command = QString("sh.exe"); + + p.setNativeArguments(getEasyRsaShPath() + " init-pki"); + + p.setWorkingDirectory(path); + + p.start(command); + p.waitForFinished(); + qDebug().noquote() << p.readAll(); + +#endif +} + +QString OpenVpnConfigurator::genReq(const QString &path, const QString &clientId) +{ +#ifdef Q_OS_WIN + QProcess p; + p.setProcessChannelMode(QProcess::MergedChannels); + p.setProcessEnvironment(prepareEnv()); + + QString command = QString("sh.exe"); + + p.setNativeArguments(getEasyRsaShPath() + " gen-req " + clientId + " nopass"); + + p.setWorkingDirectory(path); + + QObject::connect(&p, &QProcess::channelReadyRead, [&](){ + QString data = p.readAll(); + qDebug().noquote() << data; + + if (data.contains("Common Name (eg: your user, host, or server name)")) { + p.write("\n"); + } + }); + + p.start(command); + p.waitForFinished(); +// qDebug().noquote() << p.readAll(); + + return ""; +#endif +} + + +OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() +{ + OpenVpnConfigurator::ConnectionData connData; + connData.clientId = getRandomString(32); + + QTemporaryDir dir; +// if (dir.isValid()) { +// // dir.path() returns the unique directory path +// } + + QString path = dir.path(); + + initPKI(path); + genReq(path, connData.clientId); + + + QFile req(path + "/pki/reqs/" + connData.clientId + ".req"); + req.open(QIODevice::ReadOnly); + connData.request = req.readAll(); + + QFile key(path + "/pki/private/" + connData.clientId + ".key"); + key.open(QIODevice::ReadOnly); + connData.privKey = key.readAll(); + + qDebug().noquote() << connData.request; + qDebug().noquote() << connData.privKey; + + + return connData; +} + +OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams) +{ + OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); + connData.host = sshParams.host; + + QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId); + ServerController::uploadTextFileToContainer(sshParams, connData.request, reqFileName); + + ServerController::signCert(sshParams, connData.clientId); + + connData.caCert = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/pki/ca.crt")); + connData.clientCert = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/pki/issued/%1.crt").arg(connData.clientId)); + connData.taKey = ServerController::getTextFileFromContainer(sshParams, QString("/opt/amneziavpn_data/ta.key")); + + + return connData; +} + +QString OpenVpnConfigurator::genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams) +{ + QFile configTemplFile(":/server_scripts/template.ovpn"); + configTemplFile.open(QIODevice::ReadOnly); + QString config = configTemplFile.readAll(); + + ConnectionData connData = prepareOpenVpnConfig(sshParams); + + config.replace("$PROTO", "udp"); + config.replace("$REMOTE_HOST", connData.host); + config.replace("$REMOTE_PORT", "1194"); + config.replace("$CA_CERT", connData.caCert); + config.replace("$CLIENT_CERT", connData.clientCert); + config.replace("$PRIV_KEY", connData.privKey); + config.replace("$TA_KEY", connData.taKey); + + return config; +} diff --git a/client/core/openvpnconfigurator.h b/client/core/openvpnconfigurator.h new file mode 100644 index 00000000..2767d4f5 --- /dev/null +++ b/client/core/openvpnconfigurator.h @@ -0,0 +1,39 @@ +#ifndef OPENVPNCONFIGURATOR_H +#define OPENVPNCONFIGURATOR_H + +#include +#include +#include "servercontroller.h" + + +class OpenVpnConfigurator +{ +public: + + struct ConnectionData { + QString clientId; + QString request; // certificate request + QString privKey; // client private key + QString clientCert; // client signed certificate + QString caCert; // server certificate + QString taKey; // tls-auth key + QString host; // host ip + }; + + static QString genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams); + +private: + static QString getRandomString(int len); + static QString getEasyRsaShPath(); + + static QProcessEnvironment prepareEnv(); + static void initPKI(const QString &path); + static QString genReq(const QString &path, const QString &clientId); + + static ConnectionData createCertRequest(); + + static ConnectionData prepareOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams); + +}; + +#endif // OPENVPNCONFIGURATOR_H diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp new file mode 100644 index 00000000..84a75c2a --- /dev/null +++ b/client/core/servercontroller.cpp @@ -0,0 +1,245 @@ +#include "servercontroller.h" + +#include +#include +#include +#include +#include + +//#include "sshclient.h" +//#include "sshprocess.h" + +#include "sshconnectionmanager.h" +#include "sshremoteprocess.h" + +using namespace QSsh; + +bool ServerController::runScript(const SshConnectionParameters &sshParams, QString script) +{ + QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); + + SshConnection *client = connectToHost(sshParams); + + script.replace("\r", ""); + + qDebug() << "Run script"; + + const QStringList &lines = script.split("\n", QString::SkipEmptyParts); + for (int i = 0; i < lines.count(); i++) { + const QString &line = lines.at(i); + if (line.startsWith("#")) { + continue; + } + + qDebug().noquote() << "EXEC" << line; + QSharedPointer proc = client->createRemoteProcess(line.toUtf8()); + + if (!proc) { + qCritical() << "Failed to create SshRemoteProcess, breaking."; + return false; + } + + QEventLoop wait; + + QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){ + qDebug() << "Command started"; + }); + + QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){ + qDebug() << "Remote process exited with status" << status; + wait.quit(); + }); + + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ + qDebug().noquote() << proc->readAllStandardOutput(); + }); + + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ + qDebug().noquote() << proc->readAllStandardError(); + }); + + proc->start(); + if (i < lines.count() - 1) { + wait.exec(); + } + } + + qDebug() << "ServerController::runScript finished"; + +// client->disconnectFromHost(); + +// client->deleteLater(); + return true; +} + +void ServerController::uploadTextFileToContainer(const SshConnectionParameters &sshParams, + QString &file, const QString &path) +{ + QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); + + QString script = QString("docker exec -i amneziavpn sh -c \"echo \'%1\' > %2\""). + arg(file).arg(path); + + qDebug().noquote() << script; + + SshConnection *client = connectToHost(sshParams); + QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); + + if (!proc) { + qCritical() << "Failed to create SshRemoteProcess, breaking."; + return; + } + + QEventLoop wait; + + QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){ + qDebug() << "Command started"; + }); + + QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){ + qDebug() << "Remote process exited with status" << status; + wait.quit(); + }); + + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ + qDebug().noquote() << proc->readAllStandardOutput(); + }); + + QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ + qDebug().noquote() << proc->readAllStandardError(); + }); + + proc->start(); + wait.exec(); +} + +QString ServerController::getTextFileFromContainer(const SshConnectionParameters &sshParams, const QString &path) +{ + QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\""). + arg(path); + + SshConnection *client = connectToHost(sshParams); + QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); + + QEventLoop wait; + + QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int ){ + wait.quit(); + }); + + proc->start(); + wait.exec(); + + return proc->readAllStandardOutput(); +} + +bool ServerController::signCert(const SshConnectionParameters &sshParams, QString clientId) +{ + QString script_import = QString("docker exec -i amneziavpn bash -c \"cd /opt/amneziavpn_data && " + "easyrsa import-req /opt/amneziavpn_data/clients/%1.req %1 &>/dev/null\"") + .arg(clientId); + + QString script_sign = QString("docker exec -i amneziavpn bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && " + "easyrsa sign-req client %1 &>/dev/null\"") + .arg(clientId); + + QStringList script {script_import, script_sign}; + + return runScript(sshParams, script.join("\n")); +} + +bool ServerController::removeServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType) +{ + QString scriptFileName; + + if (sType == OpenVPN) { + scriptFileName = ":/server_scripts/remove_openvpn_server.sh"; + } + + QString scriptData; + + QFile file(scriptFileName); + if (! file.open(QIODevice::ReadOnly)) { + return false; + } + + scriptData = file.readAll(); + if (scriptData.isEmpty()) return false; + + return runScript(sshParams, scriptData); +} + +bool ServerController::setupServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType) +{ + QString scriptFileName; + + if (sType == OpenVPN) { + scriptFileName = ":/server_scripts/setup_openvpn_server.sh"; + } + + QString scriptData; + + QFile file(scriptFileName); + if (! file.open(QIODevice::ReadOnly)) { + return false; + } + + scriptData = file.readAll(); + if (scriptData.isEmpty()) return false; + + return runScript(sshParams, scriptData); +} + +SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams) +{ + SshConnection *client = acquireConnection(sshParams); + //QPointer client = new SshConnection(serverInfo); + + 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; +} diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h new file mode 100644 index 00000000..faec3d40 --- /dev/null +++ b/client/core/servercontroller.h @@ -0,0 +1,32 @@ +#ifndef SERVERCONTROLLER_H +#define SERVERCONTROLLER_H + +#include +#include "sshconnection.h" + +class ServerController : public QObject +{ + Q_OBJECT +public: + enum ServerType { + OpenVPN, + ShadowSocks, + WireGuard + }; + + static bool removeServer(const QSsh::SshConnectionParameters &sshParams, ServerType sType); + static bool setupServer(const QSsh::SshConnectionParameters &sshParams, ServerType sType); + + static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); + static bool runScript(const QSsh::SshConnectionParameters &sshParams, QString script); + + static void uploadTextFileToContainer(const QSsh::SshConnectionParameters &sshParams, QString &file, const QString &path); + static QString getTextFileFromContainer(const QSsh::SshConnectionParameters &sshParams, const QString &path); + + static bool signCert(const QSsh::SshConnectionParameters &sshParams, QString clientId); + +signals: + +}; + +#endif // SERVERCONTROLLER_H