From 5eede7166788a29443d04b8a0366bcb886b57bed Mon Sep 17 00:00:00 2001 From: pokamest Date: Wed, 6 Jan 2021 17:12:24 +0300 Subject: [PATCH] Refactoring --- client/client.pro | 10 +- client/core/defs.h | 56 +++++ client/core/errorstrings.h | 47 ++++ client/core/openvpnconfigurator.cpp | 26 +- client/core/openvpnconfigurator.h | 6 +- client/core/servercontroller.cpp | 222 +++++++++++++----- client/core/servercontroller.h | 40 +++- client/{ => protocols}/openvpnprotocol.cpp | 19 +- client/{ => protocols}/openvpnprotocol.h | 2 +- client/{ => protocols}/vpnprotocol.cpp | 11 +- client/{ => protocols}/vpnprotocol.h | 13 +- client/resources.qrc | 1 + .../setup_shadowsocks_server.sh | 13 + client/settings.cpp | 34 ++- client/settings.h | 20 +- client/ui/mainwindow.cpp | 121 ++++++---- client/ui/mainwindow.h | 5 +- client/ui/mainwindow.ui | 49 +++- client/utils.cpp | 2 +- client/vpnconnection.cpp | 62 +++-- client/vpnconnection.h | 13 +- 21 files changed, 559 insertions(+), 213 deletions(-) create mode 100644 client/core/defs.h create mode 100644 client/core/errorstrings.h rename client/{ => protocols}/openvpnprotocol.cpp (93%) rename client/{ => protocols}/openvpnprotocol.h (97%) rename client/{ => protocols}/vpnprotocol.cpp (94%) rename client/{ => protocols}/vpnprotocol.h (85%) create mode 100644 client/server_scripts/setup_shadowsocks_server.sh diff --git a/client/client.pro b/client/client.pro index ff423075c..9f2ac434a 100644 --- a/client/client.pro +++ b/client/client.pro @@ -11,6 +11,8 @@ include("3rd/QtSsh/src/botan/botan.pri") HEADERS += \ communicator.h \ + core/defs.h \ + core/errorstrings.h \ core/openvpnconfigurator.h \ core/router.h \ core/servercontroller.h \ @@ -19,14 +21,14 @@ HEADERS += \ localclient.h \ managementserver.h \ message.h \ - openvpnprotocol.h \ runguard.h \ settings.h \ ui/Controls/SlidingStackedWidget.h \ ui/mainwindow.h \ utils.h \ vpnconnection.h \ - vpnprotocol.h \ + protocols/vpnprotocol.h \ + protocols/openvpnprotocol.h \ SOURCES += \ communicator.cpp \ @@ -38,14 +40,14 @@ SOURCES += \ main.cpp \ managementserver.cpp \ message.cpp \ - openvpnprotocol.cpp \ runguard.cpp \ settings.cpp \ ui/Controls/SlidingStackedWidget.cpp \ ui/mainwindow.cpp \ utils.cpp \ vpnconnection.cpp \ - vpnprotocol.cpp \ + protocols/vpnprotocol.cpp \ + protocols/openvpnprotocol.cpp \ FORMS += ui/mainwindow.ui diff --git a/client/core/defs.h b/client/core/defs.h new file mode 100644 index 000000000..230a44574 --- /dev/null +++ b/client/core/defs.h @@ -0,0 +1,56 @@ +#ifndef DEFS_H +#define DEFS_H + +#include + +namespace amnezia { + +enum class Protocol { + Any, + OpenVpn, + ShadowSocks, + WireGuard +}; + +struct ServerCredentials +{ + QString hostName; + QString userName; + QString password; + int port = 22; +}; + +enum ErrorCode +{ + // General error codes + NoError = 0, + UnknownError, + InternalError, + NotImplementedError, + + // Server errorz + ServerCheckFailed, + + // Ssh connection errors + SshSocketError, SshTimeoutError, SshProtocolError, + SshHostKeyError, SshKeyFileError, SshAuthenticationError, + SshClosedByServerError, SshInternalError, + + // Ssh remote process errors + SshRemoteProcessCreationError, + FailedToStartRemoteProcessError, RemoteProcessCrashError, + + // Local errors + FailedToSaveConfigData, + OpenVpnConfigMissing, + OpenVpnManagementServerError, + + // Distro errors + AmneziaServiceConnectionFailed, + OpenVpnExecutableMissing, + EasyRsaExecutableMissing +}; + +} // namespace amnezia + +#endif // DEFS_H diff --git a/client/core/errorstrings.h b/client/core/errorstrings.h new file mode 100644 index 000000000..e3933ae69 --- /dev/null +++ b/client/core/errorstrings.h @@ -0,0 +1,47 @@ +#ifndef ERRORSTRINGS_H +#define ERRORSTRINGS_H + +#include "defs.h" +using namespace amnezia; + +QString errorString(ErrorCode code){ + switch (code) { + + // General error codes + case(NoError): return QObject::tr("No error"); + case(UnknownError): return QObject::tr("Unknown Error"); + case(NotImplementedError): return QObject::tr("Function not implemented"); + case(ServerCheckFailed): return QObject::tr("Server check failed"); + + // 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"); + case(SshInternalError): return QObject::tr("Ssh internal error"); + + // 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"); + + // Local errors + case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk"); + case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing"); + case (OpenVpnManagementServerError): return QObject::tr("OpenVpn management server error"); + + case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing"); + case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing"); + case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error"); + + case(InternalError): + default: + return QObject::tr("Internal error"); + } +} + + +#endif // ERRORSTRINGS_H diff --git a/client/core/openvpnconfigurator.cpp b/client/core/openvpnconfigurator.cpp index 38747a818..723897612 100644 --- a/client/core/openvpnconfigurator.cpp +++ b/client/core/openvpnconfigurator.cpp @@ -121,31 +121,39 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() return connData; } -OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams) +OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); - connData.host = sshParams.host; + connData.host = credentials.hostName; QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId); - ServerController::uploadTextFileToContainer(sshParams, connData.request, reqFileName); + ErrorCode e = ServerController::uploadTextFileToContainer(credentials, connData.request, reqFileName); + if (e) { + *errorCode = e; + return connData; + } - ServerController::signCert(sshParams, connData.clientId); + ServerController::signCert(credentials, 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")); + connData.caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath(), &e); + connData.clientCert = ServerController::getTextFileFromContainer(credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e); + if (e) { + *errorCode = e; + return connData; + } + connData.taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath(), &e); return connData; } -QString OpenVpnConfigurator::genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams) +QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode) { QFile configTemplFile(":/server_scripts/template.ovpn"); configTemplFile.open(QIODevice::ReadOnly); QString config = configTemplFile.readAll(); - ConnectionData connData = prepareOpenVpnConfig(sshParams); + ConnectionData connData = prepareOpenVpnConfig(credentials, errorCode); config.replace("$PROTO", "udp"); config.replace("$REMOTE_HOST", connData.host); diff --git a/client/core/openvpnconfigurator.h b/client/core/openvpnconfigurator.h index 2767d4f59..a4e965d47 100644 --- a/client/core/openvpnconfigurator.h +++ b/client/core/openvpnconfigurator.h @@ -3,6 +3,8 @@ #include #include + +#include "defs.h" #include "servercontroller.h" @@ -20,7 +22,7 @@ class OpenVpnConfigurator QString host; // host ip }; - static QString genOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams); + static QString genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); private: static QString getRandomString(int len); @@ -32,7 +34,7 @@ class OpenVpnConfigurator static ConnectionData createCertRequest(); - static ConnectionData prepareOpenVpnConfig(const QSsh::SshConnectionParameters &sshParams); + static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); }; diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 8f21a3ae5..788285977 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -6,19 +6,19 @@ #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) +ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script) { QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); SshConnection *client = connectToHost(sshParams); + if (client->state() != SshConnection::State::Connected) { + return fromSshConnectionErrorCode(client->errorState()); + } script.replace("\r", ""); @@ -36,49 +36,52 @@ bool ServerController::runScript(const SshConnectionParameters &sshParams, QStri if (!proc) { qCritical() << "Failed to create SshRemoteProcess, breaking."; - return false; + return ErrorCode::SshRemoteProcessCreationError; } QEventLoop wait; + int exitStatus; - QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){ - qDebug() << "Command started"; - }); +// 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; + exitStatus = status; + //qDebug() << "Remote process exited with status" << status; wait.quit(); }); - QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ - QString s = proc->readAllStandardOutput(); - if (s != "." && !s.isEmpty()) { - qDebug().noquote() << s; - } - }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ +// QString s = proc->readAllStandardOutput(); +// if (s != "." && !s.isEmpty()) { +// qDebug().noquote() << s << s.size(); +// } +// }); - QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ - QString s = proc->readAllStandardError(); - if (s != "." && !s.isEmpty()) { - qDebug().noquote() << s; - } - }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ +// QString s = proc->readAllStandardError(); +// if (s != "." && !s.isEmpty()) { +// qDebug().noquote() << s; +// } +// }); proc->start(); + if (i < lines.count() - 1) { wait.exec(); } + + if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) { + return fromSshProcessExitStatus(exitStatus); + } } qDebug() << "ServerController::runScript finished\n"; - -// client->disconnectFromHost(); - -// client->deleteLater(); - return true; + return ErrorCode::NoError; } -void ServerController::uploadTextFileToContainer(const SshConnectionParameters &sshParams, +ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &credentials, QString &file, const QString &path) { QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); @@ -88,60 +91,85 @@ void ServerController::uploadTextFileToContainer(const SshConnectionParameters & qDebug().noquote() << script; - SshConnection *client = connectToHost(sshParams); + SshConnection *client = connectToHost(sshParams(credentials)); + if (client->state() != SshConnection::State::Connected) { + return fromSshConnectionErrorCode(client->errorState()); + } + QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); if (!proc) { qCritical() << "Failed to create SshRemoteProcess, breaking."; - return; + return ErrorCode::SshRemoteProcessCreationError; } QEventLoop wait; + int exitStatus = 0; - QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){ - qDebug() << "Command started"; - }); +// 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; + //qDebug() << "Remote process exited with status" << status; + exitStatus = status; wait.quit(); }); - QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ - qDebug().noquote() << proc->readAllStandardOutput(); - }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){ +// qDebug().noquote() << proc->readAllStandardOutput(); +// }); - QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ - qDebug().noquote() << proc->readAllStandardError(); - }); +// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){ +// qDebug().noquote() << proc->readAllStandardError(); +// }); proc->start(); wait.exec(); + + return fromSshProcessExitStatus(exitStatus); } -QString ServerController::getTextFileFromContainer(const SshConnectionParameters &sshParams, const QString &path) +QString ServerController::getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, + ErrorCode *errorCode) { QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\""). arg(path); qDebug().noquote() << "Copy file from container\n" << script; - SshConnection *client = connectToHost(sshParams); + SshConnection *client = connectToHost(sshParams(credentials)); + if (client->state() != SshConnection::State::Connected) { + if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState()); + return QString(); + } + QSharedPointer proc = client->createRemoteProcess(script.toUtf8()); + if (!proc) { + qCritical() << "Failed to create SshRemoteProcess, breaking."; + if (errorCode) *errorCode = ErrorCode::SshRemoteProcessCreationError; + return QString(); + } QEventLoop wait; + int exitStatus = 0; - QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int ){ + QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){ + exitStatus = status; wait.quit(); }); proc->start(); wait.exec(); + if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) { + if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus); + } + return proc->readAllStandardOutput(); } -bool ServerController::signCert(const SshConnectionParameters &sshParams, QString clientId) +ErrorCode ServerController::signCert(const ServerCredentials &credentials, 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\"") @@ -153,55 +181,121 @@ bool ServerController::signCert(const SshConnectionParameters &sshParams, QStrin QStringList script {script_import, script_sign}; - return runScript(sshParams, script.join("\n")); + return runScript(sshParams(credentials), script.join("\n")); } -bool ServerController::removeServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType) +ErrorCode ServerController::checkOpenVpnServer(const ServerCredentials &credentials) +{ + QString caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath()); + QString taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath()); + + 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; + } +} + +ErrorCode ServerController::fromSshProcessExitStatus(int 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; + } +} + +SshConnectionParameters ServerController::sshParams(const ServerCredentials &credentials) +{ + QSsh::SshConnectionParameters sshParams; + sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword; + sshParams.host = credentials.hostName; + sshParams.userName = credentials.userName; + sshParams.password = credentials.password; + sshParams.timeout = 10; + sshParams.port = credentials.port; + sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone; + + return sshParams; +} + +ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto) { QString scriptFileName; - if (sType == OpenVPN) { + if (proto == Protocol::OpenVpn) { scriptFileName = ":/server_scripts/remove_openvpn_server.sh"; } QString scriptData; QFile file(scriptFileName); - if (! file.open(QIODevice::ReadOnly)) { - return false; - } + if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError; scriptData = file.readAll(); - if (scriptData.isEmpty()) return false; + if (scriptData.isEmpty()) return ErrorCode::InternalError; - return runScript(sshParams, scriptData); + return runScript(sshParams(credentials), scriptData); } -bool ServerController::setupServer(const SshConnectionParameters &sshParams, ServerController::ServerType sType) +ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto) { - QString scriptFileName; - - if (sType == OpenVPN) { - scriptFileName = ":/server_scripts/setup_openvpn_server.sh"; + if (proto == Protocol::OpenVpn) { + return setupOpenVpnServer(credentials); + } + else if (proto == Protocol::ShadowSocks) { + return setupShadowSocksServer(credentials); + } + else if (proto == Protocol::Any) { + // TODO: run concurently + return setupOpenVpnServer(credentials); + //setupShadowSocksServer(credentials); } - QString scriptData; + return ErrorCode::NotImplementedError; +} +ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials) +{ + QString scriptData; + QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh"; QFile file(scriptFileName); - if (! file.open(QIODevice::ReadOnly)) { - return false; - } + if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError; scriptData = file.readAll(); - if (scriptData.isEmpty()) return false; + if (scriptData.isEmpty()) return ErrorCode::InternalError; + + ErrorCode e = runScript(sshParams(credentials), scriptData); + if (e) return e; - return runScript(sshParams, scriptData); + //return ok; + return checkOpenVpnServer(credentials); +} + +ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials) +{ + return ErrorCode::NotImplementedError; } SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams) { SshConnection *client = acquireConnection(sshParams); - //QPointer client = new SshConnection(serverInfo); QEventLoop waitssh; QObject::connect(client, &SshConnection::connected, &waitssh, [&]() { diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index faec3d408..f4458791a 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -3,29 +3,43 @@ #include #include "sshconnection.h" +#include "sshremoteprocess.h" +#include "defs.h" + +using namespace amnezia; 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 ErrorCode fromSshConnectionErrorCode(QSsh::SshError error); - static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); - static bool runScript(const QSsh::SshConnectionParameters &sshParams, QString script); + // QSsh exitCode and exitStatus are different things + static ErrorCode fromSshProcessExitStatus(int exitStatus); + + static QString caCertPath() { return "/opt/amneziavpn_data/pki/ca.crt"; } + static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; } + static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; } + + static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials); - static void uploadTextFileToContainer(const QSsh::SshConnectionParameters &sshParams, QString &file, const QString &path); - static QString getTextFileFromContainer(const QSsh::SshConnectionParameters &sshParams, const QString &path); + static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto); + static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto); - static bool signCert(const QSsh::SshConnectionParameters &sshParams, QString clientId); + static ErrorCode checkOpenVpnServer(const ServerCredentials &credentials); + + static ErrorCode uploadTextFileToContainer(const ServerCredentials &credentials, QString &file, const QString &path); + static QString getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); + + static ErrorCode signCert(const ServerCredentials &credentials, QString clientId); + +private: + static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); + static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script); -signals: + static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials); + static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials); }; diff --git a/client/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp similarity index 93% rename from client/openvpnprotocol.cpp rename to client/protocols/openvpnprotocol.cpp index 180c27eca..41f4d65d1 100644 --- a/client/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -107,7 +107,7 @@ QString OpenVpnProtocol::openVpnExecPath() const #endif } -bool OpenVpnProtocol::start() +ErrorCode OpenVpnProtocol::start() { qDebug() << "Start OpenVPN connection"; @@ -116,18 +116,18 @@ bool OpenVpnProtocol::start() stop(); if (communicator() && !communicator()->connected()) { - setLastError("Communicator is not connected!"); - return false; + setLastError(ErrorCode::AmneziaServiceConnectionFailed); + return lastError(); } if (!QFileInfo::exists(openVpnExecPath())) { - setLastError("OpenVPN executable does not exist!\n" + openVpnExecPath()); - return false; + setLastError(ErrorCode::OpenVpnExecutableMissing); + return lastError(); } if (!QFileInfo::exists(configPath())) { - setLastError("OpenVPN config file does not exist!\n" + configPath()); - return false; + setLastError(ErrorCode::OpenVpnConfigMissing); + return lastError(); } QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; @@ -141,14 +141,15 @@ bool OpenVpnProtocol::start() }); if (!m_managementServer.start(m_managementHost, m_managementPort)) { - return false; + setLastError(ErrorCode::OpenVpnManagementServerError); + return lastError(); } setConnectionState(ConnectionState::Connecting); m_communicator->sendMessage(Message(Message::State::StartRequest, args)); startTimeoutTimer(); - return true; + return ErrorCode::NoError; } void OpenVpnProtocol::openVpnStateSigTermHandlerTimerEvent() diff --git a/client/openvpnprotocol.h b/client/protocols/openvpnprotocol.h similarity index 97% rename from client/openvpnprotocol.h rename to client/protocols/openvpnprotocol.h index 956a3b4d2..9bf1fbbfd 100644 --- a/client/openvpnprotocol.h +++ b/client/protocols/openvpnprotocol.h @@ -17,7 +17,7 @@ class OpenVpnProtocol : public VpnProtocol explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr); ~OpenVpnProtocol(); - bool start() override; + ErrorCode start() override; void stop() override; protected slots: diff --git a/client/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp similarity index 94% rename from client/vpnprotocol.cpp rename to client/protocols/vpnprotocol.cpp index 6e3a08f6a..5e9c35ba4 100644 --- a/client/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -19,11 +19,6 @@ VpnProtocol::VpnProtocol(const QString& args, QObject* parent) Q_UNUSED(args) } -VpnProtocol::~VpnProtocol() -{ - -} - void VpnProtocol::initializeCommunicator(QObject* parent) { if (!m_communicator) { @@ -36,13 +31,13 @@ Communicator* VpnProtocol::communicator() return m_communicator; } -void VpnProtocol::setLastError(const QString& error) +void VpnProtocol::setLastError(ErrorCode lastError) { - m_lastError = error; + m_lastError = lastError; qCritical().noquote() << m_lastError; } -QString VpnProtocol::lastError() const +ErrorCode VpnProtocol::lastError() const { return m_lastError; } diff --git a/client/vpnprotocol.h b/client/protocols/vpnprotocol.h similarity index 85% rename from client/vpnprotocol.h rename to client/protocols/vpnprotocol.h index b5f5955ac..3472afdf8 100644 --- a/client/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -4,6 +4,9 @@ #include #include +#include "core/defs.h" +using namespace amnezia; + class QTimer; class Communicator; @@ -13,7 +16,7 @@ class VpnProtocol : public QObject public: explicit VpnProtocol(const QString& args = QString(), QObject* parent = nullptr); - ~VpnProtocol(); + virtual ~VpnProtocol() override = default; enum class ConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, TunnelReconnecting, Error}; @@ -24,13 +27,13 @@ class VpnProtocol : public QObject virtual bool connected() const; virtual bool disconnected() const; - virtual bool start() = 0; + virtual ErrorCode start() = 0; virtual void stop() = 0; ConnectionState connectionState() const; - QString lastError() const; + ErrorCode lastError() const; QString textConnectionState() const; - void setLastError(const QString& error); + void setLastError(ErrorCode lastError); signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); @@ -53,7 +56,7 @@ protected slots: private: QTimer* m_timeoutTimer; - QString m_lastError; + ErrorCode m_lastError; quint64 m_receivedBytes; quint64 m_sentBytes; }; diff --git a/client/resources.qrc b/client/resources.qrc index 52bf71f49..ec0b46a25 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -37,5 +37,6 @@ server_scripts/setup_openvpn_server.sh server_scripts/template.ovpn images/background_connected.png + server_scripts/setup_shadowsocks_server.sh diff --git a/client/server_scripts/setup_shadowsocks_server.sh b/client/server_scripts/setup_shadowsocks_server.sh new file mode 100644 index 000000000..46df107ad --- /dev/null +++ b/client/server_scripts/setup_shadowsocks_server.sh @@ -0,0 +1,13 @@ +#DOCKER_IMAGE="amneziavpn/shadow-vpn:latest" +#CONTAINER_NAME="shadow-vpn" + +#sudo apt update +sudo apt install -y docker.io curl +sudo systemctl start docker + +sudo docker stop shadow-vpn +sudo docker rm -f shadow-vpn +sudo docker pull amneziavpn/shadow-vpn:latest +sudo docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/tcp -p 6789:6789/tcp --name shadow-vpn amneziavpn/shadow-vpn:latest + + diff --git a/client/settings.cpp b/client/settings.cpp index 299ac878b..e0062c2bd 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -12,30 +12,31 @@ Settings::Settings(QObject* parent) : QObject(parent) void Settings::read() { m_settings->beginGroup("Server"); - m_login = m_settings->value("login", QString()).toString(); + m_userName = m_settings->value("userName", QString()).toString(); m_password = m_settings->value("password", QString()).toString(); m_serverName = m_settings->value("serverName", QString()).toString(); + m_serverPort = m_settings->value("serverPort", 22).toInt(); m_settings->endGroup(); } - void Settings::save() { m_settings->beginGroup("Server"); - m_settings->setValue("login", m_login); + m_settings->setValue("userName", m_userName); m_settings->setValue("password", m_password); m_settings->setValue("serverName", m_serverName); + m_settings->setValue("serverPort", m_serverPort); m_settings->endGroup(); } bool Settings::haveAuthData() const { - return (!serverName().isEmpty() && !login().isEmpty() && !password().isEmpty()); + return (!serverName().isEmpty() && !userName().isEmpty() && !password().isEmpty()); } -void Settings::setLogin(const QString& login) +void Settings::setUserName(const QString& login) { - m_login = login; + m_userName = login; } void Settings::setPassword(const QString& password) @@ -48,17 +49,26 @@ void Settings::setServerName(const QString& serverName) m_serverName = serverName; } -QString Settings::login() const +void Settings::setServerPort(int serverPort) { - return m_login; + m_serverPort = serverPort; } -QString Settings::password() const +void Settings::setServerCredentials(const ServerCredentials &credentials) { - return m_password; + setServerName(credentials.hostName); + setServerPort(credentials.port); + setUserName(credentials.userName); + setPassword(credentials.password); } -QString Settings::serverName() const +ServerCredentials Settings::serverCredentials() { - return m_serverName; + ServerCredentials credentials; + credentials.hostName = serverName(); + credentials.userName = userName(); + credentials.password = password(); + credentials.port = serverPort(); + + return credentials; } diff --git a/client/settings.h b/client/settings.h index 9e74f97c9..14a7aa6a8 100644 --- a/client/settings.h +++ b/client/settings.h @@ -4,6 +4,10 @@ #include #include +#include "core/defs.h" + +using namespace amnezia; + class QSettings; class Settings : public QObject @@ -16,21 +20,27 @@ class Settings : public QObject void read(); void save(); - void setLogin(const QString& login); + void setUserName(const QString& login); void setPassword(const QString& password); void setServerName(const QString& serverName); + void setServerPort(int serverPort); + void setServerCredentials(const ServerCredentials &credentials); + + QString userName() const { return m_userName; } + QString password() const { return m_password; } + QString serverName() const { return m_serverName; } + int serverPort() const { return m_serverPort; } + ServerCredentials serverCredentials(); - QString login() const; - QString password() const; - QString serverName() const; bool haveAuthData() const; protected: QSettings* m_settings; - QString m_login; + QString m_userName; QString m_password; QString m_serverName; + int m_serverPort; }; #endif // SETTINGS_H diff --git a/client/ui/mainwindow.cpp b/client/ui/mainwindow.cpp index cc13775f7..9a17e3af2 100644 --- a/client/ui/mainwindow.cpp +++ b/client/ui/mainwindow.cpp @@ -2,10 +2,14 @@ #include #include #include +#include #include "communicator.h" + +#include "core/errorstrings.h" #include "core/openvpnconfigurator.h" #include "core/servercontroller.h" + #include "debug.h" #include "defines.h" #include "mainwindow.h" @@ -34,6 +38,10 @@ MainWindow::MainWindow(QWidget *parent) : ui->stackedWidget_main->setSpeed(200); ui->stackedWidget_main->setAnimation(QEasingCurve::Linear); + ui->label_new_server_wait_info->setVisible(false); + ui->progressBar_new_server_connection->setMinimum(0); + ui->progressBar_new_server_connection->setMaximum(300); + #ifdef Q_OS_MAC ui->widget_tittlebar->hide(); ui->stackedWidget_main->move(0,0); @@ -48,6 +56,8 @@ MainWindow::MainWindow(QWidget *parent) : goToPage(Page::Initialization); } + //goToPage(Page::Initialization); + connect(ui->pushButton_blocked_list, SIGNAL(clicked(bool)), this, SLOT(onPushButtonBlockedListClicked(bool))); connect(ui->pushButton_connect, SIGNAL(toggled(bool)), this, SLOT(onPushButtonConnectToggled(bool))); connect(ui->pushButton_settings, SIGNAL(clicked(bool)), this, SLOT(onPushButtonSettingsClicked(bool))); @@ -148,28 +158,71 @@ void MainWindow::onPushButtonNewServerConnectWithNewData(bool clicked) ui->lineEdit_new_server_password->text().isEmpty() ) { QMessageBox::warning(this, APPLICATION_NAME, tr("Please fill in all fields")); return; - } else { - qDebug() << "Start connection with new data"; - m_settings->setServerName(ui->lineEdit_new_server_ip->text()); - m_settings->setLogin(ui->lineEdit_new_server_login->text()); - m_settings->setPassword(ui->lineEdit_new_server_password->text()); - m_settings->save(); + } + + qDebug() << "Start connection with new data"; + + ServerCredentials serverCredentials; + serverCredentials.hostName = ui->lineEdit_new_server_ip->text(); + if (serverCredentials.hostName.contains(":")) { + serverCredentials.port = serverCredentials.hostName.split(":").at(1).toInt(); + serverCredentials.hostName = serverCredentials.hostName.split(":").at(0); + } + serverCredentials.userName = ui->lineEdit_new_server_login->text(); + serverCredentials.password = ui->lineEdit_new_server_password->text(); + + m_settings->setServerCredentials(serverCredentials); + m_settings->save(); + + ui->page_new_server->setEnabled(false); + ui->pushButton_new_server_connect_with_new_data->setVisible(false); + ui->label_new_server_wait_info->setVisible(true); + + QTimer timer; + connect(&timer, &QTimer::timeout, [&](){ + ui->progressBar_new_server_connection->setValue(ui->progressBar_new_server_connection->value() + 1); + }); + + ui->progressBar_new_server_connection->setValue(0); + timer.start(1000); + + + ErrorCode e = ServerController::setupServer(serverCredentials, Protocol::Any); + if (e) { + ui->page_new_server->setEnabled(true); + ui->pushButton_new_server_connect_with_new_data->setVisible(true); + ui->label_new_server_wait_info->setVisible(false); + + QMessageBox::warning(this, APPLICATION_NAME, + tr("Error occurred while configuring server.") + "\n" + + errorString(e) + "\n" + + tr("See logs for details.")); - //goToPage(Page::Vpn); + return; + } - if (requestOvpnConfig(m_settings->serverName(), m_settings->login(), m_settings->password())) { - goToPage(Page::Vpn); + // just ui progressbar tweak + timer.stop(); - ui->pushButton_connect->setDown(true); + int remaining_val = ui->progressBar_new_server_connection->maximum() - ui->progressBar_new_server_connection->value(); - if (!m_vpnConnection->connectToVpn()) { - ui->pushButton_connect->setChecked(false); - QMessageBox::critical(this, APPLICATION_NAME, m_vpnConnection->lastError()); - return; + if (remaining_val > 0) { + QTimer timer1; + QEventLoop loop1; + + connect(&timer1, &QTimer::timeout, [&](){ + ui->progressBar_new_server_connection->setValue(ui->progressBar_new_server_connection->value() + 1); + if (ui->progressBar_new_server_connection->value() >= ui->progressBar_new_server_connection->maximum()) { + loop1.quit(); } + }); - } + timer1.start(5); + loop1.exec(); } + + goToPage(Page::Vpn); + ui->pushButton_connect->setChecked(true); } void MainWindow::onBytesChanged(quint64 receivedData, quint64 sentData) @@ -249,9 +302,13 @@ void MainWindow::onConnectionStateChanged(VpnProtocol::ConnectionState state) void MainWindow::onPushButtonConnectToggled(bool checked) { if (checked) { - if (!m_vpnConnection->connectToVpn()) { + // TODO: Call connectToVpn with restricted server account + ServerCredentials credentials = m_settings->serverCredentials(); + + ErrorCode errorCode = m_vpnConnection->connectToVpn(credentials); + if (errorCode) { ui->pushButton_connect->setChecked(false); - QMessageBox::critical(this, APPLICATION_NAME, m_vpnConnection->lastError()); + QMessageBox::critical(this, APPLICATION_NAME, errorString(errorCode)); return; } ui->pushButton_connect->setEnabled(false); @@ -260,36 +317,6 @@ void MainWindow::onPushButtonConnectToggled(bool checked) } } -bool MainWindow::requestOvpnConfig(const QString& hostName, const QString& userName, const QString& password, int port, int timeout) -{ - QSsh::SshConnectionParameters sshParams; - sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword; - sshParams.host = hostName; - sshParams.userName = userName; - sshParams.password = password; - sshParams.timeout = timeout; - sshParams.port = port; - sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone; - - if (!ServerController::setupServer(sshParams, ServerController::OpenVPN)) { - return false; - } - - QString configData = OpenVpnConfigurator::genOpenVpnConfig(sshParams); - if (configData.isEmpty()) { - return false; - } - - QFile file(Utils::defaultVpnConfigFileName()); - if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)){ - QTextStream stream(&file); - stream << configData << endl; - return true; - } - - return false; -} - void MainWindow::on_pushButton_close_clicked() { qApp->exit(); diff --git a/client/ui/mainwindow.h b/client/ui/mainwindow.h index f50421817..6d02c8626 100644 --- a/client/ui/mainwindow.h +++ b/client/ui/mainwindow.h @@ -4,7 +4,7 @@ #include #include "framelesswindow.h" -#include "vpnprotocol.h" +#include "protocols/vpnprotocol.h" class Settings; class VpnConnection; @@ -45,9 +45,6 @@ private slots: void on_pushButton_close_clicked(); -protected: - bool requestOvpnConfig(const QString& hostName, const QString& userName, const QString& password, int port = 22, int timeout = 30); - private: void goToPage(Page page); diff --git a/client/ui/mainwindow.ui b/client/ui/mainwindow.ui index 54ff21428..361a3be57 100644 --- a/client/ui/mainwindow.ui +++ b/client/ui/mainwindow.ui @@ -653,10 +653,7 @@ color: #333333; QPushButton { - font-size: 13pt; - font: "Open Sans Semibold"; - color:rgb(212, 212, 212); - +color:rgb(212, 212, 212); border-radius: 4px; font-family: Lato; @@ -708,8 +705,26 @@ color: #15CDCB; - background: #181922; -border-radius: 4px; + QProgressBar{ +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} + +QProgressBar::chunk { +background: rgba(255, 255, 255, 0.15); +border-radius: 4px 0px 0px 4px; + +} + 24 @@ -721,7 +736,7 @@ border-radius: 4px; true - Connecting... + Configuring... @@ -772,6 +787,25 @@ QPushButton:hover { + + + true + + + + 40 + 490 + 301 + 41 + + + + Please wait, configuring process may take up to 5 minutes + + + true + + progressBar_new_server_connection label_2 label_4 @@ -784,6 +818,7 @@ QPushButton:hover { pushButton pushButton_back_from_new_server label_7 + label_new_server_wait_info diff --git a/client/utils.cpp b/client/utils.cpp index 937934361..5f23bb49d 100644 --- a/client/utils.cpp +++ b/client/utils.cpp @@ -81,7 +81,7 @@ bool Utils::processIsRunning(const QString& fileName) #ifdef Q_OS_WIN QProcess process; process.setReadChannel(QProcess::StandardOutput); - process.setReadChannelMode(QProcess::MergedChannels); + process.setProcessChannelMode(QProcess::MergedChannels); process.start(QString("wmic.exe /OUTPUT:STDOUT PROCESS get %1").arg("Caption")); process.waitForStarted(); process.waitForFinished(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 4ab255e77..1ed8f4449 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -1,6 +1,11 @@ #include +#include -#include "openvpnprotocol.h" +#include +#include + +#include "protocols/openvpnprotocol.h" +#include "utils.h" #include "vpnconnection.h" VpnConnection::VpnConnection(QObject* parent) : QObject(parent) @@ -8,11 +13,6 @@ VpnConnection::VpnConnection(QObject* parent) : QObject(parent) VpnProtocol::initializeCommunicator(parent); } -VpnConnection::~VpnConnection() -{ - -} - void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) { emit bytesChanged(receivedBytes, sentBytes); @@ -23,28 +23,56 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::ConnectionState state) emit connectionStateChanged(state); } -QString VpnConnection::lastError() const +ErrorCode VpnConnection::lastError() const { if (!m_vpnProtocol.data()) { - return "Unnown protocol"; + return ErrorCode::InternalError; } return m_vpnProtocol.data()->lastError(); } -bool VpnConnection::connectToVpn(Protocol protocol) +ErrorCode VpnConnection::requestVpnConfig(const ServerCredentials &credentials, Protocol protocol) { + ErrorCode errorCode = ErrorCode::NoError; + if (protocol == Protocol::OpenVpn) { + QString configData = OpenVpnConfigurator::genOpenVpnConfig(credentials, &errorCode); + if (errorCode) { + return errorCode; + } + + QFile file(Utils::defaultVpnConfigFileName()); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)){ + QTextStream stream(&file); + stream << configData << endl; + return ErrorCode::NoError; + } + + return ErrorCode::FailedToSaveConfigData; + } + else if (protocol == Protocol::ShadowSocks) { + // Request OpenVPN config and ShadowSocks + return ErrorCode::NotImplementedError; + } + return ErrorCode::NotImplementedError; + +} + +ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Protocol protocol) +{ + // TODO: Try protocols one by one in case of Protocol::Any + // TODO: Implement some behavior in case if connection not stable qDebug() << "Connect to VPN"; - switch (protocol) { - case Protocol::OpenVpn: + if (protocol == Protocol::Any || protocol == Protocol::OpenVpn) { + ErrorCode e = requestVpnConfig(credentials, Protocol::OpenVpn); + if (e) { + return e; + } m_vpnProtocol.reset(new OpenVpnProtocol()); - break; - ; - default: - // TODO, add later - return false; - ; + } + else if (protocol == Protocol::ShadowSocks) { + return ErrorCode::NotImplementedError; } connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState))); diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 70586c41f..0bf81851c 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -5,7 +5,10 @@ #include #include -#include "vpnprotocol.h" +#include "protocols/vpnprotocol.h" +#include "core/defs.h" + +using namespace amnezia; class VpnConnection : public QObject { @@ -13,13 +16,13 @@ class VpnConnection : public QObject public: explicit VpnConnection(QObject* parent = nullptr); - ~VpnConnection(); + ~VpnConnection() override = default; - enum class Protocol{OpenVpn}; static QString bytesToText(quint64 bytes); - QString lastError() const; - bool connectToVpn(Protocol protocol = Protocol::OpenVpn); + ErrorCode lastError() const; + ErrorCode requestVpnConfig(const ServerCredentials &credentials, Protocol protocol); + ErrorCode connectToVpn(const ServerCredentials &credentials, Protocol protocol = Protocol::Any); bool connected() const; bool disconnected() const; void disconnectFromVpn();