diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index f7466a37b..84f12ca12 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -68,6 +68,7 @@ set(AMNEZIAVPN_TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts ) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) @@ -119,7 +120,9 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h + ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h + ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h ${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h @@ -159,7 +162,9 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 22ffba54f..cba532fbd 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -84,8 +84,7 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); - m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + m_vpnConnection.reset(new VpnConnection(m_settings)); m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); @@ -98,18 +97,16 @@ void AmneziaApplication::init() qFatal("Android logging initialization failed"); } AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); - connect(m_settings.get(), &Settings::saveLogsChanged, - AndroidController::instance(), &AndroidController::setSaveLogs); + connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, - AndroidController::instance(), &AndroidController::setScreenshotsEnabled); + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), + &AndroidController::setScreenshotsEnabled); - connect(m_settings.get(), &Settings::serverRemoved, - AndroidController::instance(), &AndroidController::resetLastServer); + connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), + &AndroidController::resetLastServer); - connect(m_settings.get(), &Settings::settingsCleared, - [](){ AndroidController::instance()->resetLastServer(-1); }); + connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); }); connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { @@ -146,13 +143,10 @@ void AmneziaApplication::init() m_settingsController->importBackupFromOutside(filePath); }); - QTimer::singleShot(0, this, [this](){ - AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); - }); + QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); - connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { - AmneziaVPN::toggleScreenshots(enabled); - }); + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, + [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); }); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); @@ -368,28 +362,30 @@ void AmneziaApplication::initModels() m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(), &ServersModel::clearCachedProfile); - - connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, - [this](const QString &clientId, const QString &clientName, const DockerContainer container, - ServerCredentials credentials) { - m_serversModel->reloadDefaultServerContainerConfig(); - m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - emit m_configurator->clientModelUpdated(); - }); } void AmneziaApplication::initControllers() { - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, + m_vpnConnection, m_settings)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, + [this](const QString &errorMessage) { + emit m_pageController->showErrorMessage(errorMessage); + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); + connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(), + &ConnectionController::toggleConnection, Qt::QueuedConnection); + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, + m_clientManagementModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), &PageController::showPassphraseRequestDrawer); @@ -401,8 +397,7 @@ void AmneziaApplication::initControllers() m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, - m_settings, m_configurator)); + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); m_settingsController.reset( @@ -422,18 +417,4 @@ void AmneziaApplication::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - - m_apiController.reset(new ApiController(m_serversModel, m_containersModel)); - m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get()); - connect(m_apiController.get(), &ApiController::updateStarted, this, - [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); }); - connect(m_apiController.get(), &ApiController::errorOccurred, this, [this](const QString &errorMessage) { - if (m_connectionController->isConnectionInProgress()) { - emit m_pageController->showErrorMessage(errorMessage); - } - - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); - }); - connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(), - &ConnectionController::toggleConnection); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index a9f196458..e8410237b 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -10,7 +10,7 @@ #include "settings.h" #include "vpnconnection.h" -#include "configurators/vpn_configurator.h" +#include "core/controllers/apiController.h" #include "ui/controllers/connectionController.h" #include "ui/controllers/exportController.h" @@ -20,7 +20,6 @@ #include "ui/controllers/settingsController.h" #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" -#include "ui/controllers/apiController.h" #include "ui/controllers/appSplitTunnelingController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" @@ -82,7 +81,6 @@ private: QQmlApplicationEngine *m_engine {}; std::shared_ptr m_settings; - std::shared_ptr m_configurator; QSharedPointer m_containerProps; QSharedPointer m_protocolProps; diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index afbe4ad11..3bcf5831d 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -406,25 +406,29 @@ class AmneziaActivity : QtActivity() { Log.v(TAG, "Open file with filter: $filter") val mimeTypes = if (!filter.isNullOrEmpty()) { - val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE) + val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE) val mime = MimeTypeMap.getSingleton() extensionRegex.findAll(filter).map { - mime.getMimeTypeFromExtension(it.value.drop(2)) - }.filterNotNull().toSet() + it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*" + }.toSet() } else emptySet() Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) Log.v(TAG, "File mimyType filter: $mimeTypes") - when (mimeTypes.size) { - 1 -> type = mimeTypes.first() + if ("*/*" in mimeTypes) { + type = "*/*" + } else { + when (mimeTypes.size) { + 1 -> type = mimeTypes.first() - in 2..Int.MAX_VALUE -> { - type = "*/*" - putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) + in 2..Int.MAX_VALUE -> { + type = "*/*" + putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) + } + + else -> type = "*/*" } - - else -> type = "*/*" } }.also { startActivityForResult(it, OPEN_FILE_ACTION_CODE) diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index ce6c8f94e..37d509ee7 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -113,11 +113,13 @@ target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy ) set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy ) add_subdirectory(ios/networkextension) diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 186d20094..a9b41882a 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -10,10 +10,10 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr settings, QObject *pa { } -QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) +QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { - QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode); + QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode); QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); QString awgConfig = jsonConfig.value(config_key::config).toString(); diff --git a/client/configurators/awg_configurator.h b/client/configurators/awg_configurator.h index ef40804ca..8decd2d69 100644 --- a/client/configurators/awg_configurator.h +++ b/client/configurators/awg_configurator.h @@ -11,8 +11,8 @@ class AwgConfigurator : public WireguardConfigurator public: AwgConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); }; #endif // AWGCONFIGURATOR_H diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp index 88be71e13..6c719c70d 100644 --- a/client/configurators/cloak_configurator.cpp +++ b/client/configurators/cloak_configurator.cpp @@ -13,22 +13,24 @@ CloakConfigurator::CloakConfigurator(std::shared_ptr settings, QObject } -QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { - ErrorCode e = ErrorCode::NoError; ServerController serverController(m_settings); QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::cloak::ckPublicKeyPath, &e); + amnezia::protocols::cloak::ckPublicKeyPath, errorCode); cloakPublicKey.replace("\n", ""); + if (errorCode != ErrorCode::NoError) { + return ""; + } + QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::cloak::ckBypassUidKeyPath, &e); + amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode); cloakBypassUid.replace("\n", ""); - if (e) { - if (errorCode) *errorCode = e; + if (errorCode != ErrorCode::NoError) { return ""; } diff --git a/client/configurators/cloak_configurator.h b/client/configurators/cloak_configurator.h index cf6cd002f..5dbfd190f 100644 --- a/client/configurators/cloak_configurator.h +++ b/client/configurators/cloak_configurator.h @@ -7,14 +7,14 @@ using namespace amnezia; -class CloakConfigurator : ConfiguratorBase +class CloakConfigurator : public ConfiguratorBase { Q_OBJECT public: CloakConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); }; #endif // CLOAK_CONFIGURATOR_H diff --git a/client/configurators/configurator_base.cpp b/client/configurators/configurator_base.cpp index c24d7e1f2..7393cb1a3 100644 --- a/client/configurators/configurator_base.cpp +++ b/client/configurators/configurator_base.cpp @@ -1,8 +1,26 @@ #include "configurator_base.h" ConfiguratorBase::ConfiguratorBase(std::shared_ptr settings, QObject *parent) - : QObject{parent}, - m_settings(settings) + : QObject { parent }, m_settings(settings) { - +} + +QString ConfiguratorBase::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) +{ + processConfigWithDnsSettings(dns, protocolConfigString); + return protocolConfigString; +} + +QString ConfiguratorBase::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) +{ + processConfigWithDnsSettings(dns, protocolConfigString); + return protocolConfigString; +} + +void ConfiguratorBase::processConfigWithDnsSettings(const QPair &dns, QString &protocolConfigString) +{ + protocolConfigString.replace("$PRIMARY_DNS", dns.first); + protocolConfigString.replace("$SECONDARY_DNS", dns.second); } diff --git a/client/configurators/configurator_base.h b/client/configurators/configurator_base.h index da43a2bc2..3330e2721 100644 --- a/client/configurators/configurator_base.h +++ b/client/configurators/configurator_base.h @@ -3,10 +3,9 @@ #include -class Settings; - #include "containers/containers_defs.h" #include "core/defs.h" +#include "settings.h" class ConfiguratorBase : public QObject { @@ -14,7 +13,17 @@ class ConfiguratorBase : public QObject public: explicit ConfiguratorBase(std::shared_ptr settings, QObject *parent = nullptr); + virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) = 0; + + virtual QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); + virtual QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); + protected: + void processConfigWithDnsSettings(const QPair &dns, QString &protocolConfigString); + std::shared_ptr m_settings; }; diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 752d17502..9a8eebb0d 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -20,7 +20,7 @@ Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject } Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, ErrorCode errorCode) { Ikev2Configurator::ConnectionData connData; connData.host = credentials.hostName; @@ -40,17 +40,17 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se .arg(connData.clientId); ServerController serverController(m_settings); - ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert); + errorCode = serverController.runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") .arg(connData.password) .arg(connData.clientId) .arg(certFileName); - e = serverController.runContainerScript(credentials, container, scriptExportCert); + errorCode = serverController.runContainerScript(credentials, container, scriptExportCert); - connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); + connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, errorCode); connData.caCert = - serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); + serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); @@ -58,13 +58,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se return connData; } -QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode) +QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { Q_UNUSED(containerConfig) ConnectionData connData = prepareIkev2Config(credentials, container, errorCode); - if (errorCode && *errorCode) { + if (errorCode != ErrorCode::NoError) { return ""; } diff --git a/client/configurators/ikev2_configurator.h b/client/configurators/ikev2_configurator.h index a7d379f24..53b825250 100644 --- a/client/configurators/ikev2_configurator.h +++ b/client/configurators/ikev2_configurator.h @@ -7,7 +7,7 @@ #include "configurator_base.h" #include "core/defs.h" -class Ikev2Configurator : ConfiguratorBase +class Ikev2Configurator : public ConfiguratorBase { Q_OBJECT public: @@ -21,15 +21,15 @@ public: QString host; // host ip }; - QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); QString genIkev2Config(const ConnectionData &connData); QString genMobileConfig(const ConnectionData &connData); QString genStrongSwanConfig(const ConnectionData &connData); ConnectionData prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode = nullptr); + DockerContainer container, ErrorCode errorCode); }; #endif // IKEV2_CONFIGURATOR_H diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 0cd331f7b..22628ce7f 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -14,9 +14,9 @@ #endif #include "containers/containers_defs.h" +#include "core/controllers/serverController.h" #include "core/scripts_registry.h" #include "core/server_defs.h" -#include "core/controllers/serverController.h" #include "settings.h" #include "utilities.h" @@ -31,59 +31,51 @@ OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QOb OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, - ErrorCode *errorCode) + ErrorCode errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { - if (errorCode) - *errorCode = ErrorCode::OpenSslFailed; + errorCode = ErrorCode::OpenSslFailed; return connData; } QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); ServerController serverController(m_settings); - ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); - if (e) { - if (errorCode) - *errorCode = e; + errorCode = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); + if (errorCode != ErrorCode::NoError) { return connData; } - e = signCert(container, credentials, connData.clientId); - if (e) { - if (errorCode) - *errorCode = e; + errorCode = signCert(container, credentials, connData.clientId); + if (errorCode != ErrorCode::NoError) { return connData; } connData.caCert = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::openvpn::caCertPath, &e); + amnezia::protocols::openvpn::caCertPath, errorCode); connData.clientCert = serverController.getTextFileFromContainer( container, credentials, - QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); + QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode); - if (e) { - if (errorCode) - *errorCode = e; + if (errorCode != ErrorCode::NoError) { return connData; } connData.taKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::openvpn::taKeyPath, &e); + amnezia::protocols::openvpn::taKeyPath, errorCode); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { - if (errorCode) - *errorCode = ErrorCode::SshScpFailureError; + errorCode = ErrorCode::SshScpFailureError; } return connData; } -QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) +QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { ServerController serverController(m_settings); QString config = @@ -91,7 +83,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); - if (errorCode && *errorCode) { + if (errorCode != ErrorCode::NoError) { return ""; } @@ -113,17 +105,20 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia QJsonObject jConfig; jConfig[config_key::config] = config; - clientId = connData.clientId; + jConfig[config_key::clientId] = connData.clientId; return QJsonDocument(jConfig).toJson(); } -QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex) +QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) { - QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); + processConfigWithDnsSettings(dns, protocolConfigString); + + QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); QString config = json[config_key::config].toString(); - if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) { + if (!isApiConfig) { QRegularExpression regex("redirect-gateway.*"); config.replace(regex, ""); @@ -138,9 +133,9 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, // no redirect-gateway } if (m_settings->routeMode() == Settings::VpnAllExceptSites) { - #ifndef Q_OS_ANDROID +#ifndef Q_OS_ANDROID config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); - #endif +#endif // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("block-ipv6\n"); @@ -164,9 +159,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, return QJsonDocument(json).toJson(); } -QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) +QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) { - QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); + processConfigWithDnsSettings(dns, protocolConfigString); + + QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); QString config = json[config_key::config].toString(); QRegularExpression regex("redirect-gateway.*"); diff --git a/client/configurators/openvpn_configurator.h b/client/configurators/openvpn_configurator.h index 424a20e1b..eb99be40d 100644 --- a/client/configurators/openvpn_configurator.h +++ b/client/configurators/openvpn_configurator.h @@ -7,37 +7,37 @@ #include "configurator_base.h" #include "core/defs.h" -class OpenVpnConfigurator : ConfiguratorBase +class OpenVpnConfigurator : public ConfiguratorBase { Q_OBJECT public: OpenVpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - struct ConnectionData { + struct ConnectionData + { QString clientId; - QString request; // certificate request - QString privKey; // client private key + 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 + QString caCert; // server certificate + QString taKey; // tls-auth key + QString host; // host ip }; - QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); - QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex); - QString processConfigWithExportSettings(QString jsonConfig); - - ErrorCode signCert(DockerContainer container, - const ServerCredentials &credentials, QString clientId); + QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); + QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); static ConnectionData createCertRequest(); private: - ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode = nullptr); - + ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, + ErrorCode errorCode); + ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId); }; #endif // OPENVPN_CONFIGURATOR_H diff --git a/client/configurators/shadowsocks_configurator.cpp b/client/configurators/shadowsocks_configurator.cpp index 99e4158c3..28162962c 100644 --- a/client/configurators/shadowsocks_configurator.cpp +++ b/client/configurators/shadowsocks_configurator.cpp @@ -13,18 +13,16 @@ ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr setti } -QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { - ErrorCode e = ErrorCode::NoError; ServerController serverController(m_settings); QString ssKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::shadowsocks::ssKeyPath, &e); + amnezia::protocols::shadowsocks::ssKeyPath, errorCode); ssKey.replace("\n", ""); - if (e) { - if (errorCode) *errorCode = e; + if (errorCode != ErrorCode::NoError) { return ""; } diff --git a/client/configurators/shadowsocks_configurator.h b/client/configurators/shadowsocks_configurator.h index b03149eb2..f67045b32 100644 --- a/client/configurators/shadowsocks_configurator.h +++ b/client/configurators/shadowsocks_configurator.h @@ -6,14 +6,14 @@ #include "configurator_base.h" #include "core/defs.h" -class ShadowSocksConfigurator : ConfiguratorBase +class ShadowSocksConfigurator : public ConfiguratorBase { Q_OBJECT public: ShadowSocksConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); }; #endif // SHADOWSOCKS_CONFIGURATOR_H diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp deleted file mode 100644 index a5dc335d3..000000000 --- a/client/configurators/vpn_configurator.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "vpn_configurator.h" -#include "cloak_configurator.h" -#include "ikev2_configurator.h" -#include "openvpn_configurator.h" -#include "shadowsocks_configurator.h" -#include "ssh_configurator.h" -#include "wireguard_configurator.h" -#include "awg_configurator.h" -#include "xray_configurator.h" -#include -#include -#include - -#include "containers/containers_defs.h" -#include "settings.h" -#include "core/networkUtilities.h" - -VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent) - : ConfiguratorBase(settings, parent) -{ - openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); - shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); - cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, this)); - wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, false, this)); - ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); - sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); - awgConfigurator = std::shared_ptr(new AwgConfigurator(settings, this)); - xrayConfigurator = std::shared_ptr(new XrayConfigurator(settings, this)); -} - -QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode) -{ - switch (proto) { - case Proto::OpenVpn: - return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode); - - case Proto::ShadowSocks: - return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode); - - case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); - - case Proto::WireGuard: - return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode); - - case Proto::Awg: - return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode); - - case Proto::Xray: - return xrayConfigurator->genXrayConfig(credentials, container, containerConfig, clientId, errorCode); - - case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); - - default: return ""; - } -} - -QPair VpnConfigurator::getDnsForConfig(int serverIndex) -{ - QPair dns; - - bool useAmneziaDns = m_settings->useAmneziaDns(); - const QJsonObject &server = m_settings->server(serverIndex); - - dns.first = server.value(config_key::dns1).toString(); - dns.second = server.value(config_key::dns2).toString(); - - if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) { - if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) { - dns.first = protocols::dns::amneziaDnsIp; - } else - dns.first = m_settings->primaryDns(); - } - if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) { - dns.second = m_settings->secondaryDns(); - } - - qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second; - return dns; -} - -QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, - QString &config) -{ - auto dns = getDnsForConfig(serverIndex); - - config.replace("$PRIMARY_DNS", dns.first); - config.replace("$SECONDARY_DNS", dns.second); - - return config; -} - -QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, - QString &config) -{ - processConfigWithDnsSettings(serverIndex, container, proto, config); - - if (proto == Proto::OpenVpn) { - config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex); - } - return config; -} - -QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, - QString &config) -{ - processConfigWithDnsSettings(serverIndex, container, proto, config); - - if (proto == Proto::OpenVpn) { - config = openVpnConfigurator->processConfigWithExportSettings(config); - } - return config; -} - -void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - if (container == DockerContainer::TorWebSite) { - QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); - - qDebug() << "amnezia-tor onions" << stdOut; - - QString onion = stdOut; - onion.replace("\n", ""); - protocol.insert(config_key::site, onion); - - containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol); - } -} diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h deleted file mode 100644 index 453ff6baf..000000000 --- a/client/configurators/vpn_configurator.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef VPN_CONFIGURATOR_H -#define VPN_CONFIGURATOR_H - -#include - -#include "configurator_base.h" -#include "core/defs.h" - -class OpenVpnConfigurator; -class ShadowSocksConfigurator; -class CloakConfigurator; -class WireguardConfigurator; -class Ikev2Configurator; -class SshConfigurator; -class AwgConfigurator; -class XrayConfigurator; - -// Retrieve connection settings from server -class VpnConfigurator : public ConfiguratorBase -{ - Q_OBJECT -public: - explicit VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - - QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, Proto proto, QString &clientId, - ErrorCode *errorCode = nullptr); - - QPair getDnsForConfig(int serverIndex); - QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config); - - QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config); - QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config); - - // workaround for containers which is not support normal configuration - void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut); - - std::shared_ptr openVpnConfigurator; - std::shared_ptr shadowSocksConfigurator; - std::shared_ptr cloakConfigurator; - std::shared_ptr wireguardConfigurator; - std::shared_ptr ikev2Configurator; - std::shared_ptr sshConfigurator; - std::shared_ptr awgConfigurator; - std::shared_ptr xrayConfigurator; - -signals: - void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container, - ServerCredentials credentials); - void clientModelUpdated(); -}; - -#endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 03c9a5b94..9dfd27f94 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -13,23 +13,22 @@ #include #include "containers/containers_defs.h" +#include "core/controllers/serverController.h" #include "core/scripts_registry.h" #include "core/server_defs.h" -#include "core/controllers/serverController.h" #include "settings.h" #include "utilities.h" WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent) : ConfiguratorBase(settings, parent), m_isAwg(isAwg) { - m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath - : amnezia::protocols::wireguard::serverConfigPath; - m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath - : amnezia::protocols::wireguard::serverPublicKeyPath; - m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath - : amnezia::protocols::wireguard::serverPskKeyPath; - m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template - : ProtocolScriptType::wireguard_template; + m_serverConfigPath = + m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; + m_serverPublicKeyPath = + m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath; + m_serverPskKeyPath = + m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; + m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort; @@ -69,19 +68,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode *errorCode) + ErrorCode errorCode) { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { - if (errorCode) - *errorCode = ErrorCode::InternalError; + errorCode = ErrorCode::InternalError; return connData; } - ErrorCode e = ErrorCode::NoError; ServerController serverController(m_settings); // Get list of already created clients (only IP addresses) @@ -94,9 +91,8 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return ErrorCode::NoError; }; - e = serverController.runContainerScript(credentials, container, script, cbReadStdOut); - if (errorCode && e) { - *errorCode = e; + errorCode = serverController.runContainerScript(credentials, container, script, cbReadStdOut); + if (errorCode != ErrorCode::NoError) { return connData; } @@ -110,8 +106,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } else { int next = ips.last().split(".").last().toInt() + 1; if (next > 254) { - if (errorCode) - *errorCode = ErrorCode::AddressPoolError; + errorCode = ErrorCode::AddressPoolError; return connData; } nextIpNumber = QString::number(next); @@ -123,8 +118,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); if (l.isEmpty()) { - if (errorCode) - *errorCode = ErrorCode::AddressPoolError; + errorCode = ErrorCode::AddressPoolError; return connData; } l.removeLast(); @@ -134,20 +128,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e); + connData.serverPubKey = + serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); connData.serverPubKey.replace("\n", ""); - if (e) { - if (errorCode) - *errorCode = e; + if (errorCode != ErrorCode::NoError) { return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode); connData.pskKey.replace("\n", ""); - if (e) { - if (errorCode) - *errorCode = e; + if (errorCode != ErrorCode::NoError) { return connData; } @@ -158,26 +149,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon "AllowedIPs = %3/32\n\n") .arg(connData.clientPubKey, connData.pskKey, connData.clientIP); - e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, - libssh::ScpOverwriteMode::ScpAppendToExisting); + errorCode = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, + libssh::ScpOverwriteMode::ScpAppendToExisting); - if (e) { - if (errorCode) - *errorCode = e; + if (errorCode != ErrorCode::NoError) { return connData; } QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") .arg(m_serverConfigPath); - e = serverController.runScript( + errorCode = serverController.runScript( credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); return connData; } -QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) +QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode) { ServerController serverController(m_settings); QString scriptData = amnezia::scriptData(m_configTemplate, container); @@ -185,7 +174,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede scriptData, serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); - if (errorCode && *errorCode) { + if (errorCode != ErrorCode::NoError) { return ""; } @@ -205,30 +194,25 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede jConfig[config_key::client_pub_key] = connData.clientPubKey; jConfig[config_key::psk_key] = connData.pskKey; jConfig[config_key::server_pub_key] = connData.serverPubKey; - jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu); - clientId = connData.clientPubKey; + jConfig[config_key::clientId] = connData.clientPubKey; return QJsonDocument(jConfig).toJson(); } -QString WireguardConfigurator::processConfigWithLocalSettings(QString config) +QString WireguardConfigurator::processConfigWithLocalSettings(const QPair &dns, + const bool isApiConfig, QString &protocolConfigString) { - // TODO replace DNS if it already set - config.replace("$PRIMARY_DNS", m_settings->primaryDns()); - config.replace("$SECONDARY_DNS", m_settings->secondaryDns()); + processConfigWithDnsSettings(dns, protocolConfigString); - QJsonObject jConfig; - jConfig[config_key::config] = config; - - return QJsonDocument(jConfig).toJson(); + return protocolConfigString; } -QString WireguardConfigurator::processConfigWithExportSettings(QString config) +QString WireguardConfigurator::processConfigWithExportSettings(const QPair &dns, + const bool isApiConfig, QString &protocolConfigString) { - config.replace("$PRIMARY_DNS", m_settings->primaryDns()); - config.replace("$SECONDARY_DNS", m_settings->secondaryDns()); + processConfigWithDnsSettings(dns, protocolConfigString); - return config; + return protocolConfigString; } diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index d24229816..0448a04ed 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -25,18 +25,20 @@ public: QString port; }; - QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode errorCode); - QString processConfigWithLocalSettings(QString config); - QString processConfigWithExportSettings(QString config); + QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); + QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString); static ConnectionData genClientKeys(); private: ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); - + const QJsonObject &containerConfig, ErrorCode errorCode); + bool m_isAwg; QString m_serverConfigPath; QString m_serverPublicKeyPath; diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index f450527fe..44a045615 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -1,43 +1,36 @@ #include "xray_configurator.h" #include -#include #include +#include -#include "core/scripts_registry.h" #include "containers/containers_defs.h" #include "core/controllers/serverController.h" +#include "core/scripts_registry.h" -XrayConfigurator::XrayConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +XrayConfigurator::XrayConfigurator(std::shared_ptr settings, QObject *parent) : ConfiguratorBase(settings, parent) { - } -QString XrayConfigurator::genXrayConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) +QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, + ErrorCode errorCode) { - ErrorCode e = ErrorCode::NoError; ServerController serverController(m_settings); - QString config = - serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); - QString xrayPublicKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::xray::PublicKeyPath, &e); + QString xrayPublicKey = + serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); xrayPublicKey.replace("\n", ""); - QString xrayUuid = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::xray::uuidPath, &e); + QString xrayUuid = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode); xrayUuid.replace("\n", ""); - QString xrayShortId = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::xray::shortidPath, &e); + QString xrayShortId = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); xrayShortId.replace("\n", ""); - if (e) { - if (errorCode) *errorCode = e; + if (errorCode != ErrorCode::NoError) { return ""; } diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h index cefdc9c24..746a5762b 100644 --- a/client/configurators/xray_configurator.h +++ b/client/configurators/xray_configurator.h @@ -6,14 +6,14 @@ #include "configurator_base.h" #include "core/defs.h" -class XrayConfigurator : ConfiguratorBase +class XrayConfigurator : public ConfiguratorBase { Q_OBJECT public: XrayConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - QString genXrayConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); + QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, + ErrorCode errorCode); }; #endif // XRAY_CONFIGURATOR_H diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 2ed12d19f..0dcaa31a3 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -1,5 +1,8 @@ #include "containers_defs.h" +#include "QJsonObject" +#include "QJsonDocument" + QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) { QDebugStateSaver saver(debug); @@ -363,3 +366,13 @@ bool ContainerProps::isShareable(DockerContainer container) default: return true; } } + +QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) +{ + QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol)) + .toObject() + .value(config_key::last_config) + .toString(); + + return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index fa10deb48..ac6e92016 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -68,6 +68,8 @@ namespace amnezia static int easySetupOrder(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container); + + static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig); }; static void declareQmlContainerEnum() diff --git a/client/ui/controllers/apiController.cpp b/client/core/controllers/apiController.cpp similarity index 74% rename from client/ui/controllers/apiController.cpp rename to client/core/controllers/apiController.cpp index f9df5f4b6..4114c4683 100644 --- a/client/ui/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -5,9 +5,8 @@ #include #include -#include "configurators/openvpn_configurator.h" -#include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" +#include "configurators/wireguard_configurator.h" namespace { @@ -24,9 +23,7 @@ namespace } } -ApiController::ApiController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel) +ApiController::ApiController(QObject *parent) : QObject(parent) { } @@ -67,21 +64,14 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont return obj; } -void ApiController::updateServerConfigFromApi() +ErrorCode ApiController::updateServerConfigFromApi(QJsonObject &serverConfig) { - QtConcurrent::run([this]() { - if (m_isConfigUpdateStarted) { - emit updateFinished(false); - return; - } + QFutureWatcher watcher; - auto serverConfig = m_serversModel->getDefaultServerConfig(); + QFuture future = QtConcurrent::run([this, &serverConfig]() { auto containerConfig = serverConfig.value(config_key::containers).toArray(); - if (serverConfig.value(config_key::configVersion).toInt() && containerConfig.isEmpty()) { - emit updateStarted(); - m_isConfigUpdateStarted = true; - + if (serverConfig.value(config_key::configVersion).toInt()) { QNetworkAccessManager manager; QNetworkRequest request; @@ -114,9 +104,7 @@ void ApiController::updateServerConfigFromApi() QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); if (ba.isEmpty()) { - emit errorOccurred(errorString(ApiConfigDownloadError)); - m_isConfigUpdateStarted = false; - return; + return ErrorCode::ApiConfigDownloadError; } QByteArray ba_uncompressed = qUncompress(ba); @@ -136,35 +124,22 @@ void ApiController::updateServerConfigFromApi() auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString(); serverConfig.insert(config_key::defaultContainer, defaultContainer); - m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); } else { QString err = reply->errorString(); qDebug() << QString::fromUtf8(reply->readAll()); qDebug() << reply->error(); qDebug() << err; qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - emit errorOccurred(errorString(ApiConfigDownloadError)); - m_isConfigUpdateStarted = false; - return; + return ErrorCode::ApiConfigDownloadError; } } - - emit updateFinished(m_isConfigUpdateStarted); - m_isConfigUpdateStarted = false; - return; + return ErrorCode::NoError; }); -} - -void ApiController::clearApiConfig() -{ - auto serverConfig = m_serversModel->getDefaultServerConfig(); - - serverConfig.remove(config_key::dns1); - serverConfig.remove(config_key::dns2); - serverConfig.remove(config_key::containers); - serverConfig.remove(config_key::hostName); - - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); - - m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + return watcher.result(); } diff --git a/client/ui/controllers/apiController.h b/client/core/controllers/apiController.h similarity index 53% rename from client/ui/controllers/apiController.h rename to client/core/controllers/apiController.h index 2a1393c42..430572c56 100644 --- a/client/ui/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -4,26 +4,16 @@ #include #include "configurators/openvpn_configurator.h" -#include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" class ApiController : public QObject { Q_OBJECT public: - explicit ApiController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, QObject *parent = nullptr); + explicit ApiController(QObject *parent = nullptr); public slots: - void updateServerConfigFromApi(); - - void clearApiConfig(); - -signals: - void updateStarted(); - void updateFinished(bool isConfigUpdateStarted); - void errorOccurred(const QString &errorMessage); + ErrorCode updateServerConfigFromApi(QJsonObject &serverConfig); private: struct ApiPayloadData { @@ -36,11 +26,6 @@ private: ApiPayloadData generateApiPayloadData(const QString &protocol); QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData); void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config); - - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - - bool m_isConfigUpdateStarted = false; }; #endif // APICONTROLLER_H diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 88d8cbdc7..163c3d2b0 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -29,8 +29,7 @@ #include "core/networkUtilities.h" #include "settings.h" #include "utilities.h" - -#include +#include "vpnConfigurationController.h" namespace { @@ -179,11 +178,10 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, } QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, - const QString &path, ErrorCode *errorCode) + const QString &path, ErrorCode errorCode) { - if (errorCode) - *errorCode = ErrorCode::NoError; + errorCode = ErrorCode::NoError; QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"") .arg(ContainerProps::containerToString(container)) @@ -195,7 +193,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, return ErrorCode::NoError; }; - *errorCode = runScript(credentials, script, cbReadStdOut); + errorCode = runScript(credentials, script, cbReadStdOut); return QByteArray::fromHex(stdOut.toUtf8()); } @@ -498,7 +496,7 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr genVarsForScript(credentials, container, config)), cbReadStdOut, cbReadStdErr); - m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut); + VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut); return e; } @@ -662,7 +660,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential return vars; } -QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode) +QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -674,11 +672,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential return ErrorCode::NoError; }; - ErrorCode e = - runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); - - if (errorCode) - *errorCode = e; + errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); return stdOut; } @@ -839,147 +833,6 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential return future.result(); } -ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, - QMap &installedContainers) -{ - 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; - }; - - QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'"); - - ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - auto containersInfo = stdOut.split("\n"); - for (auto &containerInfo : containersInfo) { - if (containerInfo.isEmpty()) { - continue; - } - const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*"); - QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo); - if (containerAndPortMatch.hasMatch()) { - QString name = containerAndPortMatch.captured(1); - QString port = containerAndPortMatch.captured(2); - QString transportProto = containerAndPortMatch.captured(3); - DockerContainer container = ContainerProps::containerFromString(name); - - QJsonObject config; - Proto mainProto = ContainerProps::defaultProtocol(container); - for (auto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject containerConfig; - if (protocol == mainProto) { - containerConfig.insert(config_key::port, port); - containerConfig.insert(config_key::transport_proto, transportProto); - - if (protocol == Proto::Awg) { - QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode); - - QMap serverConfigMap; - auto serverConfigLines = serverConfig.split("\n"); - for (auto &line : serverConfigLines) { - auto trimmedLine = line.trimmed(); - if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { - continue; - } else { - QStringList parts = trimmedLine.split(" = "); - if (parts.count() == 2) { - serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); - } - } - } - - containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); - containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); - containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); - containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); - containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); - containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); - containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); - containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); - containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); - } else if (protocol == Proto::Sftp) { - stdOut.clear(); - script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name); - - ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - auto sftpInfo = stdOut.split(":"); - if (sftpInfo.size() < 2) { - logger.error() << "Key parameters for the sftp container are missing"; - continue; - } - auto userName = sftpInfo.at(0); - userName = userName.remove(0, 1); - auto password = sftpInfo.at(1); - - containerConfig.insert(config_key::userName, userName); - containerConfig.insert(config_key::password, password); - } - - config.insert(config_key::container, ContainerProps::containerToString(container)); - } - config.insert(ProtocolProps::protoToString(protocol), containerConfig); - } - installedContainers.insert(container, config); - } - const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*"); - QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo); - if (torOrDnsRegMatch.hasMatch()) { - QString name = torOrDnsRegMatch.captured(1); - QString port = torOrDnsRegMatch.captured(2); - QString transportProto = torOrDnsRegMatch.captured(3); - DockerContainer container = ContainerProps::containerFromString(name); - - QJsonObject config; - Proto mainProto = ContainerProps::defaultProtocol(container); - for (auto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject containerConfig; - if (protocol == mainProto) { - containerConfig.insert(config_key::port, port); - containerConfig.insert(config_key::transport_proto, transportProto); - - if (protocol == Proto::TorWebSite) { - stdOut.clear(); - script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name); - - ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (stdOut.isEmpty()) { - logger.error() << "Key parameters for the tor container are missing"; - continue; - } - - QString onion = stdOut; - onion.replace("\n", ""); - containerConfig.insert(config_key::site, onion); - } - - config.insert(config_key::container, ContainerProps::containerToString(container)); - } - config.insert(ProtocolProps::protoToString(protocol), containerConfig); - } - installedContainers.insert(container, config); - } - } - - return ErrorCode::NoError; -} - ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback) { diff --git a/client/core/controllers/serverController.h b/client/core/controllers/serverController.h index 7caad3667..3df3bdca5 100644 --- a/client/core/controllers/serverController.h +++ b/client/core/controllers/serverController.h @@ -30,9 +30,6 @@ public: ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig); - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, - QMap &installedContainers); - ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); @@ -40,7 +37,7 @@ public: DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, - const QString &path, ErrorCode *errorCode = nullptr); + const QString &path, ErrorCode errorCode); QString replaceVars(const QString &script, const Vars &vars); Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, @@ -55,7 +52,7 @@ public: const std::function &cbReadStdOut = nullptr, const std::function &cbReadStdErr = nullptr); - QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); + QString checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode); void cancelInstallation(); diff --git a/client/core/controllers/vpnConfigurationController.cpp b/client/core/controllers/vpnConfigurationController.cpp new file mode 100644 index 000000000..ba2106694 --- /dev/null +++ b/client/core/controllers/vpnConfigurationController.cpp @@ -0,0 +1,137 @@ +#include "vpnConfigurationController.h" + +#include "configurators/awg_configurator.h" +#include "configurators/cloak_configurator.h" +#include "configurators/ikev2_configurator.h" +#include "configurators/openvpn_configurator.h" +#include "configurators/shadowsocks_configurator.h" +#include "configurators/wireguard_configurator.h" +#include "configurators/xray_configurator.h" + +VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr &settings, QObject *parent) + : QObject { parent }, m_settings(settings) +{ +} + +QScopedPointer VpnConfigurationsController::createConfigurator(const Proto protocol) +{ + switch (protocol) { + case Proto::OpenVpn: return QScopedPointer(new OpenVpnConfigurator(m_settings)); + case Proto::ShadowSocks: return QScopedPointer(new ShadowSocksConfigurator(m_settings)); + case Proto::Cloak: return QScopedPointer(new CloakConfigurator(m_settings)); + case Proto::WireGuard: return QScopedPointer(new WireguardConfigurator(m_settings, false)); + case Proto::Awg: return QScopedPointer(new AwgConfigurator(m_settings)); + case Proto::Ikev2: return QScopedPointer(new Ikev2Configurator(m_settings)); + case Proto::Xray: return QScopedPointer(new XrayConfigurator(m_settings)); + default: return QScopedPointer(); + } +} + +ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials, + const DockerContainer container, QJsonObject &containerConfig) +{ + ErrorCode errorCode = ErrorCode::NoError; + + if (ContainerProps::containerService(container) == ServiceType::Other) { + return errorCode; + } + + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject(); + + auto configurator = createConfigurator(protocol); + QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + protocolConfig.insert(config_key::last_config, protocolConfigString); + containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); + } + + return errorCode; +} + +ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair &dns, + const ServerCredentials &credentials, const DockerContainer container, + const QJsonObject &containerConfig, const Proto protocol, + QString &protocolConfigString) +{ + ErrorCode errorCode = ErrorCode::NoError; + + if (ContainerProps::containerService(container) == ServiceType::Other) { + return errorCode; + } + + auto configurator = createConfigurator(protocol); + + protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString); + + return errorCode; +} + +QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, + const QJsonObject &containerConfig, const DockerContainer container, + ErrorCode errorCode) +{ + QJsonObject vpnConfiguration {}; + + if (ContainerProps::containerService(container) == ServiceType::Other) { + return vpnConfiguration; + } + + bool isApiConfig = serverConfig.value(config_key::configVersion).toInt(); + + for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { + if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) { + continue; + } + + QString protocolConfigString = + containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); + + auto configurator = createConfigurator(proto); + protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString); + + QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); + vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); + vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData); + } + + Proto proto = ContainerProps::defaultProtocol(container); + vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto); + + vpnConfiguration[config_key::dns1] = dns.first; + vpnConfiguration[config_key::dns2] = dns.second; + + vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString(); + vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString(); + + vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt(); + // TODO: try to get hostName, port, description for 3rd party configs + // vpnConfiguration[config_key::port] = ...; + + return vpnConfiguration; +} + +void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, + const QString &stdOut) +{ + Proto mainProto = ContainerProps::defaultProtocol(container); + + if (container == DockerContainer::TorWebSite) { + QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); + + qDebug() << "amnezia-tor onions" << stdOut; + + QString onion = stdOut; + onion.replace("\n", ""); + protocol.insert(config_key::site, onion); + + containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol); + } +} diff --git a/client/core/controllers/vpnConfigurationController.h b/client/core/controllers/vpnConfigurationController.h new file mode 100644 index 000000000..ac0b0ac5e --- /dev/null +++ b/client/core/controllers/vpnConfigurationController.h @@ -0,0 +1,35 @@ +#ifndef VPNCONFIGIRATIONSCONTROLLER_H +#define VPNCONFIGIRATIONSCONTROLLER_H + +#include + +#include "configurators/configurator_base.h" +#include "containers/containers_defs.h" +#include "core/defs.h" +#include "settings.h" + +class VpnConfigurationsController : public QObject +{ + Q_OBJECT +public: + explicit VpnConfigurationsController(const std::shared_ptr &settings, QObject *parent = nullptr); + +public slots: + ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container, + QJsonObject &containerConfig); + ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair &dns, const ServerCredentials &credentials, + const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol, + QString &protocolConfigString); + QJsonObject createVpnConfiguration(const QPair &dns, const QJsonObject &serverConfig, + const QJsonObject &containerConfig, const DockerContainer container, ErrorCode errorCode); + + static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut); +signals: + +private: + QScopedPointer createConfigurator(const Proto protocol); + + std::shared_ptr m_settings; +}; + +#endif // VPNCONFIGIRATIONSCONTROLLER_H diff --git a/client/ios/app/PrivacyInfo.xcprivacy b/client/ios/app/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..24e32f370 --- /dev/null +++ b/client/ios/app/PrivacyInfo.xcprivacy @@ -0,0 +1,33 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + + diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 4d695dfac..80f3f1f13 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -90,6 +90,14 @@ target_sources(networkextension PRIVATE ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm ) +target_sources(networkextension PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy +) + +set_property(TARGET networkextension APPEND PROPERTY RESOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy +) + ## Build wireguard-go-version.h execute_process( COMMAND go list -m golang.zx2c4.com/wireguard diff --git a/client/ios/networkextension/PrivacyInfo.xcprivacy b/client/ios/networkextension/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..380e0b7b5 --- /dev/null +++ b/client/ios/networkextension/PrivacyInfo.xcprivacy @@ -0,0 +1,25 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 0800acc8a..f2d85b021 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -424,6 +424,8 @@ bool IosController::setupCloak() openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu); } + openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]); + QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray(); for(int index = 0; index < splitTunnelSites.count(); index++) { diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 8cf7cfd2c..feee11f3c 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -94,6 +94,8 @@ namespace amnezia constexpr char crc[] = "crc"; + constexpr char clientId[] = "clientId"; + } namespace protocols diff --git a/client/settings.cpp b/client/settings.cpp index 3523bcfed..2a083b329 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -256,6 +256,16 @@ Settings::RouteMode Settings::routeMode() const return static_cast(value("Conf/routeMode", 0).toInt()); } +bool Settings::getSitesSplitTunnelingEnabled() const +{ + return value("Conf/sitesSplitTunnelingEnabled", false).toBool(); +} + +void Settings::setSitesSplitTunnelingEnabled(bool enabled) +{ + setValue("Conf/sitesSplitTunnelingEnabled", enabled); +} + bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); @@ -403,6 +413,16 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector &a m_settings.sync(); } +bool Settings::getAppsSplitTunnelingEnabled() const +{ + return value("Conf/appsSplitTunnelingEnabled", false).toBool(); +} + +void Settings::setAppsSplitTunnelingEnabled(bool enabled) +{ + setValue("Conf/appsSplitTunnelingEnabled", enabled); +} + ServerCredentials Settings::defaultServerCredentials() const { return serverCredentials(defaultServerIndex()); diff --git a/client/settings.h b/client/settings.h index 59c6b13ca..7eb650dbb 100644 --- a/client/settings.h +++ b/client/settings.h @@ -115,6 +115,9 @@ public: RouteMode routeMode() const; void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); } + bool getSitesSplitTunnelingEnabled() const; + void setSitesSplitTunnelingEnabled(bool enabled); + QVariantMap vpnSites(RouteMode mode) const { return value("Conf/" + routeModeString(mode)).toMap(); @@ -208,6 +211,9 @@ public: QVector getVpnApps(AppsRouteMode mode) const; void setVpnApps(AppsRouteMode mode, const QVector &apps); + bool getAppsSplitTunnelingEnabled() const; + void setAppsSplitTunnelingEnabled(bool enabled); + signals: void saveLogsChanged(bool enabled); void screenshotsEnabledChanged(bool enabled); diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 9138b14bb..4302873f1 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -2906,7 +2906,7 @@ While it offers a blend of security, stability, and speed, it's essential t Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. -* Доступно в AmneziaVPN только для Windows. +* Доступен в AmneziaVPN только для Windows * Низкое энергопотребление, на мобильных устройствах * Минимальная конфигурация * Распознается системами DPI-анализа @@ -2974,9 +2974,9 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Recognised by DPI analysis systems and therefore susceptible to blocking * Can operate over both TCP and UDP network protocols. OpenVPN однин из самых популярных и проверенных временем VPN-протоколов. -В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, поддержка OpenVPN множества методов аутентификации делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. +В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, OpenVPN поддерживает множество методов аутентификации, что делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. -* Доступность AmneziaVPN для всех платформ +* Доступен в AmneziaVPN для всех платформ * Нормальное энергопотребление на мобильных устройствах * Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами * Распознается системами DPI-анализа и поэтому подвержен блокировке @@ -2994,7 +2994,7 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Works over TCP network protocol. Shadowsocks, создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению. Однако некоторые системы анализа трафика все же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG, или OpenVPN over Cloak. -* Доступен в AmneziaVPN только на ПК ноутбуках. +* Доступен в AmneziaVPN только для ПК и ноутбуков * Настраиваемый протокол шифрования * Обнаруживается некоторыми DPI-системами * Работает по сетевому протоколу TCP. @@ -3037,19 +3037,19 @@ If there is a extreme level of Internet censorship in your region, we advise you * Not recognised by DPI analysis systems * Works over TCP network protocol, 443 port. - OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от блокировок. + OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от обнаружения и блокировок. -OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. +Протокол OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. -Cloak защищает OpenVPN от обнаружения и блокировок. +Плагин Cloak защищает OpenVPN от обнаружения и блокировок. Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. -Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak +Если в вашем регионе экстремальный уровень цензуры в Интернете, мы советуем вам с первого подключения использовать только OpenVPN over Cloak -* Доступность AmneziaVPN на всех платформах +* Доступен в AmneziaVPN для всех платформ * Высокое энергопотребление на мобильных устройствах * Гибкие настройки * Не распознается системами DPI-анализа @@ -3070,7 +3070,7 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. -* Доступность AmneziaVPN для всех платформ +* Доступен в AmneziaVPN для всех платформ * Низкое энергопотребление * Минимальное количество настроек * Легко распознается системами DPI-анализа, подвержен блокировке @@ -3091,7 +3091,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. Таким образом, AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. -* Доступность AmneziaVPN на всех платформах +* Доступен в AmneziaVPN для всех платформ * Низкое энергопотребление * Минимальное количество настроек * Не распознается системами DPI-анализа, устойчив к блокировке diff --git a/client/translations/amneziavpn_uk_UA.ts b/client/translations/amneziavpn_uk_UA.ts new file mode 100644 index 000000000..0bdaf8239 --- /dev/null +++ b/client/translations/amneziavpn_uk_UA.ts @@ -0,0 +1,3687 @@ + + + + + AmneziaApplication + + Split tunneling for WireGuard is not implemented, the option was disabled + Роздільне тунелювання для "Wireguard" не реалізовано, опцію вимкнено + + + Split tunneling for %1 is not implemented, the option was disabled + Роздільне тунелювання для %1 не реалізовано, опцію вимкнено + + + + AndroidController + + AmneziaVPN + AmneziaVPN + + + VPN Connected + Refers to the app - which is currently running the background and waiting + VPN Підключено + + + + AppSplitTunnelingController + + + Application added: %1 + + + + + The application has already been added + + + + + The selected applications have been added + + + + + Application removed: %1 + + + + + ConnectButton + + + Unable to disconnect during configuration preparation + + + + + ConnectionController + + + VPN Protocols is not installed. + Please install VPN container at first + VPN протоколи не встановлено. + Будь-ласка, встановіть VPN контейнер + + + + unable to create configuration + + + + + Connecting... + Підключення... + + + + Connected + Підключено + + + + Preparing... + + + + + Settings updated successfully, Reconnnection... + Налаштування оновлено, Підключення... + + + + Settings updated successfully + Налаштування оновлено. + + + + Reconnecting... + Перепідключення... + + + + + + + Connect + Підключитись + + + + Disconnecting... + Відключаємось... + + + + ConnectionTypeSelectionDrawer + + + Add new connection + Додати нове з'єднання + + + + Configure your server + Налаштувати свій сервер + + + + Open config file, key or QR code + Відкрити файл конфігурації, ключ або QR код + + + + ContextMenuType + + + C&ut + &Вирізати + + + + &Copy + &Копіювати + + + + &Paste + &Вставити + + + + &SelectAll + &Вибрати все + + + + ExportController + + + Access error! + Помилка доступу! + + + + HomeContainersListView + + + Unable change protocol while there is an active connection + Неможливо змінити протокол при активному підключенні + + + + The selected protocol is not supported on the current platform + Вибраний протокол не підтримується на цьому пристрої + + + Reconnect via VPN Procotol: + Перепідлючення VPN протоколом: + + + + HomeSplitTunnelingDrawer + + + Split tunneling + Роздільне тунелювання + + + + Allows you to connect to some sites or applications through a VPN connection and bypass others + Дозволяє підключатись до одних сайтів та застосунків через захищене з'єднання, а іншим в обхід нього + + + + Split tunneling on the server + Роздільне тунелювання на сервері + + + + Enabled +Can't be disabled for current server + Увімкнено. +Не може бути вимкнено для даного сервера. + + + + Site-based split tunneling + Роздільне тунелювання по сайтам + + + + + Enabled + Увімкнено + + + + + Disabled + Вимкнено + + + + App-based split tunneling + Роздільне тунелювання застосунків + + + + ImportController + + + Unable to open file + + + + + + Invalid configuration file + + + + + Scanned %1 of %2. + Відскановано %1 з %2. + + + + InstallController + + + %1 installed successfully. + %1 встановлено. + + + + %1 is already installed on the server. + %1 вже встановлено на сервері. + + + + +Added containers that were already installed on the server + Додані сервіси і протоколи, які були раніше встановлені на сервері + + + + +Already installed containers were found on the server. All installed containers have been added to the application + +На сервері знайдені сервіси та протоколи, всі вони додані в застосунок + + + + Settings updated successfully + Налаштування оновлено + + + + Server '%1' was rebooted + Сервер '%1' перезавантажено + + + + Server '%1' was removed + Сервер '%1' був видалений + + + + All containers from server '%1' have been removed + Всі сервіси та протоколи були видалені з сервера '%1' + + + + %1 has been removed from the server '%2' + %1 був видалений з сервера '%2' + + + + %1 cached profile cleared + + + + + Please login as the user + Буль-ласка, увійдіть в систему від імені користувача + + + + Server added successfully + Сервер додано + + + + InstalledAppsDrawer + + + Choose application + + + + + Add selected + + + + + KeyChainClass + + + Read key failed: %1 + Не вдалося зчитати ключ: %1 + + + + Write key failed: %1 + Не вдалося записати ключ: %1 + + + + Delete key failed: %1 + Не вдалося видалити ключ: %1 + + + + NotificationHandler + + + + AmneziaVPN + AmneziaVPN + + + + VPN Connected + VPN Підключено + + + + VPN Disconnected + VPN Вимкнено + + + + AmneziaVPN notification + Сповіщення AmneziaVPN + + + + Unsecured network detected: + Знайдена не захищена мережа: + + + + PageDeinstalling + + + Removing services from %1 + Видалення сервісів з %1 + + + + Usually it takes no more than 5 minutes + Зазвичай, це займає не більше 5 хвилин + + + + PageHome + + + Logging enabled + + + + + Split tunneling enabled + Роздільне тунелювання увімкнено + + + + Split tunneling disabled + Роздільне тунелювання вимкнено + + + + VPN protocol + VPN протокол + + + + Servers + Сервери + + + + Unable change server while there is an active connection + Не можна змінити сервер при активному підключенні + + + + PageProtocolAwgSettings + + + AmneziaWG settings + налаштування AmneziaWG + + + + Port + Порт + + + + MTU + + + + + Save + Зберегти + + + + Save settings? + + + + + All users with whom you shared a connection with will no longer be able to connect to it. + + + + Remove AmneziaWG + Видалити AmneziaWG + + + Remove AmneziaWG from server? + Видалити AmneziaWG з серверу? + + + All users with whom you shared a connection will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + All users who you shared a connection with will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + Save and Restart Amnezia + Зберегти і перезапустити Amnezia + + + + PageProtocolCloakSettings + + + Cloak settings + Налаштування Cloak + + + + Disguised as traffic from + Замаскувати трафік під + + + + Port + Порт + + + + + Cipher + Шифрування + + + + Save + Зберегти + + + Save and Restart Amnezia + Зберегти і перезавантажити Amnezia + + + + PageProtocolOpenVpnSettings + + + OpenVPN settings + налаштування OpenVPN + + + VPN Addresses Subnet + Підмережа для VPN + + + + VPN address subnet + + + + + Network protocol + Мережевий притокол + + + + Port + Порт + + + + Auto-negotiate encryption + Автоматично отримувати шифрування + + + + + Hash + Хеш + + + + SHA512 + SHA512 + + + + SHA384 + SHA384 + + + + SHA256 + SHA256 + + + + SHA3-512 + SHA3-512 + + + + SHA3-384 + SHA3-384 + + + + SHA3-256 + SHA3-256 + + + + whirlpool + whirlpool + + + + BLAKE2b512 + BLAKE2b512 + + + + BLAKE2s256 + BLAKE2s256 + + + + SHA1 + SHA1 + + + + + Cipher + Шифрування + + + + AES-256-GCM + AES-256-GCM + + + + AES-192-GCM + AES-192-GCM + + + + AES-128-GCM + AES-128-GCM + + + + AES-256-CBC + AES-256-CBC + + + + AES-192-CBC + AES-192-CBC + + + + AES-128-CBC + AES-128-CBC + + + + ChaCha20-Poly1305 + ChaCha20-Poly1305 + + + + ARIA-256-CBC + ARIA-256-CBC + + + + CAMELLIA-256-CBC + CAMELLIA-256-CBC + + + + none + none + + + + TLS auth + TLS авторизація + + + + Block DNS requests outside of VPN + Блокувати DNS запити за межами VPN тунеля + + + + Additional client configuration commands + Додаткові команди конфігурації клієнта + + + + + Commands: + Команди: + + + + Additional server configuration commands + Додаткові команти конфігурації сервера + + + + Save + Зберегти + + + Remove OpenVPN + Видалити OpenVPN + + + Remove OpenVpn from server? + Видалити OpenVPN з серверу? + + + All users with whom you shared a connection will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + All users who you shared a connection with will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + Continue + Продовжити + + + Cancel + Відмінити + + + Save and Restart Amnezia + Зберегти и перезапустити Amnezia + + + + PageProtocolRaw + + + settings + налаштування + + + + Show connection options + Показати параметри підключення + + + + Connection options %1 + Параметри підключення %1 + + + + Remove + Видалити + + + + Remove %1 from server? + Видалити %1 з сервера? + + + + All users with whom you shared a connection with will no longer be able to connect to it. + + + + All users with whom you shared a connection will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + All users who you shared a connection with will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings + Налаштування ShadowSocks + + + + Port + Порт + + + + + Cipher + Шифрування + + + + Save + Зберегти + + + Save and Restart Amnezia + Зберегти і перезавантажити + + + + PageProtocolWireGuardSettings + + + WG settings + + + + + Port + Порт + + + + MTU + + + + + Save + Зберегти + + + + PageProtocolXraySettings + + + XRay settings + + + + + Disguised as traffic from + Замаскувати трафік під + + + + Save + Зберегти + + + + PageServerContainers + + Continue + Продовжити + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + На вашому сервері встановлено DNS-сервіс, доступ до нього можливо тільки через VPN. + + + + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + Адреса DNS сервера співпадає з адресою вашого сервера. Налаштувати DNS можливо на вкладці "Підключення" налаштувань застосунку + + + + Remove + Видалити + + + + Remove %1 from server? + Видалити %1 з сервера? + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageServiceSftpSettings + + + Settings updated successfully + Налаштування оновлено + + + + SFTP settings + Налаштування SFTP + + + + Host + Хост + + + + + + + Copied + Скопійовано + + + + Port + Порт + + + Login + Логін + + + + User name + Імя користувача + + + + Password + Пароль + + + + Mount folder on device + Змонтувати папку з вашого пристрою + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + Для того щоб додати SFTP-папку, як локальний диск на вашому пристрої, виконайте наступні дії: <br> + + + + + <br>1. Install the latest version of + <br>1. Встановіть останню версію + + + + + <br>2. Install the latest version of + <br>2. Встановіть останню версію + + + + Detailed instructions + Детальні інструкції + + + + Remove SFTP and all data stored there + Видалити SFTP-сховище з усіма даними + + + + Remove SFTP and all data stored there? + Видалити SFTP-сховище з усіма даними які там зберігаються? + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + Налаштування оновлено + + + + Tor website settings + Налаштування сайту в мережі Тоr + + + + Website address + Адреса сайту + + + + Copied + Скопійовано + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL. + Використовуйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для відкриття цього посилання. + + + + After creating your onion site, it takes a few minutes for the Tor network to make it available for use. + Через кілька хвилин після встановлення ваш сайт Onion стане доступним у мережі Tor. + + + + When configuring WordPress set the this onion address as domain. + При налаштуванні WordPress, вкажіть цей Onion в якості домена. + + + When configuring WordPress set the this address as domain. + При налаштуванні WordPress, вкажіть цей Onion в якості домена. + + + + Remove website + Видалити сайт + + + + The site with all data will be removed from the tor network. + Сайт з усіма даними буде видалено з мережі Tor. + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageSettings + + + Settings + Налаштування + + + + Servers + Сервери + + + + Connection + Підключення + + + + Application + Застосунок + + + + Backup + Резервне копіювання + + + + About AmneziaVPN + Про AmneziaVPN + + + + Close application + Закрити застосунок + + + + PageSettingsAbout + + Support the project with a donation + Підтримайте проект донатом + + + + Support Amnezia + Підтримайте Amnezia + + + This is a free and open source application. If you like it, support the developers with a donation. + Це безкоштовний застосунок з відкритим кодом. Якщо він вам подобається - підтримайте розробників донатом. + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + І якщо він вам не подобається, то ще один привід підтримати його – донат буде використана для вдосконалення застосунку. + + + Card on Patreon + Карткою на Patreon + + + https://www.patreon.com/amneziavpn + https://www.patreon.com/amneziavpn + + + Show other methods on Github + Показати інші способи на Github + + + + Amnezia is a free and open-source application. You can support the developers if you like it. + + + + + Contacts + Контакти + + + + Telegram group + Група в Telegram + + + + To discuss features + Для дискусій + + + + https://t.me/amnezia_vpn_en + https://t.me/amnezia_vpn + + + + Mail + Пошта + + + + For reviews and bug reports + Для відгуків і повідомлень про помилки + + + + Github + Github + + + + https://github.com/amnezia-vpn/amnezia-client + https://github.com/amnezia-vpn/amnezia-client + + + + Website + Веб-сайт + + + + https://amnezia.org + https://amnezia.org + + + + Software version: %1 + Версія ПЗ: %1 + + + + Check for updates + Перевірити оновлення + + + + Privacy Policy + + + + + PageSettingsAppSplitTunneling + + + Only the Apps listed here will be accessed through the VPN + + + + + Apps from the list should not be accessed via VPN + + + + + App split tunneling + + + + + Mode + Режим + + + + Remove + Видалити + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + application name + + + + + Open executable file + + + + + Executable file (*.*) + + + + + PageSettingsApplication + + + Application + Застосунок + + + + Allow application screenshots + Дозволити скріншоти в застосунку + + + + Auto start + Автозапуск + + + + Launch the application every time the device is starts + Запускати застосунок при старті + + + + Auto connect + Автопідключення + + + + Connect to VPN on app start + Підключення до VPN при старті застосунку + + + + Start minimized + Запускати в згорнутому вигляді + + + + Launch application minimized + Запускати застосунок в згорнутому вигляді + + + + Language + Мова + + + + Logging + Логування + + + + Enabled + Увімкнено + + + + Disabled + Вимкнено + + + + Reset settings and remove all data from the application + Скинути налаштування і видалити всі дані із застосунку + + + + Reset settings and remove all data from the application? + Скинути налаштування і видалити всі дані із застосунку? + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + Всі дані із застосунку будуть видалені, всі встановлені сервіси AmneziaVPN залишаться на сервері. + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageSettingsBackup + + Backup + Резервне копіювання + + + + Settings restored from backup file + Відновлення налаштувань із бекап файлу + + + Configuration backup + Бекап конфігурація + + + + Back up your configuration + + + + + You can save your settings to a backup file to restore them the next time you install the application. + Ви можете зберегти свої налаштування у бекап файл (резервну копію), щоб відновити їх під час наступного встановлення програми + + + + The backup will contain your passwords and private keys for all servers added to AmneziaVPN. Keep this information in a secure place. + + + + + Make a backup + Зробити бекап (резервну копію) + + + + Save backup file + Зберегти бекап файл + + + + + Backup files (*.backup) + Файли резервної копії (*.backup) + + + + Backup file saved + Бекап файл збережено + + + + Restore from backup + Відновити із бекапа + + + + Open backup file + Відкрити бекап файл + + + + Import settings from a backup file? + Імпортувати налаштування із бекап файлу? + + + + All current settings will be reset + Всі поточні налаштування будуть скинуті + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + PageSettingsConnection + + + Connection + З'єднання + + + Auto connect + Автопідключення + + + Connect to VPN on app start + Підключення до VPN при старті застосунку + + + + Use AmneziaDNS + Використовувати Amnezia DNS + + + + If AmneziaDNS is installed on the server + Якщо він встановлений на сервері + + + + DNS servers + DNS сервер + + + + When AmneziaDNS is not used or installed + Ці адреси будуть використовуватись коли вимкнений AmneziaDNS + + + + Allows you to use the VPN only for certain Apps + Дозволяє використовувати VPN тільки для вибраних застосунків + + + + Site-based split tunneling + Роздільне тунелювання сайтів + + + + Allows you to select which sites you want to access through the VPN + Дозволяє доступ до одних сайтів через VPN, а для інших в обхід VPN + + + + App-based split tunneling + Роздільне VPN-тунелювання застосунків + + + Allows you to use the VPN only for certain applications + Дозволяє використовувати VPN тільки для вибраних програм + + + + PageSettingsDns + + + Default server does not support custom dns + Сервер за замовчуванням не підтримує користувацький DNS + + + + DNS servers + DNS сервер + + + When AmneziaDNS is not used or installed + Ці адреси будуть використовуватись, коли вимкнено або не встановлено AmneziaDNS + + + + If AmneziaDNS is not used or installed + Якщо AmneziaDNS вимкнено або не встановлено + + + + Primary DNS + Основний DNS + + + + Secondary DNS + Допоміжний DNS + + + + Restore default + Відновити за замовчуванням + + + + Restore default DNS settings? + Відновити налаштування DNS за замовчуванням? + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + Settings have been reset + Налаштування скинуті + + + + Save + Зберегти + + + + Settings saved + Зберегти налаштування + + + + PageSettingsLogging + + + Logging is enabled. Note that logs will be automatically disabled after 14 days, and all log files will be deleted. + + + + + Logging + Логування + + + + Enabling this function will save application's logs automatically, By default, logging functionality is disabled. Enable log saving in case of application malfunction. + + + + + Save logs + Зберегти логи + + + + Open folder with logs + Відкрити папку з логами + + + + Save + Зберегти + + + + Logs files (*.log) + Logs files (*.log) + + + + Logs file saved + Файл з логами збережено + + + + Save logs to file + Зберегти логи в файл + + + + Clear logs? + Очистити логи? + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + + Logs have been cleaned up + Логи видалено + + + + Clear logs + Видалити логи + + + + PageSettingsServerData + + + All installed containers have been added to the application + Всі встановлені протоколи та сервіси були додані в застосунок + + + Clear Amnezia cache + Очистити кеш Amnezia + + + May be needed when changing other settings + Може знадобитись при зміні інших налаштувань + + + Clear cached profiles? + Видалити кеш Amnezia? + + + + No new installed containers found + Нові встановлені протоколи і сервіси не виявлені + + + + + + + + + + + + Continue + Продовжити + + + + + + + Cancel + Відмінити + + + + Check the server for previously installed Amnezia services + Проверить сервер на наличие ранее установленных сервисов Amnezia + + + + Add them to the application if they were not displayed + Додати їх в застосунок, якщо вони не були відображені + + + + Reboot server + Перезавантажити сервер + + + + Do you want to reboot the server? + Ви впевнені, що хочете перезавантажити сервер? + + + + The reboot process may take approximately 30 seconds. Are you sure you wish to proceed? + Процес перезавантаження може зайняти близько 30 сек. Ви впевені, що хочете продовжити? + + + + Remove server from application + + + + + Do you want to remove the server from application? + Ви впевнені, що хочете видалити сервер із застосунку? + + + + Clear server from Amnezia software + + + + + Do you want to clear server from Amnezia software? + + + + + All users whom you shared a connection with will no longer be able to connect to it. + + + + Do you want to clear server Amnezia-installed services? + Ви хочете очистити сервер від сервісів Amnezia? + + + + Reset API config + Скинути API конфігурацію + + + + Do you want to reset API config? + Ви хочете скинути API конфігурацію + + + Remove this server from the app + Видалити сервер із застосунку + + + Remove server from application? + Видалити сервер із застосунку? + + + + All installed AmneziaVPN services will still remain on the server. + Всі встановлені сервіси та протоколи Amnezia все ще залишаться на сервері. + + + Clear server Amnezia-installed services + Видалити всі сервіси і протоколи Amnezia з сервера + + + Clear server Amnezia-installed services? + Видалити всі сервіси і протоколи Amnezia з сервера? + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + На сервері будуть видалені всі дані, повязані з Amnezia: протоколи, сервіси, конфігураційні файли, ключі та сертифікати. + + + + PageSettingsServerInfo + + + Server name + Імя сервера + + + + Save + Зберегти + + + + Protocols + Протоколи + + + + Services + Сервіси + + + + Management + + + + Data + Дані + + + + PageSettingsServerProtocol + + + settings + Налаштування + + + + Clear %1 profile + + + + + Clear %1 profile? + + + + + + + + + + Remove + Видалити + + + + Remove %1 from server? + Видалити %1 з сервера? + + + + All users with whom you shared a connection will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + All users who you shared a connection with will no longer be able to connect to it. + Користувачі, з якими ви поділились цим протоколм, більше не зможуть до нього підключитись. + + + + + Continue + Продовжити + + + + + Cancel + Відмінити + + + + PageSettingsServersList + + + Servers + Сервери + + + + PageSettingsSplitTunneling + + Only the sites listed here will be accessed via VPN + Тільки адреси із списку мають відкриватись через VPN + + + + Addresses from the list should not be accessed via VPN + Адреси із списку не повинні відкриватись через VPN + + + + Split tunneling + Роздільне VPN-тунелювання + + + + Mode + Режим + + + + Remove + Видалити + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + Website or IP + Сайт чи IP + + + + Import / Export Sites + Імпорт / Експорт Сайтів + + + + Only the sites listed here will be accessed through the VPN + Тільки адреси зі списку повинні відкриватись через VPN + + + + Cannot change split tunneling settings during active connection + Не можна змінити налаштування роздільного тунелювання при підключеному VPN + + + + Default server does not support split tunneling function + + + + + website or IP + вебсайт або IP + + + + Import + Імпорт + + + + Save site list + Зберегти список сайтів + + + + Save sites + Зберегти + + + + + + Sites files (*.json) + Sites files (*.json) + + + + Import a list of sites + Імпортувати список із сайтами + + + + Replace site list + Замінити список із сайтами + + + + + Open sites file + Відкрити список із сайтами + + + + Add imported sites to existing ones + Додати імпортовані сайти до існуючих + + + + PageSetupWizardConfigSource + + + Server connection + Підключення до сервера + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + Не використовуйте код підключення з загальнодоступних джерел. Можливо, його було створено для перехоплення ваших даних. + +Все в порядку, якщо ви використовуєте код, яким поділився користувач, якому ви довіряєте. + + + + Do not use connection codes from untrusted sources, as they may be created to intercept your data. + Не використовуйте код підключення з загальнодоступних джерел. Можливо, його було створено для перехоплення ваших даних. + + + + What do you have? + Виберіть що у вас є + + + + File with connection settings + Файл з налаштуваннями підключення + + + + File with connection settings or backup + Файл з налаштуваннями підключення або бекап + + + + Open config file + Відкрити файл з конфігурацією + + + + QR-code + QR-код + + + + Key as text + Ключ у вигляді тексту + + + + PageSetupWizardCredentials + + Server connection + Підключення до сервера + + + + Server IP address [:port] + Server IP address [:port] + + + 255.255.255.255:88 + 255.255.255.255:88 + + + Password / SSH private key + Password / SSH private key + + + + Continue + Продовжити + + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + Усі введені вами дані залишатимуться суворо конфіденційними +і не будуть передані чи розголошені Amnezia або будь-яким третім особам + + + + Enter the address in the format 255.255.255.255:88 + Введіть адресу в форматі 255.255.255.255:88 + + + Login to connect via SSH + Login to connect via SSH + + + + Configure your server + Налаштувати свій сервер + + + + 255.255.255.255:22 + + + + + SSH Username + + + + + Password or SSH private key + + + + + All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties + Усі дані, які ви вводите, залишатимуться суворо конфіденційними та не будуть передані чи розголошені Amnezia або будь-яким третім особам + + + + Ip address cannot be empty + Поле IP address не може бути пустим + + + + Login cannot be empty + Поле Login не може бути пустим + + + + Password/private key cannot be empty + Поле Password/Private key не може бути пустим + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + Який рівень контроля над інтернетом у вашому регіоні? + + + + Choose a VPN protocol + + + + + Skip setup + + + + Set up a VPN yourself + Налаштувати VPN самостійно + + + I want to choose a VPN protocol + Вибрати VPN-протокол + + + + Continue + Продовжити + + + Set up later + Налаштувати пізніше + + + + PageSetupWizardInstalling + + + The server has already been added to the application + Сервер уже додано в застосунок + + + Amnesia has detected that your server is currently + Amnezia виявила, що ваш сервер зараз + + + busy installing other software. Amnesia installation + зайнятий встановленням інших протоколів та сервісів. Встановлення Amnezia + + + + Amnezia has detected that your server is currently + Amnezia виявила, що сервер + + + + busy installing other software. Amnezia installation + зайнятий встановленням інших протоколів та сервісів. Встановлення Amnezia + + + + will pause until the server finishes installing other software + буде призупинено, поки сервер не завершить встановлення + + + + Installing + Встановлення + + + + Cancel installation + Відмінити встановлення + + + + + Usually it takes no more than 5 minutes + Зазвичай, займає не більше 5 хвилин + + + + PageSetupWizardProtocolSettings + + + Installing %1 + Встановити %1 + + + + More detailed + Детальніше + + + + Close + Закрити + + + + Network protocol + Мережевий протокол + + + + Port + Порт + + + + Install + Встановити + + + + PageSetupWizardProtocols + + + VPN protocol + VPN протокол + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + Виберіть протокол, який вам більше підходить. Пізніше можна встановити інші протоколи і додаткові сервіси, такі як DNS-проксі, TOR-сайт и SFTP. + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + Наведіть камеру на QR-код і утримуйте її протягом декількох секунд. + + + + PageSetupWizardStart + + + Settings restored from backup file + Відновлення налаштувань із бекап файлу + + + + Free service for creating a personal VPN on your server. + Простий і безкоштовний застосунок для запуска self-hosted VPN з високими вимогами до приватності. + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + Допомагає отримати доступ до заблокованого вмісту, не повідомляючи про вашу конфіденційність, навіть постачальникам VPN. + + + + I have the data to connect + У мене є дані для підключення + + + + I have nothing + У мене нічого нема + + + + https://amnezia.org/instructions/0_starter-guide + + + + + PageSetupWizardTextKey + + + Connection key + Ключ для підключення + + + + A line that starts with vpn://... + Стрічка, яка починається з vpn://... + + + + Key + Ключ + + + + Insert + Вставити + + + + Continue + Продовжити + + + + PageSetupWizardViewConfig + + + New connection + Нове підключення + + + Do not use connection code from public sources. It could be created to intercept your data. + Не використовуйте код підключення з загальнодоступних джерел. Він може бути створений для перехоплення ваших даних. + + + Do not use connection codes from untrusted sources, as they may be created to intercept your data. + Не використовуйте код підключення з загальнодоступних джерел. Він може бути створений для перехоплення ваших даних. + + + + Collapse content + Згорнути + + + + Show content + Показати вміст ключа + + + + Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data. + + + + + Connect + Підключитись + + + + PageShare + + + OpenVpn native format + OpenVPN нативний формат + + + + WireGuard native format + WireGuard нативний формат + + + VPN Access + VPN-Доступ + + + + Connection + З'єднання + + + VPN access without the ability to manage the server + Доступ до VPN, без можливості керування сервером + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + Доступ до керування сервером. Користувач, з яким ви ділитесь повним доступом до підключення, зможе додавати та видаляти протоколи і служби на сервері, а також змінювати налаштування. + + + + + Server + Сервер + + + Accessing + Доступ + + + + Config revoked + Кофігурацію відкликано + + + + Connection to + Підключення до + + + + File with connection settings to + Файл з налаштуванням доступу до + + + + Save OpenVPN config + Зберегти OpenVPN конфігурацію + + + + Save WireGuard config + Збергти WireGuard конфігурацію + + + + Save AmneziaWG config + Зберегти AmneziaWG конфігурацію + + + + Save ShadowSocks config + Зберегти конфігурацію ShadowSocks + + + + Save Cloak config + Зберегти конфігурацію Cloak + + + + Save XRay config + + + + + For the AmneziaVPN app + Для AmneziaVPN + + + + AmneziaWG native format + нативний формат AmneziaWG + + + + ShadowSocks native format + ShadowSocks нативний формат + + + + Cloak native format + Cloak нативний формат + + + + XRay native format + + + + + Share VPN Access + Поділитись VPN з'єднанням + + + + Share full access to the server and VPN + Поділитись повним доступом до серверу + + + + Use for your own devices, or share with those you trust to manage the server. + Використовуйте для власних пристроїв або передайте керування сервером тим, кому довіряєте. + + + + + Users + Користувачі + + + + User name + Ім'я користувача + + + + Search + Пошук + + + + Creation date: + Дата створення: + + + + Rename + Перейменувати + + + + Client name + + + + + Save + Зберегти + + + + Revoke + Відкликати + + + + Revoke the config for a user - %1? + Відкликати доступ для користувача - %1? + + + + The user will no longer be able to connect to your server. + Користувач більше не зможе підключатись до вашого сервера + + + + Continue + Продовжити + + + + Cancel + Відмінити + + + Full access + Повний доступ + + + + Share VPN access without the ability to manage the server + Поділитись доступом до VPN, без можливості керування сервером + + + + + Protocol + Протокол + + + + + Connection format + Формат підключення + + + + + Share + Поділитись + + + + PageShareFullAccess + + + Full access to the server and VPN + Повний доступ до серверу та VPN + + + + We recommend that you use full access to the server only for your own additional devices. + + Ми рекомендуємо використовувати повний доступ тілки для власних пристроїв. + + + + If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. + Якщо ви ділитеся повним доступом з іншими людьми, вони можуть видаляти та додавати протоколи та служби на сервер, що призведе до некоректної роботи VPN для всіх користувачів. + + + + + Server + Сервер + + + + Accessing + Доступ + + + + File with accessing settings to + Файл з налаштуваннями доступу до + + + + Share + Поділитись + + + + Connection to + Підключення до + + + + File with connection settings to + Файл з налаштуванням доступу до + + + + PageStart + + + Logging was disabled after 14 days, log files were deleted + + + + + PopupType + + + Close + Закрити + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + Пароль не знайдено + + + + Could not decrypt data + Не вдалося розшифрувати данні + + + + + Unknown error + Невідома помилка + + + + Could not open wallet: %1; %2 + Не вдалося відкрити зв'язку ключів: %1; %2 + + + + Password not found + Пароль не знайдено + + + + Could not open keystore + Could not open keystore + + + + Could not remove private key from keystore + Could not remove private key from keystore + + + + QKeychain::JobPrivate + + + Unknown error + Unknown error + + + + Access to keychain denied + Access to keychain denied + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + Could not store data in settings: access error + + + + Could not store data in settings: format error + Could not store data in settings: format error + + + + Could not delete data from settings: access error + Could not delete data from settings: access error + + + + Could not delete data from settings: format error + Could not delete data from settings: format error + + + + Entry not found + Entry not found + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + Password entry not found + + + + + Could not decrypt data + Could not decrypt data + + + + D-Bus is not running + D-Bus is not running + + + + + Unknown error + Unknown error + + + + No keychain service available + No keychain service available + + + + Could not open wallet: %1; %2 + Could not open wallet: %1; %2 + + + + Access to keychain denied + Access to keychain denied + + + + Could not determine data type: %1; %2 + Could not determine data type: %1; %2 + + + + + Entry not found + Entry not found + + + + Unsupported entry type 'Map' + Unsupported entry type 'Map' + + + + Unknown kwallet entry type '%1' + Unknown kwallet entry type '%1' + + + + Password not found + Password not found + + + + Could not open keystore + Could not open keystore + + + + Could not retrieve private key from keystore + Could not retrieve private key from keystore + + + + Could not create decryption cipher + Could not create decryption cipher + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + Credential size exceeds maximum size of %1 + + + + Credential key exceeds maximum size of %1 + Credential key exceeds maximum size of %1 + + + + Writing credentials failed: Win32 error code %1 + Writing credentials failed: Win32 error code %1 + + + + Encryption failed + Encryption failed + + + + D-Bus is not running + D-Bus is not running + + + + + Unknown error + Unknown error + + + + Could not open wallet: %1; %2 + Could not open wallet: %1; %2 + + + + Password not found + Password not found + + + + Could not open keystore + Could not open keystore + + + + Could not create private key generator + Could not create private key generator + + + + Could not generate new private key + Could not generate new private key + + + + Could not retrieve private key from keystore + Could not retrieve private key from keystore + + + + Could not create encryption cipher + Could not create encryption cipher + + + + Could not encrypt data + Could not encrypt data + + + + QObject + + + No error + No error + + + + Unknown Error + Unknown Error + + + + Function not implemented + Function not implemented + + + + Server check failed + Server check failed + + + + Server port already used. Check for another software + Server port already used. Check for another software + + + + Server error: Docker container missing + Server error: Docker container missing + + + + Server error: Docker failed + Server error: Docker failed + + + + Installation canceled by user + Installation canceled by user + + + + The user does not have permission to use sudo + The user does not have permission to use sudo + + + + Server error: Packet manager error + + + + + Ssh request was denied + Ssh request was denied + + + + Ssh request was interrupted + Ssh request was interrupted + + + + Ssh internal error + Ssh internal error + + + + Invalid private key or invalid passphrase entered + Invalid private key or invalid passphrase entered + + + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + + + + Timeout connecting to server + Timeout connecting to server + + + + Scp error: Generic failure + + + + Sftp error: End-of-file encountered + Sftp error: End-of-file encountered + + + Sftp error: File does not exist + Sftp error: File does not exist + + + Sftp error: Permission denied + Sftp error: Permission denied + + + Sftp error: Generic failure + Sftp error: Generic failure + + + Sftp error: Garbage received from server + Sftp error: Garbage received from server + + + Sftp error: No connection has been set up + Sftp error: No connection has been set up + + + Sftp error: There was a connection, but we lost it + Sftp error: There was a connection, but we lost it + + + Sftp error: Operation not supported by libssh yet + Sftp error: Operation not supported by libssh yet + + + Sftp error: Invalid file handle + Sftp error: Invalid file handle + + + Sftp error: No such file or directory path exists + Sftp error: No such file or directory path exists + + + Sftp error: An attempt to create an already existing file or directory has been made + Sftp error: An attempt to create an already existing file or directory has been made + + + Sftp error: Write-protected filesystem + Sftp error: Write-protected filesystem + + + Sftp error: No media was in remote drive + Sftp error: No media was in remote drive + + + + The config does not contain any containers and credentials for connecting to the server + Конфігурація не містить контейнерів і облікових даних для підключення до серверу + + + + Error when retrieving configuration from API + + + + + This config has already been added to the application + Ця конфігурація вже була додана в застосунок + + + + ErrorCode: %1. + + + + Failed to save config to disk + Failed to save config to disk + + + + OpenVPN config missing + OpenVPN config missing + + + + OpenVPN management server error + OpenVPN management server error + + + + OpenVPN executable missing + OpenVPN executable missing + + + + ShadowSocks (ss-local) executable missing + ShadowSocks (ss-local) executable missing + + + + Cloak (ck-client) executable missing + Cloak (ck-client) executable missing + + + + Amnezia helper service error + Amnezia helper service error + + + + OpenSSL failed + OpenSSL failed + + + + Can't connect: another VPN connection is active + Can't connect: another VPN connection is active + + + + Can't setup OpenVPN TAP network adapter + Can't setup OpenVPN TAP network adapter + + + + VPN pool error: no available addresses + VPN pool error: no available addresses + + + + VPN connection error + + + + + QFile error: The file could not be opened + + + + + QFile error: An error occurred when reading from the file + + + + + QFile error: The file could not be accessed + + + + + QFile error: An unspecified error occurred + + + + + QFile error: A fatal error occurred + + + + + QFile error: The operation was aborted + + + + + Internal error + Internal error + + + + IPsec + IPsec + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. + ShadowSocks - маскує VPN-трафік під звичайний веб-трафік, але розпізнається системами аналізу трафіка в деяких регіонах з високим рівнем цензури. + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + OpenVPN over Cloak - OpenVPN з маскуванням VPN під HTTPS трафік і захистом від active-probbing. Підходить для регіонів з самим високим рівнем цензури. + + + + Create a file vault on your server to securely store and transfer files. + Створіть на сервері файлове сховище для безпечного зберігання та передачі файлів. + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against blocking. + +OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + + + + + A relatively new popular VPN protocol with a simplified architecture. +WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + + + + + The REALITY protocol, a pioneering development by the creators of XRay, is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion. +It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, thus presenting an authentic TLS certificate and data. +This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, legitimate sites without the need for specific configurations. +Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REALITY's innovative "friend or foe" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship. + + + + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + IKEv2 разом з шифруванням IPSec -- це сучасний та стабільний протокол VPN. +Він може швидко переключись між мережами та пристроями, що робить його осболиво адаптованим під динамічні мережеві середовища. +Потрібно зазначити, що незважаючи на стабільність та швидкість, IKEv2 легко розпізнається та вразливий до блокувань. + +* IKEv2 в AmneziaVPN тільки для Windows. +* Низьке енергоспоживання, на мобільних пристроях +* Мінімальна конфігурація +* Розпізнається системами DPI-анализу +* Працює по мережевому протоколу UDP, порти 500 і 4500. + + + + DNS Service + DNS Сервіс + + + + Sftp file sharing service + Сервіс обміну файлами Sftp + + + + + Website in Tor network + Веб-сайт в мережі Tor + + + + Amnezia DNS + Amnezia DNS + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + OpenVPN - популярний VPN-протокол, гнучний в налаштуваннях. Має власний протокол оснований на обміні ключами SSL/TLS. + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - новий популярний VPN-протокол, з високою швидістю та низьким енергоспоживанням. Для регіонів з низьким рівнем цензури. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + AmneziaWG - фірмовий протокол Amnezia, оснований на протоколі WireGuard. Такий же швидкий, як і WireGuard, але стійкий до блокувань. Рекомендується для регіонів з високим рівнем цензури. + + + + XRay with REALITY - Suitable for countries with the highest level of internet censorship. Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods. + + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + IKEv2 сучасний стабільний протокол, трішки швидше за інших відновлює підключення. Підтримується в останніх версіях Android и iOS самими операційними системами. + + + + Deploy a WordPress site on the Tor network in two clicks. + Розгорніть сайт WordPress в мережі Tor в два кліка. + + + + Replace the current DNS server with your own. This will increase your privacy level. + Замініть DNS-сервер на Amnezia DNS. Це підвищить вашу рівень захищеності в інтернеті. + + + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + OpenVPN один із самих популярних і перевірених часом протоколів VPN. +Він використовує власний протокол, який опирається на протокол SSL/TLS для шифрування та обміну ключами. Крім того, підтримка OpenVPN багатьох методів аутентифікації робить його універсальним і адаптованим до широкого спектру пристроїв і операційних систем. Завдяки відкритому коду, OpenVPN піддається ретельному аналізу зі сторони світової спільноти, що постійно підвищує його безпеку. Завдяки оптимальному співвідношенню продуктивності, безпеки та сумісності OpenVPN залишається найкращим вибором як для приватних осіб, так і для компаній. + +* Доступний в AmneziaVPN для всіх платформ +* Нормальне енергоспоживання на мобільних пристроях +* Гнучка настройка під користувача для роботи з різними пристроями та оперційними системами +* Розпізнається системами DPI-анализу і тому вразливий до блокувань +* Може працювати за протоколом TCP і UDP. + + + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + Shadowsocks, створений на основі протоколу SOCKS5, захищає з'єднання AEAD шифруванням. Незважаючи на те, що протокол Shadowsocks розроблений таким чином, щоб бути незаметним і складним для ідентифікації, він не ідентичний стандартному HTTPS-з'єднанню. Однак деякі системи аналізу трафіку все-таки можуть знайти підключення Shadowsocks. У зв’язку з обмеженою підтримкою в Amnezia рекомендується використовувати протокол AmneziaWG або OpenVPN через Cloak. + +* Доступний в AmneziaVPN тільки на ПК. +* Гнучке налаштування протоколу шифрування +* Розпізнається деякими DPI-системами +* Працює по мережевому протоколу TCP. + + + + After installation, Amnezia will create a + + file storage on your server. You will be able to access it using + FileZilla or other SFTP clients, as well as mount the disk on your device to access + it directly from your device. + +For more detailed information, you can + find it in the support section under "Create SFTP file storage." + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + OpenVPN over Cloak - це комбінація протоколу OpenVPN і плагіна Cloak, розроблена спеціально для подолання блокувань. + +OpenVPN забезпечує найдійне VPN-підключення за рахунок шифрування всього інтернет-трафіку між клієнтом і сервером. + +Cloak захищає OpenVPN від розпізнаванння та блокування системами DPI. + +Cloak може замінити метаданні пакетів. Він повністю маскується під звичайний HTTPS трафік, а також, захищає VPN від розпізнаванння за допомогою Active Probing. Це робить його дуже стійким до розпізнання. + +Одразу після отримання першого пакету даних Cloak перевіряє автентичність вхідного з’єднання. Якщо автентифікація не вдається, плагін маскує сервер як підроблений веб-сайт, і ваша VPN стає невидимою для систем аналізу. + +Якщо в вашому регіоні екстремальний рівень цензури в Інтернеті, ми рекомендуємо відразу використовувати OpenVPN over Cloak. + +* Доступний в AmneziaVPN на всіх платформах +* Високе енергоспоживання на мобільних пристроях +* Гнучке налаштування +* Не розпізнається системами DPI-анализу +* Працює по протоколу TCP, 443 порт. + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + WireGuard - відносно новий та популярний VPN-протокол з простою архітектурою. +Забезпечує стабільне VPN-з'єднання, високу продуктивність на всіх пристроях. Використовує жорстко закодовані параметри шифрування. WireGuard порівняно з OpenVPN має нижчу затримку та кращу пропускну здатність передачі даних. +WireGuard дуже вразливий до блокування. На відміну від деяких інших протоколів VPN, які використовують методи обфускації, узгоджені шаблони пакетів WireGuard можна легше ідентифікувати та, таким чином, заблокувати вдосконаленими системами Deep Packet Inspection (DPI) та іншими інструментами моніторингу мережі. +* Доступний в AmneziaVPN для всіх платформ +* Низьке енергоспоживання +* Мінімальна кількість налаштувань +* Легко розпізнається системами DPI-анализу, вразливий до блокувань +* Працює по протоколу UDP. + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + Сучасна ітерація популярного протоколу VPN, AmneziaWG спирається на протокол WireGuard, зберігаючи його просту архітектуру та високопродуктивні можливості на різних пристроях. +Незважаючи на те, що WireGuard відомий своєю ефективністю, він має проблеми з легким виявленням через чіткі підписи пакетів. AmneziaWG вирішує цю проблему, використовуючи кращі методи обфускації, завдяки чому її трафік змішується зі звичайним інтернет-трафіком. +Це означає, що AmneziaWG зберігає швидку роботу оригінального протоколу WireGuard, додаючи додатковий рівень скритності, що робить його чудовим вибором для тих, хто бажає швидкого та непомітного VPN-з’єднання. + +* Доступно в AmneziaVPN на всіх платформах +* Низьке енергоспоживання +* Мінімальна кількість налаштувань +* Не розпізнається системами аналізу DPI, стійкий до блокування +* Працює через мережевий протокол UDP. + + + AmneziaWG container + AmneziaWG протокол + + + Sftp file sharing service - is secure FTP service + Файлове сховище для безпечного зберігання даних + + + + Sftp service + Сервіс SFTP + + + + Entry not found + Entry not found + + + + Access to keychain denied + Access to keychain denied + + + + No keyring daemon + No keyring daemon + + + + Already unlocked + Already unlocked + + + + No such keyring + No such keyring + + + + Bad arguments + Bad arguments + + + + I/O error + I/O error + + + + Cancelled + Cancelled + + + + Keyring already exists + Keyring already exists + + + + No match + No match + + + + Unknown error + Unknown error + + + + error 0x%1: %2 + error 0x%1: %2 + + + + SelectLanguageDrawer + + + Choose language + Выберите язык + + + + Settings + + + Server #1 + Server #1 + + + + + Server + Server + + + + SettingsController + + + All settings have been reset to default values + Всі налаштування були скинуті до значення "По замовчуванню" + + + Cached profiles cleared + Кеш профілю очищено + + + + Backup file is corrupted + Backup файл пошкодженно + + + + ShareConnectionDrawer + + + + Save AmneziaVPN config + Зберегти config AmneziaVPN + + + + Share + Поділитись + + + + Copy + Скопіювати + + + + + Copied + Скопійовано + + + + Copy config string + Скопіювати стрічку конфігурації + + + + Show connection settings + Показати налаштування підключення + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + Для зчитування QR-коду в застосунку Amnezia виберіть "Додати сервер" → "У мене є дані підключенн" → "QR-код, ключ чи файл налаштувань" + + + + SitesController + + + Hostname not look like ip adress or domain name + Ім’я хосту не схоже на ip-адресу чи доменне ім’я + + + + New site added: %1 + Додано новий сайт %1 + + + + Site removed: %1 + Сайт видалено %1 + + + + Can't open file: %1 + Неможливо відкрити файл: %1 + + + + Failed to parse JSON data from file: %1 + Не вдалося розібрати JSON-данні із файлу: %1 + + + + The JSON data is not an array in file: %1 + Данні JSON не являються масивом в файлі: %1 + + + + Import completed + Імпорт завершено + + + + Export completed + Експорт завершено + + + + SystemTrayNotificationHandler + + + + Show + Показати + + + + + Connect + Підключитись + + + + + Disconnect + Відключитись + + + + + Visit Website + Відвідати сайт + + + + + Quit + Закрити + + + + TextFieldWithHeaderType + + + The field can't be empty + Поле не може бути пустим + + + + VpnConnection + + + Mbps + Mbps + + + + VpnProtocol + + + Unknown + Невідомий + + + + Disconnected + Відключено + + + + Preparing + Підготовка + + + + Connecting... + Підключення... + + + + Connected + Підключено + + + + Disconnecting... + Відключення... + + + + Reconnecting... + Перепідключення... + + + + Error + Помилка + + + + amnezia::ContainerProps + + + Low + Низький + + + + Medium or High + Середній або високий + + + + Extreme + Екстремальний + + + + I just want to increase the level of my privacy. + Я просто хочу підвищити свій рівень безпеки в інтернеті. + + + + I want to bypass censorship. This option recommended in most cases. + Я хочу обійти блокування. Цей варіант рекомендується в більшості випадків. + + + + Most VPN protocols are blocked. Recommended if other options are not working. + Більшість протоколів VPN заблоковано. Рекомендовано, якщо інші варіанти не підходять. + + + High + Високий + + + Medium + Середній + + + Many foreign websites and VPN providers are blocked + Багато іноземних сайтів і VPN-провайдерів заблоковано + + + Some foreign sites are blocked, but VPN providers are not blocked + Деякі іноземні сайти заблоковані, але VPN-провайдери не заблоковані + + + I just want to increase the level of privacy + Я просто хочу підвищити свій рівень безпеки в інтернеті. + + + + main2 + + + Private key passphrase + Пароль для особистого ключа + + + + Save + Зберегти + + + diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 83953ac91..320d75e7f 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -5,13 +5,23 @@ #else #include #endif +#include +#include "core/controllers/apiController.h" +#include "core/controllers/vpnConfigurationController.h" #include "core/errorstrings.h" ConnectionController::ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_vpnConnection(vpnConnection) + const QSharedPointer &clientManagementModel, + const QSharedPointer &vpnConnection, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_clientManagementModel(clientManagementModel), + m_vpnConnection(vpnConnection), + m_settings(settings) { connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, this, &ConnectionController::onConnectionStateChanged); @@ -45,16 +55,36 @@ ConnectionController::ConnectionController(const QSharedPointer &s void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); + auto serverConfig = m_serversModel->getServerConfig(serverIndex); + + ErrorCode errorCode = ErrorCode::NoError; + + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); + + if (serverConfig.value(config_key::configVersion).toInt() + && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + ApiController apiController; + errorCode = apiController.updateServerConfigFromApi(serverConfig); + if (errorCode != ErrorCode::NoError) { + emit connectionErrorOccurred(errorString(errorCode)); + return; + } + m_serversModel->editServer(serverConfig, serverIndex); + } if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit noInstalledContainers(); + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); return; } - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); + DockerContainer container = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - DockerContainer container = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); - const QJsonObject &containerConfig = m_containersModel->getContainerConfig(container); + if (!m_containersModel->isSupportedByCurrentPlatform(container)) { + emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform")); + return; + } if (container == DockerContainer::None) { emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); @@ -63,7 +93,27 @@ void ConnectionController::openConnection() qApp->processEvents(); - emit connectToVpn(serverIndex, credentials, container, containerConfig); + VpnConfigurationsController vpnConfigurationController(m_settings); + + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); + errorCode = updateProtocolConfig(container, credentials, containerConfig); + if (errorCode != ErrorCode::NoError) { + emit connectionErrorOccurred(errorString(errorCode)); + return; + } + + auto dns = m_serversModel->getDnsPair(serverIndex); + serverConfig = m_serversModel->getServerConfig(serverIndex); + + auto vpnConfiguration = + vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container, errorCode); + if (errorCode != ErrorCode::NoError) { + emit connectionErrorOccurred(tr("unable to create configuration")); + return; + } + + emit connectToVpn(serverIndex, credentials, container, vpnConfiguration); } void ConnectionController::closeConnection() @@ -112,6 +162,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) } case Vpn::ConnectionState::Preparing: { m_isConnectionInProgress = true; + m_connectionStateText = tr("Preparing..."); break; } case Vpn::ConnectionState::Error: { @@ -180,10 +231,14 @@ QVector ConnectionController::getTimes() const { return m_times; } - -void ConnectionController::toggleConnection(bool skipConnectionInProgressCheck) +void ConnectionController::toggleConnection() { - if (!skipConnectionInProgressCheck && isConnectionInProgress()) { + if (m_state == Vpn::ConnectionState::Preparing) { + emit preparingConfig(); + return; + } + + if (isConnectionInProgress()) { closeConnection(); } else if (isConnected()) { closeConnection(); @@ -201,3 +256,51 @@ bool ConnectionController::isConnected() const { return m_isConnected; } + +bool ConnectionController::isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container) +{ + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QString protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)) + .toObject() + .value(config_key::last_config) + .toString(); + + if (protocolConfig.isEmpty()) { + return false; + } + } + return true; +} + +ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, + const ServerCredentials &credentials, QJsonObject &containerConfig) +{ + QFutureWatcher watcher; + + QFuture future = QtConcurrent::run([this, container, &credentials, &containerConfig]() { + ErrorCode errorCode = ErrorCode::NoError; + if (!isProtocolConfigExists(containerConfig, container)) { + VpnConfigurationsController vpnConfigurationController(m_settings); + errorCode = + vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + m_serversModel->updateContainerConfig(container, containerConfig); + + errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName())); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + } + return errorCode; + }); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + return watcher.result(); +} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 55de0eb2c..321e83945 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -2,6 +2,7 @@ #define CONNECTIONCONTROLLER_H #include "protocols/vpnprotocol.h" +#include "ui/models/clientManagementModel.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" #include "vpnconnection.h" @@ -19,7 +20,9 @@ public: explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, QObject *parent = nullptr); + const QSharedPointer &clientManagementModel, + const QSharedPointer &vpnConnection, + const std::shared_ptr &settings, QObject *parent = nullptr); ~ConnectionController() = default; @@ -34,7 +37,7 @@ public: Q_INVOKABLE QVector getTimes() const; public slots: - void toggleConnection(bool skipConnectionInProgressCheck); + void toggleConnection(); void openConnection(); void closeConnection(); @@ -46,9 +49,12 @@ public slots: void onTranslationsUpdated(); + ErrorCode updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials, + QJsonObject &containerConfig); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig); + const QJsonObject &vpnConfiguration); void disconnectFromVpn(); void connectionStateChanged(); @@ -58,14 +64,21 @@ signals: void noInstalledContainers(); + void connectButtonClicked(); + void preparingConfig(); + private: Vpn::ConnectionState getCurrentConnectionState(); + bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container); QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_clientManagementModel; QSharedPointer m_vpnConnection; + std::shared_ptr m_settings; + bool m_isConnected = false; bool m_isConnectionInProgress = false; QString m_connectionStateText = tr("Connect"); diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index c60627fed..9d68dfce6 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -8,11 +8,7 @@ #include #include -#include "configurators/awg_configurator.h" -#include "configurators/cloak_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/wireguard_configurator.h" +#include "core/controllers/vpnConfigurationController.h" #include "core/errorstrings.h" #include "systemController.h" #ifdef Q_OS_ANDROID @@ -20,25 +16,20 @@ #endif #include "qrcodegen.hpp" -ExportController::ExportController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, +ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &clientManagementModel, - const std::shared_ptr &settings, - const std::shared_ptr &configurator, QObject *parent) + const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_clientManagementModel(clientManagementModel), - m_settings(settings), - m_configurator(configurator) + m_settings(settings) { #ifdef Q_OS_ANDROID m_authResultNotifier.reset(new AuthResultNotifier); m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier)); - connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, - [this]() { emit exportErrorOccurred(tr("Access error!")); }); - connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, - &ExportController::generateFullAccessConfig); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, [this]() { emit exportErrorOccurred(tr("Access error!")); }); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, &ExportController::generateFullAccessConfig); #endif } @@ -47,9 +38,9 @@ void ExportController::generateFullAccessConfig() clearPreviousConfig(); int serverIndex = m_serversModel->getProcessedServerIndex(); - QJsonObject config = m_settings->server(serverIndex); + QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); - QJsonArray containers = config.value(config_key::containers).toArray(); + QJsonArray containers = serverConfig.value(config_key::containers).toArray(); for (auto i = 0; i < containers.size(); i++) { auto containerConfig = containers.at(i).toObject(); auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); @@ -63,13 +54,11 @@ void ExportController::generateFullAccessConfig() containers.replace(i, containerConfig); } - config[config_key::containers] = containers; + serverConfig[config_key::containers] = containers; - QByteArray compressedConfig = QJsonDocument(config).toJson(); + QByteArray compressedConfig = QJsonDocument(serverConfig).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_config = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -83,8 +72,7 @@ void ExportController::generateFullAccessConfigAndroid() auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); if (appContext.isValid()) { auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", - appContext.object()); + "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); if (intent.isValid()) { if (intent.object() != nullptr) { QtAndroidPrivate::startActivity(intent.object(), 1, m_authResultReceiver.get()); @@ -103,116 +91,108 @@ void ExportController::generateConnectionConfig(const QString &clientName) int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); QJsonObject containerConfig = m_containersModel->getContainerConfig(container); containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - ErrorCode errorCode = ErrorCode::NoError; - for (Proto protocol : ContainerProps::protocolsForContainer(container)) { - QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); + VpnConfigurationsController vpnConfigurationController(m_settings); + ErrorCode errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig); - QString clientId; - QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, - clientId, &errorCode); - if (errorCode) { - emit exportErrorOccurred(errorString(errorCode)); - return; - } - protocolConfig.insert(config_key::last_config, vpnConfig); - containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); - if (protocol == Proto::OpenVpn || protocol == Proto::Awg || protocol == Proto::WireGuard) { - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - if (errorCode) { - emit exportErrorOccurred(errorString(errorCode)); - return; - } - } + errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, clientName); + if (errorCode != ErrorCode::NoError) { + emit exportErrorOccurred(errorString(errorCode)); + return; } - QJsonObject config = m_settings->server(serverIndex); // todo change to servers_model + QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); if (!errorCode) { - config.remove(config_key::userName); - config.remove(config_key::password); - config.remove(config_key::port); - config.insert(config_key::containers, QJsonArray { containerConfig }); - config.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + serverConfig.remove(config_key::userName); + serverConfig.remove(config_key::password); + serverConfig.remove(config_key::port); + serverConfig.insert(config_key::containers, QJsonArray { containerConfig }); + serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - auto dns = m_configurator->getDnsForConfig(serverIndex); - config.insert(config_key::dns1, dns.first); - config.insert(config_key::dns2, dns.second); + auto dns = m_serversModel->getDnsPair(serverIndex); + serverConfig.insert(config_key::dns1, dns.first); + serverConfig.insert(config_key::dns2, dns.second); } - QByteArray compressedConfig = QJsonDocument(config).toJson(); + QByteArray compressedConfig = QJsonDocument(serverConfig).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_config = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + m_config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } -void ExportController::generateOpenVpnConfig(const QString &clientName) +ErrorCode ExportController::generateNativeConfig(const DockerContainer container, const QString &clientName, const Proto &protocol, + QJsonObject &jsonNativeConfig) { clearPreviousConfig(); int serverIndex = m_serversModel->getProcessedServerIndex(); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); + auto dns = m_serversModel->getDnsPair(serverIndex); + bool isApiConfig = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::IsServerFromApiRole)); - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); QJsonObject containerConfig = m_containersModel->getContainerConfig(container); containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + VpnConfigurationsController vpnConfigurationController(m_settings); + + QString protocolConfigString; + + ErrorCode errorCode = vpnConfigurationController.createProtocolConfigString(isApiConfig, dns, credentials, container, containerConfig, + protocol, protocolConfigString); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); + + if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) { + auto clientId = jsonNativeConfig.value(config_key::clientId).toString(); + errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials); + } + return errorCode; +} + +void ExportController::generateOpenVpnConfig(const QString &clientName) +{ + QJsonObject nativeConfig; + DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); ErrorCode errorCode = ErrorCode::NoError; - QString clientId; - QString config = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, - clientId, &errorCode); + + if (container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) { + errorCode = generateNativeConfig(container, clientName, Proto::OpenVpn, nativeConfig); + } else { + errorCode = generateNativeConfig(container, clientName, ContainerProps::defaultProtocol(container), nativeConfig); + } + if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; } - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); - QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8()); - - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - if (errorCode) { - emit exportErrorOccurred(errorString(errorCode)); - return; - } - emit exportConfigChanged(); } void ExportController::generateWireGuardConfig(const QString &clientName) { - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - QString clientId; - ErrorCode errorCode = ErrorCode::NoError; - QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, - clientId, &errorCode); + QJsonObject nativeConfig; + ErrorCode errorCode = generateNativeConfig(DockerContainer::WireGuard, clientName, Proto::WireGuard, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; } - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); - QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } @@ -220,38 +200,19 @@ void ExportController::generateWireGuardConfig(const QString &clientName) qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - if (errorCode) { - emit exportErrorOccurred(errorString(errorCode)); - return; - } - emit exportConfigChanged(); } void ExportController::generateAwgConfig(const QString &clientName) { - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - QString clientId; - ErrorCode errorCode = ErrorCode::NoError; - QString config = m_configurator->awgConfigurator->genAwgConfig(credentials, container, containerConfig, - clientId, &errorCode); + QJsonObject nativeConfig; + ErrorCode errorCode = generateNativeConfig(DockerContainer::Awg, clientName, Proto::Awg, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; } - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Awg, config); - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); - QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } @@ -259,42 +220,34 @@ void ExportController::generateAwgConfig(const QString &clientName) qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW); m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1))); - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - if (errorCode) { - emit exportErrorOccurred(errorString(errorCode)); - return; - } - emit exportConfigChanged(); } void ExportController::generateShadowSocksConfig() { - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - + QJsonObject nativeConfig; + DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); ErrorCode errorCode = ErrorCode::NoError; - QString config = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container, - containerConfig, &errorCode); - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::ShadowSocks, config); - QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + if (container == DockerContainer::Cloak) { + errorCode = generateNativeConfig(container, "", Proto::ShadowSocks, nativeConfig); + } else { + errorCode = generateNativeConfig(container, "", ContainerProps::defaultProtocol(container), nativeConfig); + } - QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n"); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + + QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } - m_nativeConfigString = - QString("%1:%2@%3:%4") - .arg(configJson.value("method").toString(), configJson.value("password").toString(), - configJson.value("server").toString(), configJson.value("server_port").toString()); + m_nativeConfigString = QString("%1:%2@%3:%4") + .arg(nativeConfig.value("method").toString(), nativeConfig.value("password").toString(), + nativeConfig.value("server").toString(), nativeConfig.value("server_port").toString()); m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64(); @@ -306,30 +259,17 @@ void ExportController::generateShadowSocksConfig() void ExportController::generateCloakConfig() { - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - ErrorCode errorCode = ErrorCode::NoError; - QString config = - m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &errorCode); - + QJsonObject nativeConfig; + ErrorCode errorCode = generateNativeConfig(DockerContainer::Cloak, "", Proto::Cloak, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; } - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Cloak, config); - QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object(); - configJson.remove(config_key::transport_proto); - configJson.insert("ProxyMethod", "shadowsocks"); + nativeConfig.remove(config_key::transport_proto); + nativeConfig.insert("ProxyMethod", "shadowsocks"); - QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n"); + QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } @@ -339,29 +279,14 @@ void ExportController::generateCloakConfig() void ExportController::generateXrayConfig() { - clearPreviousConfig(); - - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); - - DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); - QJsonObject containerConfig = m_containersModel->getContainerConfig(container); - containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); - - ErrorCode errorCode = ErrorCode::NoError; - - QString clientId; - QString config = - m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, Proto::Xray, clientId, &errorCode); - + QJsonObject nativeConfig; + ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; } - config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Xray, config); - QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object(); - QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n"); + QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); for (const QString &line : lines) { m_config.append(line + "\n"); } @@ -399,8 +324,7 @@ void ExportController::updateClientManagementModel(const DockerContainer contain void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) { - ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials, - m_serversModel->getProcessedServerIndex()); + ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex()); if (errorCode != ErrorCode::NoError) { emit exportErrorOccurred(errorString(errorCode)); } diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 06d620032..b978137e4 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -3,10 +3,9 @@ #include -#include "configurators/vpn_configurator.h" +#include "ui/models/clientManagementModel.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" -#include "ui/models/clientManagementModel.h" #ifdef Q_OS_ANDROID #include "platforms/android/authResultReceiver.h" #endif @@ -15,11 +14,9 @@ class ExportController : public QObject { Q_OBJECT public: - explicit ExportController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const QSharedPointer &clientManagementModel, - const std::shared_ptr &settings, - const std::shared_ptr &configurator, QObject *parent = nullptr); + explicit ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &clientManagementModel, const std::shared_ptr &settings, + QObject *parent = nullptr); Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) @@ -65,11 +62,13 @@ private: void clearPreviousConfig(); + ErrorCode generateNativeConfig(const DockerContainer container, const QString &clientName, const Proto &protocol, + QJsonObject &jsonNativeConfig); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; QSharedPointer m_clientManagementModel; std::shared_ptr m_settings; - std::shared_ptr m_configurator; QString m_config; QString m_nativeConfigString; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 120a7e7c0..dc0cb00ae 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -4,11 +4,13 @@ #include #include #include -#include #include +#include -#include "core/errorstrings.h" #include "core/controllers/serverController.h" +#include "core/controllers/vpnConfigurationController.h" +#include "core/errorstrings.h" +#include "logger.h" #include "core/networkUtilities.h" #include "utilities.h" #include "ui/models/protocols/awgConfigModel.h" @@ -16,6 +18,8 @@ namespace { + Logger logger("ServerController"); + #ifdef Q_OS_WINDOWS QString getNextDriverLetter() { @@ -42,14 +46,15 @@ namespace #endif } -InstallController::InstallController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, +InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &protocolsModel, + const QSharedPointer &clientManagementModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_protocolModel(protocolsModel), + m_clientManagementModel(clientManagementModel), m_settings(settings) { } @@ -74,8 +79,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr if (protocol == mainProto) { containerConfig.insert(config_key::port, QString::number(port)); - containerConfig.insert(config_key::transport_proto, - ProtocolProps::transportProtoToString(transportProto, protocol)); + containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol)); if (container == DockerContainer::Awg) { QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); @@ -107,9 +111,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; - } - - if (container == DockerContainer::Sftp) { + } else if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::password, Utils::getRandomString(10)); } @@ -119,109 +121,142 @@ void InstallController::install(DockerContainer container, int port, TransportPr config.insert(ProtocolProps::protoToString(protocol), containerConfig); } + ServerCredentials serverCredentials; if (m_shouldCreateServer) { if (isServerAlreadyExists()) { return; } - installServer(container, config); + serverCredentials = m_processedServerCredentials; } else { - installContainer(container, config); + int serverIndex = m_serversModel->getProcessedServerIndex(); + serverCredentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); } -} -void InstallController::installServer(DockerContainer container, QJsonObject &config) -{ ServerController serverController(m_settings); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation); QMap installedContainers; - ErrorCode errorCode = - serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); - - QString finishMessage = ""; - - if (!installedContainers.contains(container)) { - errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); - installedContainers.insert(container, config); - finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); - } else { - finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); - } - if (installedContainers.size() > 1) { - finishMessage += tr("\nAdded containers that were already installed on the server"); - } - - if (errorCode == ErrorCode::NoError) { - QJsonObject server; - server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); - server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); - server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); - server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - QJsonArray containerConfigs; - for (const QJsonObject &containerConfig : qAsConst(installedContainers)) { - containerConfigs.append(containerConfig); - } - - server.insert(config_key::containers, containerConfigs); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - m_serversModel->addServer(server); - - emit installServerFinished(finishMessage); + ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, installedContainers); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); return; } - emit installationErrorOccurred(errorString(errorCode)); -} - -void InstallController::installContainer(DockerContainer container, QJsonObject &config) -{ - int serverIndex = m_serversModel->getProcessedServerIndex(); - ServerCredentials serverCredentials = - qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - - ServerController serverController(m_settings); - connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation); - - QMap installedContainers; - ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); - QString finishMessage = ""; if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(serverCredentials, container, config); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + installedContainers.insert(container, config); finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); } else { finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); } - bool isInstalledContainerAddedToGui = false; - - if (errorCode == ErrorCode::NoError) { - for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { - QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key()); - if (containerConfig.isEmpty()) { - m_serversModel->addContainerConfig(iterator.key(), iterator.value()); - if (container != iterator.key()) { // skip the newly installed container - isInstalledContainerAddedToGui = true; - } - } - } - if (isInstalledContainerAddedToGui) { - finishMessage += tr("\nAlready installed containers were found on the server. " - "All installed containers have been added to the application"); - } - - emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); return; } - emit installationErrorOccurred(errorString(errorCode)); + if (m_shouldCreateServer) { + installServer(container, installedContainers, serverCredentials, finishMessage); + } else { + installContainer(container, installedContainers, serverCredentials, finishMessage); + } +} + +void InstallController::installServer(const DockerContainer container, const QMap &installedContainers, + const ServerCredentials &serverCredentials, QString &finishMessage) +{ + if (installedContainers.size() > 1) { + finishMessage += tr("\nAdded containers that were already installed on the server"); + } + + QJsonObject server; + server.insert(config_key::hostName, m_processedServerCredentials.hostName); + server.insert(config_key::userName, m_processedServerCredentials.userName); + server.insert(config_key::password, m_processedServerCredentials.secretData); + server.insert(config_key::port, m_processedServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + QJsonArray containerConfigs; + VpnConfigurationsController vpnConfigurationController(m_settings); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto containerConfig = iterator.value(); + + if (ContainerProps::isSupportedByCurrentPlatform(container)) { + auto errorCode = + vpnConfigurationController.createProtocolConfigForContainer(m_processedServerCredentials, iterator.key(), containerConfig); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + containerConfigs.append(containerConfig); + + errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName())); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + } else { + containerConfigs.append(containerConfig); + } + } + + server.insert(config_key::containers, containerConfigs); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + m_serversModel->addServer(server); + + emit installServerFinished(finishMessage); +} + +void InstallController::installContainer(const DockerContainer container, const QMap &installedContainers, + const ServerCredentials &serverCredentials, QString &finishMessage) +{ + bool isInstalledContainerAddedToGui = false; + + VpnConfigurationsController vpnConfigurationController(m_settings); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key()); + if (containerConfig.isEmpty()) { + containerConfig = iterator.value(); + + if (ContainerProps::isSupportedByCurrentPlatform(container)) { + auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(), containerConfig); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + m_serversModel->addContainerConfig(iterator.key(), containerConfig); + + errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName())); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + } else { + m_serversModel->addContainerConfig(iterator.key(), containerConfig); + } + + if (container != iterator.key()) { // skip the newly installed container + isInstalledContainerAddedToGui = true; + } + } + } + if (isInstalledContainerAddedToGui) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } + + emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); } bool InstallController::isServerAlreadyExists() @@ -230,8 +265,7 @@ bool InstallController::isServerAlreadyExists() auto modelIndex = m_serversModel->index(i); const ServerCredentials credentials = qvariant_cast(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole)); - if (m_currentlyInstalledServerCredentials.hostName == credentials.hostName - && m_currentlyInstalledServerCredentials.port == credentials.port) { + if (m_processedServerCredentials.hostName == credentials.hostName && m_processedServerCredentials.port == credentials.port) { emit serverAlreadyExists(i); return true; } @@ -248,15 +282,37 @@ void InstallController::scanServerForInstalledContainers() ServerController serverController(m_settings); QMap installedContainers; - ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, installedContainers); if (errorCode == ErrorCode::NoError) { bool isInstalledContainerAddedToGui = false; + VpnConfigurationsController vpnConfigurationController(m_settings); for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { - QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key()); + auto container = iterator.key(); + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); if (containerConfig.isEmpty()) { - m_serversModel->addContainerConfig(iterator.key(), iterator.value()); + containerConfig = iterator.value(); + + if (ContainerProps::isSupportedByCurrentPlatform(container)) { + auto errorCode = + vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, container, containerConfig); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + m_serversModel->addContainerConfig(container, containerConfig); + + errorCode = m_clientManagementModel->appendClient(container, serverCredentials, containerConfig, + QString("Admin [%1]").arg(QSysInfo::prettyProductName())); + if (errorCode) { + emit installationErrorOccurred(errorString(errorCode)); + return; + } + } else { + m_serversModel->addContainerConfig(container, containerConfig); + } + isInstalledContainerAddedToGui = true; } } @@ -268,6 +324,151 @@ void InstallController::scanServerForInstalledContainers() emit installationErrorOccurred(errorString(errorCode)); } +ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers) +{ + 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; + }; + + ServerController serverController(m_settings); + QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'"); + + ErrorCode errorCode = serverController.runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + auto containersInfo = stdOut.split("\n"); + for (auto &containerInfo : containersInfo) { + if (containerInfo.isEmpty()) { + continue; + } + const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*"); + QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo); + if (containerAndPortMatch.hasMatch()) { + QString name = containerAndPortMatch.captured(1); + QString port = containerAndPortMatch.captured(2); + QString transportProto = containerAndPortMatch.captured(3); + DockerContainer container = ContainerProps::containerFromString(name); + + QJsonObject config; + Proto mainProto = ContainerProps::defaultProtocol(container); + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + if (protocol == mainProto) { + containerConfig.insert(config_key::port, port); + containerConfig.insert(config_key::transport_proto, transportProto); + + if (protocol == Proto::Awg) { + QString serverConfig = serverController.getTextFileFromContainer(container, credentials, + protocols::awg::serverConfigPath, errorCode); + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = + serverConfigMap.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = + serverConfigMap.value(config_key::transportPacketMagicHeader); + } else if (protocol == Proto::Sftp) { + stdOut.clear(); + script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name); + + ErrorCode errorCode = serverController.runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + auto sftpInfo = stdOut.split(":"); + if (sftpInfo.size() < 2) { + logger.error() << "Key parameters for the sftp container are missing"; + continue; + } + auto userName = sftpInfo.at(0); + userName = userName.remove(0, 1); + auto password = sftpInfo.at(1); + + containerConfig.insert(config_key::userName, userName); + containerConfig.insert(config_key::password, password); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } + installedContainers.insert(container, config); + } + const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*"); + QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo); + if (torOrDnsRegMatch.hasMatch()) { + QString name = torOrDnsRegMatch.captured(1); + QString port = torOrDnsRegMatch.captured(2); + QString transportProto = torOrDnsRegMatch.captured(3); + DockerContainer container = ContainerProps::containerFromString(name); + + QJsonObject config; + Proto mainProto = ContainerProps::defaultProtocol(container); + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + if (protocol == mainProto) { + containerConfig.insert(config_key::port, port); + containerConfig.insert(config_key::transport_proto, transportProto); + + if (protocol == Proto::TorWebSite) { + stdOut.clear(); + script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name); + + ErrorCode errorCode = serverController.runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (stdOut.isEmpty()) { + logger.error() << "Key parameters for the tor container are missing"; + continue; + } + + QString onion = stdOut; + onion.replace("\n", ""); + containerConfig.insert(config_key::site, onion); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } + installedContainers.insert(container, config); + } + } + + return ErrorCode::NoError; +} + void InstallController::updateContainer(QJsonObject config) { int serverIndex = m_serversModel->getProcessedServerIndex(); @@ -284,6 +485,7 @@ void InstallController::updateContainer(QJsonObject config) connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation); errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + clearCachedProfile(); } if (errorCode == ErrorCode::NoError) { @@ -334,23 +536,51 @@ void InstallController::removeAllContainers() emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::removeCurrentlyProcessedContainer() +void InstallController::removeProcessedContainer() { int serverIndex = m_serversModel->getProcessedServerIndex(); QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); - int container = m_containersModel->getCurrentlyProcessedContainerIndex(); - QString containerName = m_containersModel->getCurrentlyProcessedContainerName(); + int container = m_containersModel->getProcessedContainerIndex(); + QString containerName = m_containersModel->getProcessedContainerName(); ErrorCode errorCode = m_serversModel->removeContainer(container); if (errorCode == ErrorCode::NoError) { - emit removeCurrentlyProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName)); + emit removeProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); } +void InstallController::removeApiConfig() +{ + auto serverConfig = m_serversModel->getServerConfig(m_serversModel->getDefaultServerIndex()); + + serverConfig.remove(config_key::dns1); + serverConfig.remove(config_key::dns2); + serverConfig.remove(config_key::containers); + serverConfig.remove(config_key::hostName); + + serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); + + m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); +} + +void InstallController::clearCachedProfile() +{ + int serverIndex = m_serversModel->getProcessedServerIndex(); + DockerContainer container = static_cast(m_containersModel->getProcessedContainerIndex()); + QJsonObject containerConfig = m_containersModel->getContainerConfig(container); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + m_serversModel->clearCachedProfile(container); + m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex); + + emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container))); +} + QRegularExpression InstallController::ipAddressPortRegExp() { return NetworkUtilities::ipAddressPortRegExp(); @@ -361,17 +591,15 @@ QRegularExpression InstallController::ipAddressRegExp() return NetworkUtilities::ipAddressRegExp(); } -void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, - const QString &secretData) +void InstallController::setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) { - m_currentlyInstalledServerCredentials.hostName = hostName; - if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { - m_currentlyInstalledServerCredentials.port = - m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); - m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); + m_processedServerCredentials.hostName = hostName; + if (m_processedServerCredentials.hostName.contains(":")) { + m_processedServerCredentials.port = m_processedServerCredentials.hostName.split(":").at(1).toInt(); + m_processedServerCredentials.hostName = m_processedServerCredentials.hostName.split(":").at(0); } - m_currentlyInstalledServerCredentials.userName = userName; - m_currentlyInstalledServerCredentials.secretData = secretData; + m_processedServerCredentials.userName = userName; + m_processedServerCredentials.secretData = secretData; } void InstallController::setShouldCreateServer(bool shouldCreateServer) @@ -398,8 +626,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; #elif defined AMNEZIA_DESKTOP - mountPath = - QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); + mountPath = QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); QDir dir(mountPath); if (!dir.exists()) { dir.mkpath(mountPath); @@ -461,8 +688,7 @@ bool InstallController::checkSshConnection() ErrorCode errorCode = ErrorCode::NoError; m_privateKeyPassphrase = ""; - if (m_currentlyInstalledServerCredentials.secretData.contains("BEGIN") - && m_currentlyInstalledServerCredentials.secretData.contains("PRIVATE KEY")) { + if (m_processedServerCredentials.secretData.contains("BEGIN") && m_processedServerCredentials.secretData.contains("PRIVATE KEY")) { auto passphraseCallback = [this]() { emit passphraseRequestStarted(); QEventLoop loop; @@ -473,10 +699,9 @@ bool InstallController::checkSshConnection() }; QString decryptedPrivateKey; - errorCode = serverController.getDecryptedPrivateKey(m_currentlyInstalledServerCredentials, decryptedPrivateKey, - passphraseCallback); + errorCode = serverController.getDecryptedPrivateKey(m_processedServerCredentials, decryptedPrivateKey, passphraseCallback); if (errorCode == ErrorCode::NoError) { - m_currentlyInstalledServerCredentials.secretData = decryptedPrivateKey; + m_processedServerCredentials.secretData = decryptedPrivateKey; } else { emit installationErrorOccurred(errorString(errorCode)); return false; @@ -484,7 +709,7 @@ bool InstallController::checkSshConnection() } QString output; - output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); + output = serverController.checkSshConnection(m_processedServerCredentials, errorCode); if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorString(errorCode)); @@ -508,10 +733,10 @@ void InstallController::setEncryptedPassphrase(QString passphrase) void InstallController::addEmptyServer() { QJsonObject server; - server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); - server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); - server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); - server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::hostName, m_processedServerCredentials.hostName); + server.insert(config_key::userName, m_processedServerCredentials.userName); + server.insert(config_key::password, m_processedServerCredentials.secretData); + server.insert(config_key::port, m_processedServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); @@ -532,17 +757,17 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co const AwgConfig oldConfig(oldProtoConfig); const AwgConfig newConfig(newProtoConfig); - if (!oldConfig.hasEqualServerSettings(newConfig)) { - return true; + if (oldConfig.hasEqualServerSettings(newConfig)) { + return false; } } else if (container == DockerContainer::WireGuard) { const WgConfig oldConfig(oldProtoConfig); const WgConfig newConfig(newProtoConfig); - if (!oldConfig.hasEqualServerSettings(newConfig)) { - return true; + if (oldConfig.hasEqualServerSettings(newConfig)) { + return false; } } - return false; + return true; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 490903499..b44b25202 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -6,24 +6,24 @@ #include "containers/containers_defs.h" #include "core/defs.h" +#include "ui/models/clientManagementModel.h" #include "ui/models/containers_model.h" -#include "ui/models/servers_model.h" #include "ui/models/protocols_model.h" +#include "ui/models/servers_model.h" class InstallController : public QObject { Q_OBJECT public: - explicit InstallController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, + explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &protocolsModel, + const QSharedPointer &clientManagementModel, const std::shared_ptr &settings, QObject *parent = nullptr); ~InstallController(); public slots: void install(DockerContainer container, int port, TransportProto transportProto); - void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, - const QString &secretData); + void setProcessedServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); void scanServerForInstalledContainers(); @@ -33,7 +33,11 @@ public slots: void removeProcessedServer(); void rebootProcessedServer(); void removeAllContainers(); - void removeCurrentlyProcessedContainer(); + void removeProcessedContainer(); + + void removeApiConfig(); + + void clearCachedProfile(); QRegularExpression ipAddressPortRegExp(); QRegularExpression ipAddressRegExp(); @@ -50,14 +54,14 @@ signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); - void updateContainerFinished(const QString& message); + void updateContainerFinished(const QString &message); void scanServerFinished(bool isInstalledContainerFound); void rebootProcessedServerFinished(const QString &finishedMessage); void removeProcessedServerFinished(const QString &finishedMessage); void removeAllContainersFinished(const QString &finishedMessage); - void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); + void removeProcessedContainerFinished(const QString &finishedMessage); void installationErrorOccurred(const QString &errorMessage); @@ -71,19 +75,25 @@ signals: void currentContainerUpdated(); + void cachedProfileCleared(const QString &message); + private: - void installServer(DockerContainer container, QJsonObject &config); - void installContainer(DockerContainer container, QJsonObject &config); + void installServer(const DockerContainer container, const QMap &installedContainers, + const ServerCredentials &serverCredentials, QString &finishMessage); + void installContainer(const DockerContainer container, const QMap &installedContainers, + const ServerCredentials &serverCredentials, QString &finishMessage); bool isServerAlreadyExists(); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); bool isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); QSharedPointer m_serversModel; QSharedPointer m_containersModel; QSharedPointer m_protocolModel; + QSharedPointer m_clientManagementModel; std::shared_ptr m_settings; - ServerCredentials m_currentlyInstalledServerCredentials; + ServerCredentials m_processedServerCredentials; bool m_shouldCreateServer; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 16787ef8a..4ac675048 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -153,12 +153,6 @@ void SettingsController::clearSettings() #endif } -void SettingsController::clearCachedProfiles() -{ - m_serversModel->clearCachedProfiles(); - emit changeSettingsFinished(tr("Cached profiles cleared")); -} - bool SettingsController::isAutoConnectEnabled() { return m_settings->isAutoConnect(); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 72ba72e92..2678c65d5 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -46,7 +46,6 @@ public slots: QString getAppVersion(); void clearSettings(); - void clearCachedProfiles(); bool isAutoConnectEnabled(); void toggleAutoConnect(bool enable); diff --git a/client/ui/models/appSplitTunnelingModel.cpp b/client/ui/models/appSplitTunnelingModel.cpp index 6aba56069..84d01f997 100644 --- a/client/ui/models/appSplitTunnelingModel.cpp +++ b/client/ui/models/appSplitTunnelingModel.cpp @@ -5,14 +5,8 @@ AppSplitTunnelingModel::AppSplitTunnelingModel(std::shared_ptr settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings) { - auto routeMode = m_settings->getAppsRouteMode(); - if (routeMode == Settings::AppsRouteMode::VpnAllApps) { - m_isSplitTunnelingEnabled = false; - m_currentRouteMode = Settings::AppsRouteMode::VpnAllExceptApps; - } else { - m_isSplitTunnelingEnabled = true; - m_currentRouteMode = routeMode; - } + m_isSplitTunnelingEnabled = m_settings->getAppsSplitTunnelingEnabled(); + m_currentRouteMode = m_settings->getAppsRouteMode(); m_apps = m_settings->getVpnApps(m_currentRouteMode); } @@ -84,11 +78,7 @@ bool AppSplitTunnelingModel::isSplitTunnelingEnabled() void AppSplitTunnelingModel::toggleSplitTunneling(bool enabled) { - if (enabled) { - setRouteMode(m_currentRouteMode); - } else { - m_settings->setAppsRouteMode(Settings::AppsRouteMode::VpnAllApps); - } + m_settings->setAppsSplitTunnelingEnabled(enabled); m_isSplitTunnelingEnabled = enabled; emit splitTunnelingToggled(); } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 7c81c80ec..ae4c48dc6 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -10,7 +10,8 @@ namespace { Logger logger("ClientManagementModel"); - namespace configKey { + namespace configKey + { constexpr char clientId[] = "clientId"; constexpr char clientName[] = "clientName"; constexpr char container[] = "container"; @@ -61,7 +62,6 @@ void ClientManagementModel::migration(const QByteArray &clientsTableString) m_clientsTable.push_back(client); } - } ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials) @@ -74,15 +74,13 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr ErrorCode error = ErrorCode::NoError; QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); } else { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); } - const QByteArray clientsTableString = - serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error); + const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, error); if (error != ErrorCode::NoError) { logger.error() << "Failed to get the clientsTable file from the server"; endResetModel(); @@ -96,8 +94,7 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr int count = 0; - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { error = getOpenVpnClients(serverController, container, credentials, count); } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { error = getWireGuardClients(serverController, container, credentials, count); @@ -109,8 +106,7 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson(); if (clientsTableString != newClientsTableString) { - error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, - clientsTableFile); + error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { logger.error() << "Failed to upload the clientsTable file to the server"; } @@ -121,7 +117,8 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr return error; } -ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count) +ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverController, DockerContainer container, + ServerCredentials credentials, int &count) { ErrorCode error = ErrorCode::NoError; QString stdOut; @@ -130,10 +127,8 @@ ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverContr return ErrorCode::NoError; }; - const QString getOpenVpnClientsList = - "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; - QString script = serverController.replaceVars(getOpenVpnClientsList, - serverController.genVarsForScript(credentials, container)); + const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; + QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container)); error = serverController.runScript(credentials, script, cbReadStdOut); if (error != ErrorCode::NoError) { logger.error() << "Failed to retrieve the list of issued certificates on the server"; @@ -163,14 +158,13 @@ ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverContr return error; } -ErrorCode ClientManagementModel::getWireGuardClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count) +ErrorCode ClientManagementModel::getWireGuardClients(ServerController &serverController, DockerContainer container, + ServerCredentials credentials, int &count) { ErrorCode error = ErrorCode::NoError; - const QString wireGuardConfigFile = - QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg"); - const QString wireguardConfigString = - serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); + const QString wireGuardConfigFile = QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg"); + const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, error); if (error != ErrorCode::NoError) { logger.error() << "Failed to get the wg conf file from the server"; return error; @@ -215,10 +209,27 @@ bool ClientManagementModel::isClientExists(const QString &clientId) return false; } -ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, - const DockerContainer container, ServerCredentials credentials) +ErrorCode ClientManagementModel::appendClient(const DockerContainer container, const ServerCredentials &credentials, + const QJsonObject &containerConfig, const QString &clientName) { - ErrorCode error; + Proto protocol; + if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + protocol = Proto::OpenVpn; + } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { + protocol = ContainerProps::defaultProtocol(container); + } else { + return ErrorCode::NoError; + } + + auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + + return appendClient(protocolConfig.value(config_key::clientId).toString(), clientName, container, credentials); +} + +ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, + ServerCredentials credentials) +{ + ErrorCode error = ErrorCode::NoError; error = updateModel(container, credentials); if (error != ErrorCode::NoError) { @@ -246,8 +257,7 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt ServerController serverController(m_settings); QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); } else { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); @@ -279,15 +289,13 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie ServerController serverController(m_settings); QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); } else { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); } - ErrorCode error = - serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { logger.error() << "Failed to upload the clientsTable file to the server"; } @@ -295,15 +303,14 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie return error; } -ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, - ServerCredentials credentials, const int serverIndex) +ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, ServerCredentials credentials, + const int serverIndex) { ErrorCode errorCode = ErrorCode::NoError; auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { errorCode = revokeOpenVpn(row, container, credentials, serverIndex); } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { errorCode = revokeWireGuard(row, container, credentials); @@ -333,8 +340,50 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain return errorCode; } -ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, - ServerCredentials credentials, const int serverIndex) +ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig, const DockerContainer container, ServerCredentials credentials, + const int serverIndex) +{ + ErrorCode errorCode = ErrorCode::NoError; + errorCode = updateModel(container, credentials); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + Proto protocol; + if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + protocol = Proto::OpenVpn; + } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { + protocol = ContainerProps::defaultProtocol(container); + } else { + return ErrorCode::NoError; + } + + auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + + int row; + bool clientExists = false; + QString clientId = protocolConfig.value(config_key::clientId).toString(); + for (row = 0; row < rowCount(); row++) { + auto client = m_clientsTable.at(row).toObject(); + if (clientId == client.value(configKey::clientId).toString()) { + clientExists = true; + break; + } + } + if (!clientExists) { + return errorCode; + } + + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + errorCode = revokeOpenVpn(row, container, credentials, serverIndex); + } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { + errorCode = revokeWireGuard(row, container, credentials); + } + return errorCode; +} + +ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials, + const int serverIndex) { auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); @@ -348,8 +397,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai .arg(clientId); ServerController serverController(m_settings); - const QString script = - serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container)); + const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container)); ErrorCode error = serverController.runScript(credentials, script); if (error != ErrorCode::NoError) { logger.error() << "Failed to revoke the certificate"; @@ -373,16 +421,14 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai return ErrorCode::NoError; } -ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container, - ServerCredentials credentials) +ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials) { - ErrorCode error; + ErrorCode error = ErrorCode::NoError; ServerController serverController(m_settings); const QString wireGuardConfigFile = QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg"); - const QString wireguardConfigString = - serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); + const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, error); if (error != ErrorCode::NoError) { logger.error() << "Failed to get the wg conf file from the server"; return error; @@ -413,8 +459,7 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); } else { clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); @@ -428,8 +473,7 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'"; error = serverController.runScript( credentials, - serverController.replaceVars(script.arg(wireGuardConfigFile), - serverController.genVarsForScript(credentials, container))); + serverController.replaceVars(script.arg(wireGuardConfigFile), serverController.genVarsForScript(credentials, container))); if (error != ErrorCode::NoError) { logger.error() << "Failed to execute the command 'wg syncconf' on the server"; return error; diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index c003881b3..836207bae 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -24,11 +24,14 @@ public: public slots: ErrorCode updateModel(DockerContainer container, ServerCredentials credentials); + ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig, + const QString &clientName); ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, ServerCredentials credentials); - ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, - ServerCredentials credentials, bool addTimeStamp = false); + ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, ServerCredentials credentials, + bool addTimeStamp = false); ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials, const int serverIndex); + ErrorCode revokeClient(const QJsonObject &containerConfig, const DockerContainer container, ServerCredentials credentials, const int serverIndex); protected: QHash roleNames() const override; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 38e547df2..b8633a186 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -38,7 +38,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); case IsInstalledRole: return m_containers.contains(container); - case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); + case IsCurrentlyProcessedRole: return container == static_cast(m_processedContainerIndex); case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); case IsShareableRole: return ContainerProps::isShareable(container); } @@ -63,19 +63,19 @@ void ContainersModel::updateModel(const QJsonArray &containers) endResetModel(); } -void ContainersModel::setCurrentlyProcessedContainerIndex(int index) +void ContainersModel::setProcessedContainerIndex(int index) { - m_currentlyProcessedContainerIndex = index; + m_processedContainerIndex = index; } -int ContainersModel::getCurrentlyProcessedContainerIndex() +int ContainersModel::getProcessedContainerIndex() { - return m_currentlyProcessedContainerIndex; + return m_processedContainerIndex; } -QString ContainersModel::getCurrentlyProcessedContainerName() +QString ContainersModel::getProcessedContainerName() { - return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); + return ContainerProps::containerHumanNames().value(static_cast(m_processedContainerIndex)); } QJsonObject ContainersModel::getContainerConfig(const int containerIndex) @@ -83,6 +83,16 @@ QJsonObject ContainersModel::getContainerConfig(const int containerIndex) return qvariant_cast(data(index(containerIndex), ConfigRole)); } +bool ContainersModel::isSupportedByCurrentPlatform(const int containerIndex) +{ + return qvariant_cast(data(index(containerIndex), IsSupportedRole)); +} + +bool ContainersModel::isServiceContainer(const int containerIndex) +{ + return qvariant_cast(data(index(containerIndex), ServiceTypeRole) == ServiceType::Other); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 385adf572..9999307f3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -42,13 +42,16 @@ public: public slots: void updateModel(const QJsonArray &containers); - void setCurrentlyProcessedContainerIndex(int containerIndex); - int getCurrentlyProcessedContainerIndex(); + void setProcessedContainerIndex(int containerIndex); + int getProcessedContainerIndex(); - QString getCurrentlyProcessedContainerName(); + QString getProcessedContainerName(); QJsonObject getContainerConfig(const int containerIndex); + bool isSupportedByCurrentPlatform(const int containerIndex); + bool isServiceContainer(const int containerIndex); + protected: QHash roleNames() const override; @@ -58,7 +61,7 @@ signals: private: QMap m_containers; - int m_currentlyProcessedContainerIndex; + int m_processedContainerIndex; }; #endif // CONTAINERS_MODEL_H diff --git a/client/ui/models/installedAppsModel.cpp b/client/ui/models/installedAppsModel.cpp index 7255b4cc2..6030c19e2 100644 --- a/client/ui/models/installedAppsModel.cpp +++ b/client/ui/models/installedAppsModel.cpp @@ -64,6 +64,7 @@ QVector> InstalledAppsModel::getSelectedAppsInfo() appsInfo.push_back({ appName, packageName }); } + m_selectedAppIndexes.clear(); return appsInfo; } diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 0c408a514..51675c3c8 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -43,6 +43,7 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan switch (language) { case LanguageSettings::AvailableLanguageEnum::English: strLanguage = "English"; break; case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; + case LanguageSettings::AvailableLanguageEnum::Ukrainian: strLanguage = "Українська"; break; case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break; case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break; @@ -60,6 +61,7 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; + case LanguageSettings::AvailableLanguageEnum::Ukrainian: emit updateTranslations(QLocale::Ukrainian); break; case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break; case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break; case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break; @@ -74,6 +76,7 @@ int LanguageModel::getCurrentLanguageIndex() case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; + case QLocale::Ukrainian: return static_cast(LanguageSettings::AvailableLanguageEnum::Ukrainian); break; case QLocale::Persian: return static_cast(LanguageSettings::AvailableLanguageEnum::Persian); break; case QLocale::Arabic: return static_cast(LanguageSettings::AvailableLanguageEnum::Arabic); break; case QLocale::Burmese: return static_cast(LanguageSettings::AvailableLanguageEnum::Burmese); break; diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index f3516e2d5..f62fe7dab 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -13,6 +13,7 @@ namespace LanguageSettings English, Russian, China_cn, + Ukrainian, Persian, Arabic, Burmese diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp index 7ef3af5b3..30d00306c 100644 --- a/client/ui/models/protocols/openvpnConfigModel.cpp +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -105,12 +105,12 @@ void OpenVpnConfigModel::updateModel(const QJsonObject &config) m_protocolConfig.insert(config_key::hash, protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash)); m_protocolConfig.insert(config_key::block_outside_dns, - protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); + protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)); m_protocolConfig.insert( config_key::tls_auth, - protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); + protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); m_protocolConfig.insert(config_key::additional_client_config, protocolConfig.value(config_key::additional_client_config) .toString(protocols::openvpn::defaultAdditionalClientConfig)); diff --git a/client/ui/models/protocols/shadowsocksConfigModel.cpp b/client/ui/models/protocols/shadowsocksConfigModel.cpp index 60c8feeea..2fe2d2a9d 100644 --- a/client/ui/models/protocols/shadowsocksConfigModel.cpp +++ b/client/ui/models/protocols/shadowsocksConfigModel.cpp @@ -37,6 +37,8 @@ QVariant ShadowSocksConfigModel::data(const QModelIndex &index, int role) const case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + case Roles::IsPortEditableRole: return m_container == DockerContainer::ShadowSocks ? true : false; + case Roles::IsCipherEditableRole: return m_container == DockerContainer::ShadowSocks ? true : false; } return QVariant(); @@ -71,6 +73,8 @@ QHash ShadowSocksConfigModel::roleNames() const roles[PortRole] = "port"; roles[CipherRole] = "cipher"; + roles[IsPortEditableRole] = "isPortEditable"; + roles[IsCipherEditableRole] = "isCipherEditable"; return roles; } diff --git a/client/ui/models/protocols/shadowsocksConfigModel.h b/client/ui/models/protocols/shadowsocksConfigModel.h index d8fa036b1..566df7683 100644 --- a/client/ui/models/protocols/shadowsocksConfigModel.h +++ b/client/ui/models/protocols/shadowsocksConfigModel.h @@ -13,7 +13,9 @@ class ShadowSocksConfigModel : public QAbstractListModel public: enum Roles { PortRole = Qt::UserRole + 1, - CipherRole + CipherRole, + IsPortEditableRole, + IsCipherEditableRole }; explicit ShadowSocksConfigModel(QObject *parent = nullptr); diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp index 60f6685fc..8903f40f6 100644 --- a/client/ui/models/protocols/wireguardConfigModel.cpp +++ b/client/ui/models/protocols/wireguardConfigModel.cpp @@ -63,7 +63,7 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config) QJsonObject WireGuardConfigModel::getConfig() { - const WgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); + const WgConfig oldConfig(m_fullConfig.value(config_key::wireguard).toObject()); const WgConfig newConfig(m_protocolConfig); if (!oldConfig.hasEqualServerSettings(newConfig)) { diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index b1bffa3f9..b2838ce3c 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -77,6 +77,7 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const case Proto::Cloak: return PageLoader::PageEnum::PageProtocolCloakSettings; case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; + case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgSettings; case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; case Proto::Xray: return PageLoader::PageEnum::PageProtocolXraySettings; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 8a6c5a38d..61af8c8da 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,14 +1,17 @@ #include "servers_model.h" #include "core/controllers/serverController.h" +#include "core/networkUtilities.h" -ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) - : m_settings(settings), QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { + m_isAmneziaDnsEnabled = m_settings->useAmneziaDns(); + connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) { - auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); + auto defaultContainer = + ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer); emit ServersModel::defaultServerNameChanged(); updateDefaultServerContainersModel(); @@ -28,10 +31,15 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int } QJsonObject server = m_servers.at(index.row()).toObject(); + const auto configVersion = server.value(config_key::configVersion).toInt(); switch (role) { case NameRole: { - server.insert(config_key::description, value.toString()); + if (configVersion) { + server.insert(config_key::name, value.toString()); + } else { + server.insert(config_key::description, value.toString()); + } m_settings->editServer(index.row(), server); m_servers.replace(index.row(), server); if (index.row() == m_defaultServerIndex) { @@ -336,9 +344,9 @@ void ServersModel::updateDefaultServerContainersModel() emit defaultServerContainersUpdated(containers); } -QJsonObject ServersModel::getDefaultServerConfig() +QJsonObject ServersModel::getServerConfig(const int serverIndex) { - return m_servers.at(m_defaultServerIndex).toObject(); + return m_servers.at(serverIndex).toObject(); } void ServersModel::reloadDefaultServerContainerConfig() @@ -378,7 +386,8 @@ void ServersModel::updateContainerConfig(const int containerIndex, const QJsonOb server.insert(config_key::containers, containers); auto defaultContainer = server.value(config_key::defaultContainer).toString(); - if ((ContainerProps::containerFromString(defaultContainer) == DockerContainer::None || ContainerProps::containerService(container) != ServiceType::Other)) { + if ((ContainerProps::containerFromString(defaultContainer) == DockerContainer::None + || ContainerProps::containerService(container) != ServiceType::Other)) { server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); } @@ -396,7 +405,9 @@ void ServersModel::addContainerConfig(const int containerIndex, const QJsonObjec server.insert(config_key::containers, containers); auto defaultContainer = server.value(config_key::defaultContainer).toString(); - if ((ContainerProps::containerFromString(defaultContainer) == DockerContainer::None || ContainerProps::containerService(container) != ServiceType::Other)) { + if (ContainerProps::containerFromString(defaultContainer) == DockerContainer::None + && ContainerProps::containerService(container) != ServiceType::Other + && ContainerProps::isSupportedByCurrentPlatform(container)) { server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); } @@ -408,7 +419,7 @@ void ServersModel::setDefaultContainer(const int serverIndex, const int containe auto container = static_cast(containerIndex); QJsonObject s = m_servers.at(serverIndex).toObject(); s.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - editServer(s, serverIndex); //check + editServer(s, serverIndex); // check } const QString ServersModel::getDefaultServerDefaultContainerName() @@ -420,8 +431,7 @@ const QString ServersModel::getDefaultServerDefaultContainerName() ErrorCode ServersModel::removeAllContainers() { ServerController serverController(m_settings); - ErrorCode errorCode = - serverController.removeAllContainers(m_settings->serverCredentials(m_processedServerIndex)); + ErrorCode errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_processedServerIndex)); if (errorCode == ErrorCode::NoError) { QJsonObject s = m_servers.at(m_processedServerIndex).toObject(); @@ -468,7 +478,8 @@ ErrorCode ServersModel::removeContainer(const int containerIndex) if (containers.empty()) { defaultContainer = DockerContainer::None; } else { - defaultContainer = ContainerProps::containerFromString(containers.begin()->toObject().value(config_key::container).toString()); + defaultContainer = + ContainerProps::containerFromString(containers.begin()->toObject().value(config_key::container).toString()); } server.insert(config_key::defaultContainer, ContainerProps::containerToString(defaultContainer)); } @@ -478,24 +489,9 @@ ErrorCode ServersModel::removeContainer(const int containerIndex) return errorCode; } -void ServersModel::clearCachedProfiles() -{ - const auto &containers = m_settings->containers(m_processedServerIndex); - for (DockerContainer container : containers.keys()) { - m_settings->clearLastConnectionConfig(m_processedServerIndex, container); - } - - m_servers.replace(m_processedServerIndex, m_settings->server(m_processedServerIndex)); - if (m_processedServerIndex == m_defaultServerIndex) { - updateDefaultServerContainersModel(); - } - updateContainersModel(); -} - void ServersModel::clearCachedProfile(const DockerContainer container) { m_settings->clearLastConnectionConfig(m_processedServerIndex, container); - m_servers.replace(m_processedServerIndex, m_settings->server(m_processedServerIndex)); if (m_processedServerIndex == m_defaultServerIndex) { updateDefaultServerContainersModel(); @@ -515,6 +511,36 @@ bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex) const return false; } +QPair ServersModel::getDnsPair(int serverIndex) +{ + QPair dns; + + const QJsonObject &server = m_servers.at(m_processedServerIndex).toObject(); + const auto containers = server.value(config_key::containers).toArray(); + bool isDnsContainerInstalled = false; + for (const QJsonValue &container : containers) { + if (ContainerProps::containerFromString(container.toObject().value(config_key::container).toString()) == DockerContainer::Dns) { + isDnsContainerInstalled = true; + } + } + + dns.first = server.value(config_key::dns1).toString(); + dns.second = server.value(config_key::dns2).toString(); + + if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) { + if (m_isAmneziaDnsEnabled && isDnsContainerInstalled) { + dns.first = protocols::dns::amneziaDnsIp; + } else + dns.first = m_settings->primaryDns(); + } + if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) { + dns.second = m_settings->secondaryDns(); + } + + qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second; + return dns; +} + QStringList ServersModel::getAllInstalledServicesName(const int serverIndex) { QStringList servicesName; @@ -598,7 +624,8 @@ bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) { return !(protocolConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0")); - } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn || defaultContainer == DockerContainer::ShadowSocks) { + } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn + || defaultContainer == DockerContainer::ShadowSocks) { return !(protocolConfig.value(config_key::last_config).toString().contains("redirect-gateway")); } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 00d8d06c1..7f4e3f6b8 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -80,13 +80,12 @@ public slots: void editServer(const QJsonObject &server, const int serverIndex); void removeServer(); - QJsonObject getDefaultServerConfig(); + QJsonObject getServerConfig(const int serverIndex); void reloadDefaultServerContainerConfig(); void updateContainerConfig(const int containerIndex, const QJsonObject config); void addContainerConfig(const int containerIndex, const QJsonObject config); - void clearCachedProfiles(); void clearCachedProfile(const DockerContainer container); ErrorCode removeContainer(const int containerIndex); @@ -98,6 +97,7 @@ public slots: QStringList getAllInstalledServicesName(const int serverIndex); void toggleAmneziaDns(bool enabled); + QPair getDnsPair(const int serverIndex); bool isServerFromApiAlreadyExists(const quint16 crc); diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index 96b6ca605..e6d12835c 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -3,14 +3,8 @@ SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings) { - auto routeMode = m_settings->routeMode(); - if (routeMode == Settings::RouteMode::VpnAllSites) { - m_isSplitTunnelingEnabled = false; - m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites; - } else { - m_isSplitTunnelingEnabled = true; - m_currentRouteMode = routeMode; - } + m_isSplitTunnelingEnabled = m_settings->getSitesSplitTunnelingEnabled(); + m_currentRouteMode = m_settings->routeMode(); fillSites(); } @@ -107,11 +101,7 @@ bool SitesModel::isSplitTunnelingEnabled() void SitesModel::toggleSplitTunneling(bool enabled) { - if (enabled) { - setRouteMode(m_currentRouteMode); - } else { - m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); - } + m_settings->setSitesSplitTunnelingEnabled(enabled); m_isSplitTunnelingEnabled = enabled; emit splitTunnelingToggled(); } diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index a915eb21f..14e398a44 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -17,16 +17,16 @@ Button { implicitWidth: 190 implicitHeight: 190 + text: ConnectionController.connectionStateText + Connections { target: ConnectionController - function onConnectionErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) + function onPreparingConfig() { + PageController.showNotificationMessage(qsTr("Unable to disconnect during configuration preparation")) } } - text: ConnectionController.connectionStateText - // enabled: !ConnectionController.isConnectionInProgress background: Item { @@ -139,6 +139,6 @@ Button { onClicked: { ServersModel.setProcessedServerIndex(ServersModel.defaultIndex) - ApiController.updateServerConfigFromApi() + ConnectionController.connectButtonClicked() } } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index c785af8b5..501dc616a 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -51,7 +51,7 @@ ListView { imageSource: "qrc:/images/controls/download.svg" showImage: !isInstalled - checkable: isInstalled && !ConnectionController.isConnected && isSupported + checkable: isInstalled && !ConnectionController.isConnected checked: proxyDefaultServerContainersModel.mapToSource(index) === ServersModel.getDefaultServerData("defaultContainer") onClicked: { @@ -64,12 +64,7 @@ ListView { containersDropDown.close() ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) } else { - if (!isSupported && isInstalled) { - PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) - return - } - - ContainersModel.setCurrentlyProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) + ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.close() diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index a5da60c76..b5049ffbc 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -59,8 +59,6 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 16 - enabled: !ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi") - text: qsTr("Site-based split tunneling") descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index b4e45e1a3..915b2f54d 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -5,6 +5,8 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" +import SortFilterProxyModel 0.2 + import InstalledAppsModel 1.0 DrawerType2 { @@ -34,7 +36,7 @@ DrawerType2 { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.bottom: addButton.top + anchors.bottom: searchField.top anchors.topMargin: 16 BackButtonType { @@ -66,7 +68,15 @@ DrawerType2 { clip: true interactive: true - model: installedAppsModel + model: SortFilterProxyModel { + id: proxyInstalledAppsModel + sourceModel: installedAppsModel + filters: RegExpFilter { + roleName: "appName" + pattern: ".*" + searchField.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + } ScrollBar.vertical: ScrollBar { id: scrollBar @@ -93,7 +103,7 @@ DrawerType2 { text: appName onCheckedChanged: { - listView.model.selectedStateChanged(index, checked) + installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked) } } @@ -113,6 +123,21 @@ DrawerType2 { } } + TextFieldWithHeaderType { + id: searchField + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: addButton.top + anchors.bottomMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + backgroundColor: "#2C2D30" + + textFieldPlaceholderText: qsTr("application name") + } + BasicButtonType { id: addButton @@ -127,7 +152,7 @@ DrawerType2 { clickedFunc: function() { PageController.showBusyIndicator(true) - AppSplitTunnelingController.addApps(listView.model.getSelectedAppsInfo()) + AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo()) PageController.showBusyIndicator(false) root.close() } diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 3a33e5cc0..5102f3e6b 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -41,7 +41,7 @@ ListView { clickedFunction: function() { if (isInstalled) { var containerIndex = root.model.mapToSource(index) - ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + ContainersModel.setProcessedContainerIndex(containerIndex) if (serviceType !== ProtocolEnum.Other) { if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { @@ -52,27 +52,6 @@ ListView { } switch (containerIndex) { - case ContainerEnum.OpenVpn: { - OpenVpnConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings) - break - } - case ContainerEnum.Xray: { - XrayConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolXraySettings) - break - } - - case ContainerEnum.WireGuard: { - WireGuardConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolWireGuardSettings) - break - } - case ContainerEnum.Awg: { - AwgConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolAwgSettings) - break - } case ContainerEnum.Ipsec: { ProtocolsModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolRaw) @@ -98,7 +77,7 @@ ListView { } } else { - ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) + ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 58d5c7fc7..05935045e 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -170,7 +170,7 @@ PageType { Header1TextType { id: collapsedButtonHeader - Layout.maximumWidth: drawer.isCollapsed ? drawer.width - 48 - 18 - 12 : drawer.width// todo + Layout.maximumWidth: drawer.width - 48 - 18 - 12 maximumLineCount: 2 elide: Qt.ElideRight diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 774217eea..29a7afdaf 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -311,34 +311,6 @@ PageType { KeyNavigation.tab: saveRestartButton } - BasicButtonType { - Layout.topMargin: 24 - Layout.leftMargin: -8 - implicitHeight: 32 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - textColor: "#EB5757" - - text: qsTr("Remove AmneziaWG") - - onClicked: { - var headerText = qsTr("Remove AmneziaWG from server?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() - } - var noButtonFunction = function() { - } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - BasicButtonType { id: saveRestartButton diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 6f8be12c0..dd3b5a0d7 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -129,7 +129,6 @@ PageType { TextFieldWithHeaderType { id: portTextField - Layout.fillWidth: true Layout.topMargin: 40 @@ -366,37 +365,6 @@ PageType { } } - BasicButtonType { - Layout.topMargin: 24 - Layout.leftMargin: -8 - implicitHeight: 32 - - visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.OpenVpn - - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - textColor: "#EB5757" - - text: qsTr("Remove OpenVPN") - - clickedFunc: function() { - var headerText = qsTr("Remove OpenVpn from server?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - BasicButtonType { id: saveRestartButton diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 4de3ffe85..0d8da97d6 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -35,7 +35,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") } } @@ -177,18 +177,18 @@ PageType { visible: ServersModel.isProcessedServerHasWriteAccess() - text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: "#EB5757" clickedFunction: function() { - var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() + InstallController.removeProcessedContainer() } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index f9447c780..4847036db 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -86,6 +86,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 40 + enabled: isPortEditable + headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 @@ -105,6 +107,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 + enabled: isCipherEditable + descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") @@ -148,6 +152,8 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: isPortEditable | isCipherEditable + text: qsTr("Save") clickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 40d8803a6..fda339407 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -114,35 +114,6 @@ PageType { checkEmptyText: true } - BasicButtonType { - Layout.topMargin: 24 - Layout.leftMargin: -8 - implicitHeight: 32 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - textColor: "#EB5757" - - text: qsTr("Remove WG") - - clickedFunc: function() { - var headerText = qsTr("Remove WG from server?") - var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - BasicButtonType { Layout.fillWidth: true Layout.topMargin: 24 @@ -163,9 +134,5 @@ PageType { } } } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 588a7f8b6..c47ff3104 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -98,43 +98,12 @@ PageType { } } - BasicButtonType { - Layout.topMargin: 24 - Layout.leftMargin: -8 - implicitHeight: 32 - - visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.Xray - - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - textColor: "#EB5757" - - text: qsTr("Remove XRay") - - clickedFunc: function() { - var headerText = qsTr("Remove XRay from server?") - var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - BasicButtonType { Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnezia") + text: qsTr("Save") onClicked: { forceActiveFocus() @@ -146,10 +115,6 @@ PageType { } } } - - QuestionDrawer { - id: questionDrawer - } } } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index c24bdd422..457c16b47 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -59,17 +59,23 @@ PageType { Layout.topMargin: 24 width: parent.width - text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: "#EB5757" clickedFunction: function() { - var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + && SettingsController.isAmneziaDnsEnabled()) { + PageController.showNotificationMessage(qsTr("Cannot remove Amnezia DNS from running server")) + } else + { + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeProcessedContainer() + } } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 91ca35309..7b8feb3c9 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -254,7 +254,7 @@ PageType { var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() + InstallController.removeProcessedContainer() } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 002e03109..9a4871fd3 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -66,7 +66,7 @@ PageType { text: qsTr("Website address") descriptionText: { - var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + var containerIndex = ContainersModel.getProcessedContainerIndex() var config = ContainersModel.getContainerConfig(containerIndex) return config[ContainerProps.containerTypeToString(containerIndex)]["site"] } @@ -132,7 +132,7 @@ PageType { var yesButtonFunction = function() { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() + InstallController.removeProcessedContainer() } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index e787d87c5..cb72d5a2c 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -20,6 +20,17 @@ import "../Components" PageType { id: root + property bool pageEnabled + + Component.onCompleted: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) + root.pageEnabled = false + } else { + root.pageEnabled = true + } + } + QtObject { id: routeMode property int allApps: 0 @@ -70,6 +81,8 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("App split tunneling") + + enabled: root.pageEnabled } SwitcherType { @@ -78,6 +91,8 @@ PageType { Layout.fillWidth: true Layout.rightMargin: 16 + enabled: root.pageEnabled + checked: AppSplitTunnelingModel.isTunnelingEnabled onToggled: { AppSplitTunnelingModel.toggleSplitTunneling(checked) @@ -99,7 +114,7 @@ PageType { headerText: qsTr("Mode") - enabled: Qt.platform.os === "android" + enabled: Qt.platform.os === "android" && root.pageEnabled listView: ListViewWithRadioButtonType { rootWidth: root.width @@ -139,6 +154,8 @@ PageType { anchors.topMargin: 16 contentHeight: col.implicitHeight + addAppButton.implicitHeight + addAppButton.anchors.bottomMargin + addAppButton.anchors.topMargin + enabled: root.pageEnabled + Column { id: col anchors.top: parent.top @@ -213,6 +230,8 @@ PageType { RowLayout { id: addAppButton + enabled: root.pageEnabled + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index af029b7bf..f02224c60 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -169,8 +169,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - SettingsController.clearSettings() - PageController.replaceStartPage() + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot reset settings during active connection")) + } else + { + SettingsController.clearSettings() + PageController.replaceStartPage() + } } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 1ba4305ac..78969564e 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -135,9 +135,14 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(filePath) - PageController.showBusyIndicator(false) + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot restore backup settings during active connection")) + } else + { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b54cd7e72..fe121c46d 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -47,7 +47,7 @@ PageType { PageController.showNotificationMessage(finishedMessage) } - function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { + function onRemoveProcessedContainerFinished(finishedMessage) { PageController.closePage() // close deInstalling page PageController.closePage() // close page with remove button PageController.showNotificationMessage(finishedMessage) @@ -84,35 +84,6 @@ PageType { property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess() - LabelWithButtonType { - visible: content.isServerWithWriteAccess - Layout.fillWidth: true - - text: qsTr("Clear Amnezia cache") - descriptionText: qsTr("May be needed when changing other settings") - - clickedFunction: function() { - var headerText = qsTr("Clear cached profiles?") - var descriptionText = qsTr("") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - PageController.showBusyIndicator(true) - SettingsController.clearCachedProfiles() - PageController.showBusyIndicator(false) - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType { - visible: content.isServerWithWriteAccess - } - LabelWithButtonType { visible: content.isServerWithWriteAccess Layout.fillWidth: true @@ -145,12 +116,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - ConnectionController.closeConnection() + PageController.showNotificationMessage(qsTr("Cannot reboot server during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.rebootProcessedServer() + PageController.showBusyIndicator(false) } - InstallController.rebootProcessedServer() - PageController.showBusyIndicator(false) } var noButtonFunction = function() { } @@ -176,12 +148,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - ConnectionController.closeConnection() + PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.removeProcessedServer() + PageController.showBusyIndicator(false) } - InstallController.removeProcessedServer() - PageController.showBusyIndicator(false) } var noButtonFunction = function() { } @@ -206,11 +179,12 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - ConnectionController.closeConnection() + PageController.showNotificationMessage(qsTr("Cannot clear server from Amnezia software during active connection")) + } else { + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeAllContainers() } - InstallController.removeAllContainers() } var noButtonFunction = function() { } @@ -237,9 +211,13 @@ PageType { var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { - PageController.showBusyIndicator(true) - ApiController.clearApiConfig() - PageController.showBusyIndicator(false) + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot reset API config during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.removeApiConfig() + PageController.showBusyIndicator(false) + } } var noButtonFunction = function() { } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 4d2000049..36b8a1df3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -34,113 +34,147 @@ PageType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 + Layout.bottomMargin: 32 - headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") } - } - FlickableType { - id: fl - anchors.top: header.bottom - anchors.left: parent.left - anchors.right: parent.right - contentHeight: content.height + ListView { + id: protocols + Layout.fillWidth: true + height: protocols.contentItem.height + clip: true + interactive: true + model: ProtocolsModel - Column { - id: content + delegate: Item { + implicitWidth: protocols.width + implicitHeight: delegateContent.implicitHeight - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 32 + ColumnLayout { + id: delegateContent - ListView { - id: protocols - width: parent.width - height: protocols.contentItem.height - clip: true - interactive: false - model: ProtocolsModel + anchors.fill: parent - delegate: Item { - implicitWidth: protocols.width - implicitHeight: delegateContent.implicitHeight + LabelWithButtonType { + id: button - ColumnLayout { - id: delegateContent + Layout.fillWidth: true - anchors.fill: parent + text: protocolName + rightImageSource: "qrc:/images/controls/chevron-right.svg" - LabelWithButtonType { - id: button - - Layout.fillWidth: true - - text: protocolName - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - switch (protocolIndex) { - case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; - } - PageController.goToPage(protocolPage); - } - - MouseArea { - anchors.fill: button - cursorShape: Qt.PointingHandCursor - enabled: false + clickedFunction: function() { + switch (protocolIndex) { + case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } + PageController.goToPage(protocolPage); } - DividerType {} + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } + } - LabelWithButtonType { - id: removeButton + LabelWithButtonType { + id: clearCacheButton - width: parent.width + Layout.fillWidth: true - visible: ServersModel.isProcessedServerHasWriteAccess() + visible: ServersModel.isProcessedServerHasWriteAccess() - text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() - textColor: "#EB5757" + text: qsTr("Clear %1 profile").arg(ContainersModel.getProcessedContainerName()) - clickedFunction: function() { - var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") + clickedFunction: function() { + var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - var yesButtonFunction = function() { + var yesButtonFunction = function() { + PageController.showBusyIndicator(true) + InstallController.clearCachedProfile() + PageController.showBusyIndicator(false) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + MouseArea { + anchors.fill: clearCacheButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: ServersModel.isProcessedServerHasWriteAccess() + } + + LabelWithButtonType { + id: removeButton + + Layout.fillWidth: true + + visible: ServersModel.isProcessedServerHasWriteAccess() + + text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Cannot remove active container")) + } else + { PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeCurrentlyProcessedContainer() + InstallController.removeProcessedContainer() } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + var noButtonFunction = function() { } - MouseArea { - anchors.fill: removeButton - cursorShape: Qt.PointingHandCursor - enabled: false - } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - DividerType {} + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: ServersModel.isProcessedServerHasWriteAccess() } } - - QuestionDrawer { - id: questionDrawer - } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index f50cda1dc..a8349f608 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -24,9 +24,7 @@ PageType { defaultActiveFocusItem: searchField.textField - property bool pageEnabled: { - return !ConnectionController.isConnected && !isServerFromApi - } + property bool pageEnabled Component.onCompleted: { if (ConnectionController.isConnected) { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index d1ba348e0..6e112c469 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -114,7 +114,7 @@ PageType { } InstallController.setShouldCreateServer(true) - InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + InstallController.setProcessedServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) PageController.showBusyIndicator(true) var isConnectionOpened = InstallController.checkSshConnection() diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 10fdadf9e..aba71560b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -159,7 +159,7 @@ PageType { clickedFunc: function() { if (root.isEasySetup) { - ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) + ContainersModel.setProcessedContainerIndex(containers.dockerContainer) PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(containers.dockerContainer, containers.containerDefaultPort, diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 6b6e79c6c..632bb7276 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -25,8 +25,9 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { - if (!ConnectionController.isConnected && !isServiceInstall) { - ServersModel.setDefaultContainer(ServersModel.processedIndex, ContainersModel.getCurrentlyProcessedContainerIndex()) + var containerIndex = ContainersModel.getProcessedContainerIndex() + if (!ConnectionController.isConnected && !ContainersModel.isServiceContainer(containerIndex)) { + ServersModel.setDefaultContainer(ServersModel.processedIndex, containerIndex) } PageController.closePage() // close installing page diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 71b33eb0a..c684db21b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -104,7 +104,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 79b48d44e..9907aa559 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -12,6 +12,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Components" +import "../Config" PageType { id: root @@ -32,7 +33,7 @@ PageType { onRevokeConfig: function(index) { PageController.showBusyIndicator(true) ExportController.revokeConfig(index, - ContainersModel.getCurrentlyProcessedContainerIndex(), + ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Config revoked")) @@ -258,7 +259,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 PageController.showBusyIndicator(true) - ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(), + ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) } @@ -418,13 +419,13 @@ PageType { protocolSelector.text = selectedText - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) + ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) fillConnectionTypeModel() if (accessTypeSelector.currentIndex === 1) { PageController.showBusyIndicator(true) - ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(), + ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) } @@ -696,7 +697,7 @@ PageType { PageController.showBusyIndicator(true) ExportController.renameClient(index, clientNameEditor.textFieldText, - ContainersModel.getCurrentlyProcessedContainerIndex(), + ContainersModel.getProcessedContainerIndex(), ServersModel.getProcessedServerCredentials()) PageController.showBusyIndicator(false) clientNameEditDrawer.close() diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8621f3def..0ad7b1127 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -108,6 +108,10 @@ PageType { PageController.showNotificationMessage(message) PageController.closePage() } + + function onCachedProfileCleared(message) { + PageController.showNotificationMessage(message) + } } Connections { diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index 2c7c695f6..e13613025 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -67,7 +67,9 @@ void SystemTrayNotificationHandler::onTranslationsUpdated() void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath) { QIcon trayIconMask(QPixmap(iconPath).scaled(128,128)); +#ifndef Q_OS_MAC trayIconMask.setIsMask(true); +#endif m_systemTrayIcon.setIcon(trayIconMask); } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index b81840534..512dc2298 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "core/controllers/serverController.h" @@ -30,9 +29,8 @@ #include "core/networkUtilities.h" #include "vpnconnection.h" -VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, - QObject *parent) - : QObject(parent), m_settings(settings), m_configurator(configurator), m_checkTimer(new QTimer(this)) +VpnConnection::VpnConnection(std::shared_ptr settings, QObject *parent) + : QObject(parent), m_settings(settings), m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); #ifdef Q_OS_IOS @@ -212,109 +210,8 @@ ErrorCode VpnConnection::lastError() const return m_vpnProtocol.data()->lastError(); } -QMap VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) -{ - QMap configs; - for (Proto proto : ProtocolProps::allProtocols()) { - - QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)) - .toObject() - .value(config_key::last_config) - .toString(); - - if (!cfg.isEmpty()) - configs.insert(proto, cfg); - } - return configs; -} - -QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, - Proto proto, ErrorCode *errorCode) -{ - QMap lastVpnConfig = getLastVpnConfig(containerConfig); - - QString configData; - if (lastVpnConfig.contains(proto)) { - configData = lastVpnConfig.value(proto); - configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); - } else { - QString clientId; - configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, clientId, errorCode); - - if (errorCode && *errorCode) { - return ""; - } - - QString configDataBeforeLocalProcessing = configData; - - configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); - - if (serverIndex >= 0) { - qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container; - QJsonObject protoObject = m_settings->protocolConfig(serverIndex, container, proto); - protoObject.insert(config_key::last_config, configDataBeforeLocalProcessing); - m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); - } - - if ((container != DockerContainer::Cloak && container != DockerContainer::ShadowSocks) || - ((container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) && proto == Proto::OpenVpn)) { - QEventLoop wait; - emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials); - QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit); - wait.exec(); - } - } - - return configData; -} - -QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, - ErrorCode *errorCode) -{ - QJsonObject vpnConfiguration; - vpnConfiguration[config_key::serverIndex] = serverIndex; - - for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { - auto s = m_settings->server(serverIndex); - if (m_settings->server(serverIndex).value(config_key::configVersion).toInt() && - container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) { - continue; - } - - QJsonObject vpnConfigData = - QJsonDocument::fromJson(createVpnConfigurationForProto(serverIndex, credentials, container, - containerConfig, proto, errorCode).toUtf8()).object(); - - if (errorCode && *errorCode) { - return {}; - } - - vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData); - } - - Proto proto = ContainerProps::defaultProtocol(container); - vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto); - - auto dns = m_configurator->getDnsForConfig(serverIndex); - - vpnConfiguration[config_key::dns1] = dns.first; - vpnConfiguration[config_key::dns2] = dns.second; - - const QJsonObject &server = m_settings->server(serverIndex); - vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString(); - vpnConfiguration[config_key::description] = server.value(config_key::description).toString(); - - vpnConfiguration[config_key::configVersion] = server.value(config_key::configVersion).toInt(); - // TODO: try to get hostName, port, description for 3rd party configs - // vpnConfiguration[config_key::port] = ...; - - return vpnConfiguration; -} - void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig) + const QJsonObject &vpnConfiguration) { qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") .arg(serverIndex) @@ -346,15 +243,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede } #endif - ErrorCode e = ErrorCode::NoError; - - m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); - - if (e) { - emit connectionStateChanged(Vpn::ConnectionState::Error); - return; - } - + m_vpnConfiguration = vpnConfiguration; appendSplitTunnelingConfig(); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) @@ -378,8 +267,8 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede createProtocolConnections(); - e = m_vpnProtocol.data()->start(); - if (e) + ErrorCode errorCode = m_vpnProtocol.data()->start(); + if (errorCode != ErrorCode::NoError) emit connectionStateChanged(Vpn::ConnectionState::Error); } @@ -412,7 +301,11 @@ void VpnConnection::appendSplitTunnelingConfig() } } - auto routeMode = m_settings->routeMode(); + Settings::RouteMode routeMode = Settings::RouteMode::VpnAllSites; + if (m_settings->getSitesSplitTunnelingEnabled()) { + routeMode = m_settings->routeMode(); + } + auto sites = m_settings->getVpnIps(routeMode); QJsonArray sitesJsonArray; @@ -429,7 +322,11 @@ void VpnConnection::appendSplitTunnelingConfig() m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); - auto appsRouteMode = m_settings->getAppsRouteMode(); + Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps; + if (m_settings->getAppsSplitTunnelingEnabled()) { + appsRouteMode = m_settings->getAppsRouteMode(); + } + auto apps = m_settings->getVpnApps(appsRouteMode); QJsonArray appsJsonArray; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 3cc670d56..33c1b67a9 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -11,7 +11,6 @@ #include "core/defs.h" #include "settings.h" - #ifdef AMNEZIA_DESKTOP #include "core/ipcclient.h" #endif @@ -20,9 +19,6 @@ #include "protocols/android_vpnprotocol.h" #endif -class VpnConfigurator; -class ServerController; - using namespace amnezia; class VpnConnection : public QObject @@ -30,24 +26,13 @@ class VpnConnection : public QObject Q_OBJECT public: - explicit VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, QObject* parent = nullptr); + explicit VpnConnection(std::shared_ptr settings, QObject* parent = nullptr); ~VpnConnection() override; static QString bytesPerSecToText(quint64 bytes); ErrorCode lastError() const; - static QMap getLastVpnConfig(const QJsonObject &containerConfig); - QString createVpnConfigurationForProto(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, - ErrorCode *errorCode = nullptr); - - QJsonObject createVpnConfiguration(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); - - bool isConnected() const; bool isDisconnected() const; @@ -63,7 +48,7 @@ public: public slots: void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void disconnectFromVpn(); @@ -88,8 +73,6 @@ protected: private: std::shared_ptr m_settings; - std::shared_ptr m_configurator; - QJsonObject m_vpnConfiguration; QJsonObject m_routeMode; QString m_remoteAddress;