merge dev

This commit is contained in:
dranik
2026-04-30 10:45:41 +03:00
472 changed files with 26051 additions and 17334 deletions

View File

@@ -2,7 +2,7 @@
/client/3rd-prebuild
/client/android
/client/cmake
/client/core/serialization
/client/core/utils/serialization
/client/daemon
/client/fonts
/client/images

View File

@@ -17,6 +17,7 @@ jobs:
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -98,6 +99,7 @@ jobs:
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -204,6 +206,7 @@ jobs:
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -318,6 +321,7 @@ jobs:
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -395,6 +399,7 @@ jobs:
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -477,6 +482,7 @@ jobs:
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
@@ -545,6 +551,7 @@ jobs:
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}

View File

@@ -17,6 +17,7 @@ jobs:
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.15.2)
set(AMNEZIAVPN_VERSION 4.8.15.4)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2119)
set(APP_ANDROID_VERSION_CODE 2120)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -25,6 +25,7 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
@@ -65,7 +66,12 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
qt6_add_resources(QRC ${QRC}
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
)
# -- i18n begin
set(CMAKE_AUTORCC ON)
@@ -231,6 +237,12 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
)
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(tests)
endif()
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).

View File

@@ -1,4 +1,4 @@
#include "amnezia_application.h"
#include "amneziaApplication.h"
#include <QClipboard>
#include <QFontDatabase>
@@ -15,17 +15,17 @@
#include <QEvent>
#include <QDir>
#include <QSettings>
#include <QtQuick/QQuickWindow>
#include <QWindow>
#include "core/protocols/qmlRegisterProtocols.h"
#include "logger.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
bool AmneziaApplication::m_forceQuit = false;
@@ -54,7 +54,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this);
m_nam = new QNetworkAccessManager(this);
}
@@ -132,7 +132,7 @@ void AmneziaApplication::init()
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -153,16 +153,6 @@ void AmneziaApplication::init()
m_coreController->setQmlRoot();
bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID
if (enabled) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
@@ -207,13 +197,11 @@ void AmneziaApplication::registerTypes()
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
using namespace amnezia::ProtocolEnumNS;
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
@@ -227,6 +215,7 @@ void AmneziaApplication::registerTypes()
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
amnezia::declareQmlProtocolEnum();
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}

View File

@@ -14,8 +14,10 @@
#include <QClipboard>
#include "core/controllers/coreController.h"
#include "settings.h"
#include "vpnconnection.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
#include "ui/models/containerProps.h"
#include "ui/models/protocolProps.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@@ -51,7 +53,7 @@ public slots:
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
SecureQSettings* m_settings;
QScopedPointer<CoreController> m_coreController;

View File

@@ -111,7 +111,6 @@ dependencies {
implementation(project(":wireguard"))
implementation(project(":awg"))
implementation(project(":openvpn"))
implementation(project(":cloak"))
implementation(project(":xray"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)

View File

@@ -1,18 +0,0 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
}
kotlin {
jvmToolchain(17)
}
android {
namespace = "org.amnezia.vpn.protocol.cloak"
}
dependencies {
compileOnly(project(":utils"))
compileOnly(project(":protocolApi"))
implementation(project(":openvpn"))
}

View File

@@ -1,45 +0,0 @@
package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.json.JSONObject
class Cloak : OpenVpn() {
override fun internalInit() {
super.internalInit()
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
}
override fun parseConfig(config: JSONObject): ClientAPI_Config {
val openVpnConfig = ClientAPI_Config()
val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config")
val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data"))
val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT)
val configStr = "$openVpnConfigStr\n<cloak>\n$cloakConfigStr\n</cloak>\n"
openVpnConfig.usePluggableTransports = true
openVpnConfig.content = configStr
return openVpnConfig
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn")
if (cloakConfigJson.has("port")) {
val port = cloakConfigJson["port"]
cloakConfigJson.remove("port")
cloakConfigJson.put("RemotePort", port)
}
if (cloakConfigJson.has("remote")) {
val remote = cloakConfigJson["remote"]
cloakConfigJson.remove("remote")
cloakConfigJson.put("RemoteHost", remote)
}
return cloakConfigJson
}
}

View File

@@ -35,7 +35,6 @@ include(":protocolApi")
include(":wireguard")
include(":awg")
include(":openvpn")
include(":cloak")
include(":xray")
include(":xray:libXray")

View File

@@ -2,7 +2,6 @@ package org.amnezia.vpn
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.cloak.Cloak
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.protocol.xray.Xray
@@ -36,14 +35,6 @@ enum class VpnProto(
override fun createProtocol(): Protocol = OpenVpn()
},
CLOAK(
"Cloak",
"org.amnezia.vpn:amneziaOpenVpnService",
OpenVpnService::class.java
) {
override fun createProtocol(): Protocol = Cloak()
},
XRAY(
"XRay",
"org.amnezia.vpn:amneziaXrayService",
@@ -72,4 +63,4 @@ enum class VpnProto(
companion object {
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
}
}
}

View File

@@ -77,13 +77,13 @@ class Xray : Protocol() {
return
}
val xrayJsonConfig = config.optJSONObject("xray_config_data")
val xrayConfigData = config.optJSONObject("xray_config_data")
?: config.optJSONObject("ssxray_config_data")
?: throw BadConfigException("config_data not found")
val xrayJsonConfig = JSONObject(xrayConfigData.optString("config"))
// Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present.
ensureInboundAuth(xrayJsonConfig)
val xrayConfig = parseConfig(config, xrayJsonConfig)
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })

View File

@@ -31,15 +31,15 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.cpp
)
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})

View File

@@ -28,11 +28,11 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.mm
)

View File

@@ -1,34 +1,68 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/migrations.h
${CLIENT_ROOT_DIR}/core/utils/migrations.h
${CLIENT_ROOT_DIR}/../ipc/ipc.h
${CLIENT_ROOT_DIR}/amnezia_application.h
${CLIENT_ROOT_DIR}/containers/containers_defs.h
${CLIENT_ROOT_DIR}/core/defs.h
${CLIENT_ROOT_DIR}/core/errorstrings.h
${CLIENT_ROOT_DIR}/core/scripts_registry.h
${CLIENT_ROOT_DIR}/core/server_defs.h
${CLIENT_ROOT_DIR}/core/api/apiDefs.h
${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
${CLIENT_ROOT_DIR}/amneziaApplication.h
${CLIENT_ROOT_DIR}/core/utils/errorCodes.h
${CLIENT_ROOT_DIR}/core/utils/routeModes.h
${CLIENT_ROOT_DIR}/core/utils/commonStructs.h
${CLIENT_ROOT_DIR}/core/utils/containerEnum.h
${CLIENT_ROOT_DIR}/core/utils/protocolEnum.h
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.h
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.h
${CLIENT_ROOT_DIR}/core/utils/constants/configKeys.h
${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h
${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h
${CLIENT_ROOT_DIR}/core/utils/errorStrings.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
${CLIENT_ROOT_DIR}/core/controllers/serverController.h
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
${CLIENT_ROOT_DIR}/ui/pages.h
${CLIENT_ROOT_DIR}/ui/qautostart.h
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h
${CLIENT_ROOT_DIR}/core/controllers/serversController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.h
${CLIENT_ROOT_DIR}/core/installers/installerBase.h
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.h
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.h
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.h
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.h
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
${CLIENT_ROOT_DIR}/core/controllers/connectionController.h
${CLIENT_ROOT_DIR}/core/controllers/settingsController.h
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.h
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.h
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.h
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
${CLIENT_ROOT_DIR}/ui/utils/pages.h
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/sshclient.h
${CLIENT_ROOT_DIR}/core/networkUtilities.h
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
${CLIENT_ROOT_DIR}/core/utils/utilities.h
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
${CLIENT_ROOT_DIR}/core/utils/constants.h
)
# Mozilla headres
@@ -47,39 +81,64 @@ endif()
if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
)
endif()
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/migrations.cpp
${CLIENT_ROOT_DIR}/amnezia_application.cpp
${CLIENT_ROOT_DIR}/containers/containers_defs.cpp
${CLIENT_ROOT_DIR}/core/errorstrings.cpp
${CLIENT_ROOT_DIR}/core/scripts_registry.cpp
${CLIENT_ROOT_DIR}/core/server_defs.cpp
${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/migrations.cpp
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
${CLIENT_ROOT_DIR}/core/utils/errorStrings.cpp
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.cpp
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.cpp
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.cpp
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
${CLIENT_ROOT_DIR}/ui/qautostart.cpp
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
${CLIENT_ROOT_DIR}/core/sshclient.cpp
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp
${CLIENT_ROOT_DIR}/core/serialization/vless.cpp
${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp
${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp
${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.cpp
${CLIENT_ROOT_DIR}/core/installers/installerBase.cpp
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
${CLIENT_ROOT_DIR}/core/controllers/connectionController.cpp
${CLIENT_ROOT_DIR}/core/controllers/settingsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.cpp
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ssd.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vless.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/trojan.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
)
# Mozilla sources
@@ -100,29 +159,41 @@ if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp)
set(COMMON_FILES_H
${CLIENT_ROOT_DIR}/amneziaApplication.h
${CLIENT_ROOT_DIR}/secureQSettings.h
${CLIENT_ROOT_DIR}/vpnConnection.h
)
set(COMMON_FILES_CPP
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
${CLIENT_ROOT_DIR}/secureQSettings.cpp
${CLIENT_ROOT_DIR}/vpnConnection.cpp
)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.cpp)
file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h)
file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.h
@@ -140,16 +211,21 @@ file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/controllers/*.h
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.h
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.h
)
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.cpp
)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${CORE_MODELS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
)
@@ -157,17 +233,18 @@ set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${CORE_MODELS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
)
if(WIN32)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.cpp
)
set(RESOURCES ${RESOURCES}
@@ -180,37 +257,33 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/ipcclient.h
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
${CLIENT_ROOT_DIR}/core/utils/ipcClient.h
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/utils/ipcClient.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.cpp
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.cpp
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.cpp
)
endif()
if(APPLE AND MACOS_NE)
# Include only the tray notification handler in NE builds
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
)
endif()

View File

@@ -1,59 +0,0 @@
#include "awg_configurator.h"
#include "protocols/protocols_defs.h"
#include <QJsonDocument>
#include <QJsonObject>
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: WireguardConfigurator(settings, serverController, true, parent)
{
}
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString();
QMap<QString, QString> configMap;
auto configLines = awgConfig.split("\n");
for (auto &line : configLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount);
jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize);
jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize);
jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize);
jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize);
jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader);
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
if (container == DockerContainer::Awg2) {
jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
}
jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson();
}

View File

@@ -1,18 +0,0 @@
#ifndef AWGCONFIGURATOR_H
#define AWGCONFIGURATOR_H
#include <QObject>
#include "wireguard_configurator.h"
class AwgConfigurator : public WireguardConfigurator
{
Q_OBJECT
public:
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // AWGCONFIGURATOR_H

View File

@@ -1,51 +0,0 @@
#include "cloak_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
QString cloakPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
cloakPublicKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QString cloakBypassUid =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
cloakBypassUid.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QJsonObject config;
config.insert("Transport", "direct");
config.insert("ProxyMethod", "openvpn");
config.insert("EncryptionMethod", "aes-gcm");
config.insert("UID", cloakBypassUid);
config.insert("PublicKey", cloakPublicKey);
config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS");
config.insert("NumConn", 1);
config.insert("BrowserSig", "chrome");
config.insert("StreamTimeout", 300);
config.insert("RemoteHost", credentials.hostName);
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
return textCfg;
}

View File

@@ -1,20 +0,0 @@
#ifndef CLOAK_CONFIGURATOR_H
#define CLOAK_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
using namespace amnezia;
class CloakConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // CLOAK_CONFIGURATOR_H

View File

@@ -1,26 +0,0 @@
#include "configurator_base.h"
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: QObject { parent }, m_settings(settings), m_serverController(serverController)
{
}
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
{
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
}

View File

@@ -1,33 +0,0 @@
#ifndef CONFIGURATORBASE_H
#define CONFIGURATORBASE_H
#include <QObject>
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "core/controllers/serverController.h"
#include "settings.h"
class ConfiguratorBase : public QObject
{
Q_OBJECT
public:
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode) = 0;
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
protected:
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServerController> m_serverController;
};
#endif // CONFIGURATORBASE_H

View File

@@ -1,35 +0,0 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class Ikev2Configurator : public ConfiguratorBase
{
Q_OBJECT
public:
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
struct ConnectionData {
QByteArray clientCert; // p12 client cert
QByteArray caCert; // p12 server cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
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);
};
#endif // IKEV2_CONFIGURATOR_H

View File

@@ -1,43 +0,0 @@
#ifndef OPENVPN_CONFIGURATOR_H
#define OPENVPN_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
struct ConnectionData
{
QString clientId;
QString request; // certificate request
QString privKey; // client private key
QString clientCert; // client signed certificate
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData createCertRequest();
private:
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode &errorCode);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
};
#endif // OPENVPN_CONFIGURATOR_H

View File

@@ -1,40 +0,0 @@
#include "shadowsocks_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
QString ssKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
ssKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QJsonObject config;
config.insert("server", credentials.hostName);
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
config.insert("password", ssKey);
config.insert("timeout", 60);
config.insert("method", "$SHADOWSOCKS_CIPHER");
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg;
}

View File

@@ -1,19 +0,0 @@
#ifndef SHADOWSOCKS_CONFIGURATOR_H
#define SHADOWSOCKS_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class ShadowSocksConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // SHADOWSOCKS_CONFIGURATOR_H

View File

@@ -1,112 +0,0 @@
#include "ssh_configurator.h"
#include <QDebug>
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "core/server_defs.h"
#include "utilities.h"
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
QTemporaryFile tmp;
#ifdef QT_DEBUG
tmp.setAutoRemove(false);
#endif
tmp.open();
tmp.write(key.toUtf8());
tmp.close();
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
#else
p.setProgram("ssh-keygen");
p.setArguments(QStringList() << "-p"
<< "-P"
<< ""
<< "-N"
<< ""
<< "-m"
<< "pem"
<< "-f" << tmp.fileName());
#endif
p.start();
p.waitForFinished();
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
tmp.open();
return tmp.readAll();
#else
return key;
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
#ifdef Q_OS_WIN
p->setProcessEnvironment(prepareEnv());
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe");
if (credentials.secretData.contains("PRIVATE KEY")) {
// todo: connect by key
// p->setNativeArguments(QString("%1@%2")
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
} else {
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
}
#else
p->setProgram("/bin/bash");
#endif
p->startDetached();
#endif
}
QProcessEnvironment SshConfigurator::prepareEnv()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnvVar = env.value("PATH");
#ifdef Q_OS_WIN
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
// qDebug().noquote() << "ENV PATH" << pathEnvVar;
return env;
}

View File

@@ -1,22 +0,0 @@
#ifndef SSH_CONFIGURATOR_H
#define SSH_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class SshConfigurator : ConfiguratorBase
{
Q_OBJECT
public:
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QProcessEnvironment prepareEnv();
QString convertOpenSShKey(const QString &key);
void openSshTerminal(const ServerCredentials &credentials);
};
#endif // SSH_CONFIGURATOR_H

View File

@@ -1,54 +0,0 @@
#ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H
#include <QHostAddress>
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
#include "core/scripts_registry.h"
class WireguardConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
bool isAwg, QObject *parent = nullptr);
struct ConnectionData
{
QString clientPrivKey; // client private key
QString clientPubKey; // client public key
QString clientIP; // internal client IP address
QString serverPubKey; // tls-auth key
QString pskKey; // preshared key
QString host; // host ip
QString port;
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData genClientKeys();
private:
QList<QHostAddress> getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
bool m_isAwg;
QString m_serverConfigPath;
QString m_serverPublicKeyPath;
QString m_serverPskKeyPath;
amnezia::ProtocolScriptType m_configTemplate;
QString m_protocolName;
QString m_defaultPort;
};
#endif // WIREGUARD_CONFIGURATOR_H

View File

@@ -1,23 +0,0 @@
#ifndef XRAY_CONFIGURATOR_H
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class XrayConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
private:
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -1,94 +0,0 @@
#ifndef CONTAINERS_DEFS_H
#define CONTAINERS_DEFS_H
#include <QObject>
#include <QQmlEngine>
#include "../protocols/protocols_defs.h"
using namespace amnezia;
namespace amnezia
{
namespace ContainerEnumNS
{
Q_NAMESPACE
enum DockerContainer {
None = 0,
Awg,
Awg2,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,
// non-vpn
TorWebSite,
Dns,
Sftp,
Socks5Proxy
};
Q_ENUM_NS(DockerContainer)
} // namespace ContainerEnumNS
using namespace ContainerEnumNS;
using namespace ProtocolEnumNS;
class ContainerProps : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerHumanNames();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDescriptions();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDetailedDescriptions();
// these protocols will be displayed in container settings
Q_INVOKABLE static QVector<amnezia::Proto> protocolsForContainer(amnezia::DockerContainer container);
Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c);
// binding between Docker container and main protocol of given container
// it may be changed fot future containers :)
Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c);
Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c);
Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c);
static bool isEasySetupContainer(amnezia::DockerContainer container);
static QString easySetupHeader(amnezia::DockerContainer container);
static QString easySetupDescription(amnezia::DockerContainer container);
static int easySetupOrder(amnezia::DockerContainer container);
static bool isShareable(amnezia::DockerContainer container);
static bool isAwgContainer(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container);
};
static void declareQmlContainerEnum()
{
qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum",
"Error: only enums");
}
} // namespace amnezia
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
#endif // CONTAINERS_DEFS_H

View File

@@ -0,0 +1,109 @@
#include "awgConfigurator.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
using namespace amnezia;
AwgConfigurator::AwgConfigurator(SshSession* sshSession, QObject *parent)
: WireguardConfigurator(sshSession, true, parent)
{
}
ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const AwgServerConfig* serverConfig = nullptr;
const AwgClientConfig* clientConfig = nullptr;
if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
serverConfig = &awgProtocolConfig->serverConfig;
if (awgProtocolConfig->clientConfig.has_value()) {
clientConfig = &awgProtocolConfig->clientConfig.value();
}
}
ProtocolConfig wireguardConfig = WireguardConfigurator::createConfig(credentials, container, containerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return AwgProtocolConfig{};
}
WireGuardProtocolConfig* wgConfig = wireguardConfig.as<WireGuardProtocolConfig>();
if (!wgConfig || !wgConfig->clientConfig.has_value()) {
errorCode = ErrorCode::InternalError;
return AwgProtocolConfig{};
}
QString awgConfig = wgConfig->clientConfig->nativeConfig;
QMap<QString, QString> configMap;
auto configLines = awgConfig.split("\n");
for (auto &line : configLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
AwgProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
AwgClientConfig newClientConfig;
newClientConfig.nativeConfig = awgConfig;
newClientConfig.hostName = wgConfig->clientConfig->hostName;
newClientConfig.port = wgConfig->clientConfig->port;
newClientConfig.clientIp = wgConfig->clientConfig->clientIp;
newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey;
newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey;
newClientConfig.serverPublicKey = wgConfig->clientConfig->serverPublicKey;
newClientConfig.presharedKey = wgConfig->clientConfig->presharedKey;
newClientConfig.clientId = wgConfig->clientConfig->clientId;
newClientConfig.allowedIps = wgConfig->clientConfig->allowedIps;
newClientConfig.persistentKeepAlive = wgConfig->clientConfig->persistentKeepAlive;
QString mtu = protocols::awg::defaultMtu;
if (clientConfig && !clientConfig->mtu.isEmpty()) {
mtu = clientConfig->mtu;
}
newClientConfig.mtu = mtu;
newClientConfig.junkPacketCount = configMap.value(configKey::junkPacketCount);
newClientConfig.junkPacketMinSize = configMap.value(configKey::junkPacketMinSize);
newClientConfig.junkPacketMaxSize = configMap.value(configKey::junkPacketMaxSize);
newClientConfig.initPacketJunkSize = configMap.value(configKey::initPacketJunkSize);
newClientConfig.responsePacketJunkSize = configMap.value(configKey::responsePacketJunkSize);
newClientConfig.initPacketMagicHeader = configMap.value(configKey::initPacketMagicHeader);
newClientConfig.responsePacketMagicHeader = configMap.value(configKey::responsePacketMagicHeader);
newClientConfig.underloadPacketMagicHeader = configMap.value(configKey::underloadPacketMagicHeader);
newClientConfig.transportPacketMagicHeader = configMap.value(configKey::transportPacketMagicHeader);
newClientConfig.specialJunk1 = configMap.value(configKey::specialJunk1);
newClientConfig.specialJunk2 = configMap.value(configKey::specialJunk2);
newClientConfig.specialJunk3 = configMap.value(configKey::specialJunk3);
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
if (container == DockerContainer::Awg2) {
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
}
newClientConfig.isObfuscationEnabled = false;
protocolConfig.setClientConfig(newClientConfig);
return protocolConfig;
}

View File

@@ -0,0 +1,20 @@
#ifndef AWGCONFIGURATOR_H
#define AWGCONFIGURATOR_H
#include <QObject>
#include "wireguardConfigurator.h"
class AwgConfigurator : public WireguardConfigurator
{
Q_OBJECT
public:
AwgConfigurator(SshSession* sshSession, QObject *parent = nullptr);
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
};
#endif // AWGCONFIGURATOR_H

View File

@@ -0,0 +1,50 @@
#include "configuratorBase.h"
#include "core/configurators/awgConfigurator.h"
#include "core/configurators/ikev2Configurator.h"
#include "core/configurators/openVpnConfigurator.h"
#include "core/configurators/wireguardConfigurator.h"
#include "core/configurators/xrayConfigurator.h"
using namespace amnezia;
ConfiguratorBase::ConfiguratorBase(SshSession* sshSession, QObject *parent)
: QObject { parent }, m_sshSession(sshSession)
{
}
QScopedPointer<ConfiguratorBase> ConfiguratorBase::create(Proto protocol,
SshSession* sshSession)
{
switch (protocol) {
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(sshSession));
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(sshSession, false));
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(sshSession));
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(sshSession));
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
default: return QScopedPointer<ConfiguratorBase>();
}
}
ProtocolConfig ConfiguratorBase::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
return protocolConfig;
}
ProtocolConfig ConfiguratorBase::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
return protocolConfig;
}
void ConfiguratorBase::applyDnsToNativeConfig(const DnsSettings &dns, ProtocolConfig &protocolConfig)
{
QString config = protocolConfig.nativeConfig();
config.replace("$PRIMARY_DNS", dns.primaryDns);
config.replace("$SECONDARY_DNS", dns.secondaryDns);
protocolConfig.setNativeConfig(config);
}

View File

@@ -0,0 +1,43 @@
#ifndef CONFIGURATORBASE_H
#define CONFIGURATORBASE_H
#include <QObject>
#include <QScopedPointer>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
class SshSession;
class ConfiguratorBase : public QObject
{
Q_OBJECT
public:
explicit ConfiguratorBase(SshSession* sshSession, QObject *parent = nullptr);
static QScopedPointer<ConfiguratorBase> create(amnezia::Proto protocol,
SshSession* sshSession);
virtual amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) = 0;
virtual amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig);
virtual amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig);
protected:
void applyDnsToNativeConfig(const amnezia::DnsSettings &dns, amnezia::ProtocolConfig &protocolConfig);
SshSession* m_sshSession;
};
#endif // CONFIGURATORBASE_H

View File

@@ -1,4 +1,4 @@
#include "ikev2_configurator.h"
#include "ikev2Configurator.h"
#include <QDebug>
#include <QJsonDocument>
@@ -8,14 +8,16 @@
#include <QTemporaryFile>
#include <QUuid>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "utilities.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
Ikev2Configurator::Ikev2Configurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
{
}
@@ -25,7 +27,6 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
connData.clientId = Utils::getRandomString(16);
connData.password = Utils::getRandomString(16);
connData.password = "";
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
@@ -39,14 +40,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId);
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
errorCode = m_sshSession->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);
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
errorCode = m_sshSession->runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
connData.clientCert = m_sshSession->getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert = m_sshSession->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();
@@ -54,26 +55,51 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData;
}
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
ProtocolConfig Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
Q_UNUSED(containerConfig)
const Ikev2ServerConfig* serverConfig = nullptr;
if (auto* ikev2Config = containerConfig.protocolConfig.as<Ikev2ProtocolConfig>()) {
serverConfig = &ikev2Config->serverConfig;
}
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return Ikev2ProtocolConfig{};
}
return genIkev2Config(connData);
QString configJson = genIkev2Config(connData);
QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8());
QJsonObject configObj = doc.object();
Ikev2ProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
} else {
protocolConfig.serverConfig.hostName = connData.host;
}
Ikev2ClientConfig clientConfig;
clientConfig.nativeConfig = configJson;
clientConfig.hostName = connData.host;
clientConfig.userName = connData.clientId;
clientConfig.cert = QString(connData.clientCert.toBase64());
clientConfig.password = connData.password;
clientConfig.clientId = connData.clientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
{
QJsonObject config;
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.clientCert.toBase64());
config[config_key::password] = connData.password;
config[configKey::hostName] = connData.host;
config[configKey::userName] = connData.clientId;
config[configKey::cert] = QString(connData.clientCert.toBase64());
config[configKey::password] = connData.password;
return QJsonDocument(config).toJson();
}

View File

@@ -0,0 +1,39 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class Ikev2Configurator : public ConfiguratorBase
{
Q_OBJECT
public:
Ikev2Configurator(SshSession* sshSession, QObject *parent = nullptr);
struct ConnectionData {
QByteArray clientCert; // p12 client cert
QByteArray caCert; // p12 server cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
ConnectionData prepareIkev2Config(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, amnezia::ErrorCode &errorCode);
};
#endif // IKEV2_CONFIGURATOR_H

View File

@@ -1,8 +1,9 @@
#include "openvpn_configurator.h"
#include "openVpnConfigurator.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
@@ -13,26 +14,34 @@
#include <QApplication>
#endif
#include "core/networkUtilities.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "settings.h"
#include "utilities.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/openVpnProtocolConfig.h"
using namespace amnezia;
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
OpenVpnConfigurator::OpenVpnConfigurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
{
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode)
DockerContainer container,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
@@ -44,26 +53,26 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (errorCode != ErrorCode::NoError) {
return connData;
}
errorCode = signCert(container, credentials, connData.clientId);
errorCode = signCert(container, credentials, dnsSettings, connData.clientId);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.caCert =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = m_serverController->getTextFileFromContainer(
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = m_sshSession->getTextFileFromContainer(
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
errorCode = ErrorCode::SshScpFailureError;
@@ -72,15 +81,23 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
const OpenVpnServerConfig* serverConfig = nullptr;
if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) {
serverConfig = &openVpnProtocolConfig->serverConfig;
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), vars);
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
ConnectionData connData = prepareOpenVpnConfig(credentials, container, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return OpenVpnProtocolConfig{};
}
auto sanitizeStaticKey = [](const QString &key) {
@@ -116,42 +133,45 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
config.replace("block-outside-dns", "");
#endif
QJsonObject jConfig;
jConfig[config_key::config] = config;
jConfig[config_key::clientId] = connData.clientId;
return QJsonDocument(jConfig).toJson();
OpenVpnProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
OpenVpnClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.clientId = connData.clientId;
clientConfig.blockOutsideDns = false;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
applyDnsToNativeConfig(settings.dns, protocolConfig);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
QString config = protocolConfig.nativeConfig();
if (!isApiConfig) {
if (!settings.isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
config.replace(dnsRegex, "");
}
if (!m_settings->isSitesSplitTunnelingEnabled()) {
if (!settings.splitTunneling.isSitesSplitTunnelingEnabled) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
config.append("block-ipv6\n");
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
} else if (settings.splitTunneling.routeMode == RouteMode::VpnOnlyForwardSites) {
// no redirect-gateway
} else if (settings.splitTunneling.routeMode == RouteMode::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif
config.append("block-ipv6\n");
}
@@ -162,64 +182,57 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
#endif
#if (defined(MZ_MACOS) || defined(MZ_LINUX))
QString dnsConf = QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n")
.arg(qApp->applicationDirPath());
config.append(dnsConf);
config.append(QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n")
.arg(qApp->applicationDirPath()));
#endif
json[config_key::config] = config;
return QJsonDocument(json).toJson();
protocolConfig.setNativeConfig(config);
return protocolConfig;
}
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
ProtocolConfig OpenVpnConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
applyDnsToNativeConfig(settings.dns, protocolConfig);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
QString config = protocolConfig.nativeConfig();
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
config.replace(dnsRegex, "");
}
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("block-ipv6\n");
// remove block-outside-dns for all exported configs
config.replace("block-outside-dns", "");
json[config_key::config] = config;
return QJsonDocument(json).toJson();
protocolConfig.setNativeConfig(config);
return protocolConfig;
}
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials,
const DnsSettings &dnsSettings, QString clientId)
{
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
"easyrsa import-req %2/%3.req %3\"")
.arg(ContainerProps::containerToString(container))
.arg(ContainerUtils::containerToString(container))
.arg(amnezia::protocols::openvpn::clientsDirPath)
.arg(clientId);
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
"easyrsa sign-req client %2\"")
.arg(ContainerProps::containerToString(container))
.arg(ContainerUtils::containerToString(container))
.arg(clientId);
QStringList scriptList { script_import, script_sign };
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
QString script = m_sshSession->replaceVars(scriptList.join("\n"), amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns));
return m_serverController->runScript(credentials, script);
return m_sshSession->runScript(credentials, script);
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()

View File

@@ -0,0 +1,49 @@
#ifndef OPENVPN_CONFIGURATOR_H
#define OPENVPN_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class OpenVpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
OpenVpnConfigurator(SshSession* sshSession, QObject *parent = nullptr);
struct ConnectionData
{
QString clientId;
QString request; // certificate request
QString privKey; // client private key
QString clientCert; // client signed certificate
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
static ConnectionData createCertRequest();
private:
ConnectionData prepareOpenVpnConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
amnezia::ErrorCode signCert(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
const amnezia::DnsSettings &dnsSettings, QString clientId);
};
#endif // OPENVPN_CONFIGURATOR_H

View File

@@ -1,4 +1,4 @@
#include "wireguard_configurator.h"
#include "wireguardConfigurator.h"
#include <QDebug>
#include <QJsonDocument>
@@ -13,17 +13,26 @@
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "settings.h"
#include "utilities.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/wireGuardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include <QJsonArray>
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
const QSharedPointer<ServerController> &serverController, bool isAwg,
using namespace amnezia;
WireguardConfigurator::WireguardConfigurator(SshSession* sshSession, bool isAwg,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
: ConfiguratorBase(sshSession, parent), m_isAwg(isAwg)
{
m_serverConfigPath =
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
@@ -33,8 +42,8 @@ WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
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;
m_protocolName = m_isAwg ? configKey::awg : configKey::wireguard;
m_defaultPort = m_isAwg ? protocols::awg::defaultPort : protocols::wireguard::defaultPort;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
@@ -91,12 +100,21 @@ QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig,
const WireGuardServerConfig* serverConfig,
const AwgServerConfig* awgServerConfig,
const DnsSettings &dnsSettings,
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);
QString portStr = m_defaultPort;
if (serverConfig && !serverConfig->port.isEmpty()) {
portStr = serverConfig->port;
} else if (awgServerConfig && !awgServerConfig->port.isEmpty()) {
portStr = awgServerConfig->port;
}
connData.port = portStr;
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
errorCode = ErrorCode::InternalError;
@@ -114,7 +132,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return ErrorCode::NoError;
};
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
errorCode = m_sshSession->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return connData;
}
@@ -123,11 +141,14 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
QHostAddress nextIp = [&] {
QHostAddress result;
QHostAddress lastIp;
QString subnetAddress = protocols::wireguard::defaultSubnetAddress;
if (serverConfig && !serverConfig->subnetAddress.isEmpty()) {
subnetAddress = serverConfig->subnetAddress;
} else if (awgServerConfig && !awgServerConfig->subnetAddress.isEmpty()) {
subnetAddress = awgServerConfig->subnetAddress;
}
if (ips.empty()) {
lastIp.setAddress(containerConfig.value(m_protocolName)
.toObject()
.value(config_key::subnet_address)
.toString(protocols::wireguard::defaultSubnetAddress));
lastIp.setAddress(subnetAddress);
} else {
lastIp = ips.last();
}
@@ -145,13 +166,13 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
// Get keys
connData.serverPubKey =
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
@@ -165,7 +186,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, configPart, configPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
@@ -178,23 +199,43 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
errorCode = m_serverController->runScript(
errorCode = m_sshSession->runScript(
credentials,
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
m_sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns)));
return connData;
}
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const WireGuardServerConfig* wireguardServerConfig = nullptr;
const WireGuardClientConfig* wireguardClientConfig = nullptr;
const AwgServerConfig* awgServerConfig = nullptr;
const AwgClientConfig* awgClientConfig = nullptr;
if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) {
wireguardServerConfig = &wireGuardProtocolConfig->serverConfig;
if (wireGuardProtocolConfig->clientConfig.has_value()) {
wireguardClientConfig = &wireGuardProtocolConfig->clientConfig.value();
}
} else if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
awgServerConfig = &awgProtocolConfig->serverConfig;
if (awgProtocolConfig->clientConfig.has_value()) {
awgClientConfig = &awgProtocolConfig->clientConfig.value();
}
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = m_serverController->replaceVars(
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
QString config = m_sshSession->replaceVars(scriptData, vars);
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return WireGuardProtocolConfig{};
}
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
@@ -202,40 +243,46 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig;
jConfig[config_key::config] = config;
jConfig[config_key::hostName] = connData.host;
jConfig[config_key::port] = connData.port.toInt();
jConfig[config_key::client_priv_key] = connData.clientPrivKey;
jConfig[config_key::client_ip] = connData.clientIP;
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);
jConfig[config_key::persistent_keep_alive] = "25";
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
jConfig[config_key::allowed_ips] = allowedIps;
jConfig[config_key::clientId] = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();
QString mtu = protocols::wireguard::defaultMtu;
if (wireguardClientConfig && !wireguardClientConfig->mtu.isEmpty()) {
mtu = wireguardClientConfig->mtu;
} else if (awgClientConfig && !awgClientConfig->mtu.isEmpty()) {
mtu = awgClientConfig->mtu;
}
WireGuardProtocolConfig protocolConfig;
if (wireguardServerConfig) {
protocolConfig.serverConfig = *wireguardServerConfig;
}
WireGuardClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.hostName = connData.host;
clientConfig.port = connData.port.toInt();
clientConfig.clientIp = connData.clientIP;
clientConfig.clientPrivateKey = connData.clientPrivKey;
clientConfig.clientPublicKey = connData.clientPubKey;
clientConfig.serverPublicKey = connData.serverPubKey;
clientConfig.presharedKey = connData.pskKey;
clientConfig.clientId = connData.clientPubKey;
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
clientConfig.persistentKeepAlive = "25";
clientConfig.mtu = mtu;
clientConfig.isObfuscationEnabled = false;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
ProtocolConfig WireguardConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
return ConfiguratorBase::processConfigWithLocalSettings(settings, protocolConfig);
}
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
ProtocolConfig WireguardConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
return ConfiguratorBase::processConfigWithExportSettings(settings, protocolConfig);
}

View File

@@ -0,0 +1,61 @@
#ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H
#include <QHostAddress>
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
class WireguardConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
WireguardConfigurator(SshSession* sshSession,
bool isAwg, QObject *parent = nullptr);
struct ConnectionData
{
QString clientPrivKey; // client private key
QString clientPubKey; // client public key
QString clientIP; // internal client IP address
QString serverPubKey; // tls-auth key
QString pskKey; // preshared key
QString host; // host ip
QString port;
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
static ConnectionData genClientKeys();
private:
QList<QHostAddress> getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::WireGuardServerConfig* serverConfig,
const amnezia::AwgServerConfig* awgServerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
bool m_isAwg;
QString m_serverConfigPath;
QString m_serverPublicKeyPath;
QString m_serverPskKeyPath;
amnezia::ProtocolScriptType m_configTemplate;
QString m_protocolName;
QString m_defaultPort;
};
#endif // WIREGUARD_CONFIGURATOR_H

View File

@@ -1,32 +1,43 @@
#include "xray_configurator.h"
#include "xrayConfigurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
#include "logger.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
namespace {
Logger logger("XrayConfigurator");
}
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
{
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
// Get current server config
QString currentConfig = m_serverController->getTextFileFromContainer(
QString currentConfig = m_sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
@@ -45,13 +56,13 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains("inbounds")) {
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray inbounds = serverConfig["inbounds"].toArray();
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
@@ -59,38 +70,38 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains("settings")) {
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
if (!settings.contains(amnezia::protocols::xray::clients)) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray clients = settings["clients"].toArray();
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
// Create configuration for new client
QJsonObject clientConfig {
{"id", clientId},
{"flow", "xtls-rprx-vision"}
{amnezia::protocols::xray::id, clientId},
{amnezia::protocols::xray::flow, "xtls-rprx-vision"}
};
clients.append(clientConfig);
// Update config
settings["clients"] = clients;
inbound["settings"] = settings;
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig["inbounds"] = inbounds;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_serverController->uploadTextFileToContainer(
errorCode = m_sshSession->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
@@ -104,9 +115,9 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_serverController->runScript(
errorCode = m_sshSession->runScript(
credentials,
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
);
if (errorCode != ErrorCode::NoError) {
@@ -117,57 +128,75 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
return clientId;
}
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
// Get client ID from prepareServerConfig
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
const XrayServerConfig* serverConfig = nullptr;
if (auto* xrayConfig = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayConfig->serverConfig;
}
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
return "";
return XrayProtocolConfig{};
}
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars);
if (config.isEmpty()) {
logger.error() << "Failed to get config template";
errorCode = ErrorCode::InternalError;
return "";
return XrayProtocolConfig{};
}
QString xrayPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
errorCode = ErrorCode::InternalError;
return "";
return XrayProtocolConfig{};
}
xrayPublicKey.replace("\n", "");
QString xrayShortId =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return "";
return XrayProtocolConfig{};
}
xrayShortId.replace("\n", "");
// Validate all required variables are present
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
logger.error() << "Config template missing required variables:"
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
errorCode = ErrorCode::InternalError;
return "";
return XrayProtocolConfig{};
}
config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
return config;
XrayProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.localPort = "";
clientConfig.id = xrayClientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}

View File

@@ -0,0 +1,27 @@
#ifndef XRAY_CONFIGURATOR_H
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class XrayConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
XrayConfigurator(SshSession* sshSession, QObject *parent = nullptr);
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
private:
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -0,0 +1,54 @@
#include "allowedDnsController.h"
AllowedDnsController::AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository)
: m_appSettingsRepository(appSettingsRepository)
{
fillDnsServers();
}
bool AllowedDnsController::addDns(const QString &ip)
{
if (m_dnsServers.contains(ip)) {
return false;
}
m_dnsServers.append(ip);
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
return true;
}
void AllowedDnsController::addDnsList(const QStringList &dnsServers, bool replaceExisting)
{
if (replaceExisting) {
m_dnsServers.clear();
}
for (const QString &ip : dnsServers) {
if (!m_dnsServers.contains(ip)) {
m_dnsServers.append(ip);
}
}
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
}
void AllowedDnsController::removeDns(int index)
{
if (index < 0 || index >= m_dnsServers.size()) {
return;
}
m_dnsServers.removeAt(index);
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
}
QStringList AllowedDnsController::getCurrentDnsServers() const
{
return m_dnsServers;
}
void AllowedDnsController::fillDnsServers()
{
m_dnsServers = m_appSettingsRepository->getAllowedDnsServers();
}

View File

@@ -0,0 +1,26 @@
#ifndef ALLOWEDDNSCONTROLLER_H
#define ALLOWEDDNSCONTROLLER_H
#include <QStringList>
#include "core/repositories/secureAppSettingsRepository.h"
class AllowedDnsController
{
public:
explicit AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository);
bool addDns(const QString &ip);
void addDnsList(const QStringList &dnsServers, bool replaceExisting);
void removeDns(int index);
QStringList getCurrentDnsServers() const;
private:
void fillDnsServers();
SecureAppSettingsRepository* m_appSettingsRepository;
QStringList m_dnsServers;
};
#endif // ALLOWEDDNSCONTROLLER_H

View File

@@ -0,0 +1,72 @@
#include "newsController.h"
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/configKeys.h"
#include <QtConcurrent/QtConcurrent>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSharedPointer>
using namespace amnezia;
NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController)
: m_appSettingsRepository(appSettingsRepository), m_serversController(serversController)
{
}
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
{
if (!m_serversController) {
qWarning() << "ServersController is null, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
}
const auto stacks = m_serversController->gatewayStacks();
if (stacks.isEmpty()) {
qDebug() << "No Gateway stacks, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray()));
}
auto gatewayController = QSharedPointer<GatewayController>::create(
m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
const QJsonObject stacksJson = stacks.toJson();
if (stacksJson.contains(apiDefs::key::userCountryCode)) {
payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode));
}
if (stacksJson.contains(apiDefs::key::serviceType)) {
payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType));
}
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
return future.then([gatewayController](QPair<ErrorCode, QByteArray> result) -> QPair<ErrorCode, QJsonArray> {
auto [errorCode, responseBody] = result;
if (errorCode != ErrorCode::NoError) {
return qMakePair(errorCode, QJsonArray());
}
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
QJsonArray newsArray;
if (doc.isArray()) {
newsArray = doc.array();
} else if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.value("news").isArray()) {
newsArray = obj.value("news").toArray();
}
}
return qMakePair(ErrorCode::NoError, newsArray);
});
}

View File

@@ -0,0 +1,28 @@
#ifndef NEWSCONTROLLER_H
#define NEWSCONTROLLER_H
#include <QFuture>
#include <QJsonArray>
#include <QPair>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/controllers/serversController.h"
class NewsController
{
public:
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController);
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
private:
SecureAppSettingsRepository* m_appSettingsRepository;
ServersController* m_serversController;
};
#endif // NEWSCONTROLLER_H

View File

@@ -0,0 +1,248 @@
#include "servicesCatalogController.h"
#include <QJsonDocument>
#include <QSysInfo>
#include <QJsonArray>
#include <QEventLoop>
#include <QDebug>
#include <QCoreApplication>
#include <QHash>
#include <QSet>
#include <limits>
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "version.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#endif
namespace
{
namespace configKey
{
constexpr char serviceDescription[] = "service_description";
constexpr char subscriptionPlans[] = "subscription_plans";
constexpr char storeProductId[] = "store_product_id";
constexpr char priceLabel[] = "price_label";
constexpr char subtitle[] = "subtitle";
constexpr char isTrial[] = "is_trial";
constexpr char minPriceLabel[] = "min_price_label";
}
namespace serviceType
{
constexpr char amneziaPremium[] = "amnezia-premium";
}
#if defined(Q_OS_IOS) || defined(MACOS_NE)
struct StoreKitPlanQuote {
QString displayPrice;
double priceAmount = 0.0;
double subscriptionBillingMonths = 0.0;
QString displayPricePerMonth;
};
constexpr double oneMonthThreshold = 1.0 + 1e-6;
constexpr double monthsFallbackThreshold = 1e-6;
constexpr double monthlyPriceEpsilon = 1e-9;
QStringList collectPremiumStoreProductIds(const QJsonArray &services)
{
QStringList productIds;
QSet<QString> seenProductIds;
for (const QJsonValue &serviceValue : services) {
const QJsonObject serviceObject = serviceValue.toObject();
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
continue;
}
const QJsonArray subscriptionPlans =
serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray();
for (const QJsonValue &planValue : subscriptionPlans) {
if (!planValue.isObject()) {
continue;
}
const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString();
if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) {
continue;
}
seenProductIds.insert(storeProductId);
productIds.append(storeProductId);
}
}
return productIds;
}
QHash<QString, StoreKitPlanQuote> buildStoreKitQuoteMap(const QList<QVariantMap> &fetchedProducts)
{
QHash<QString, StoreKitPlanQuote> quotesByProductId;
quotesByProductId.reserve(fetchedProducts.size());
for (const QVariantMap &productInfo : fetchedProducts) {
const QString productId = productInfo.value(QStringLiteral("productId")).toString();
if (productId.isEmpty()) {
continue;
}
QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString();
if (displayPrice.isEmpty()) {
const QString price = productInfo.value(QStringLiteral("price")).toString();
const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString();
displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode);
}
StoreKitPlanQuote quote;
quote.displayPrice = displayPrice;
quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble();
quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble();
quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString();
quotesByProductId.insert(productId, quote);
}
return quotesByProductId;
}
void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data)
{
QJsonArray services = data.value(apiDefs::key::services).toArray();
if (services.isEmpty()) {
return;
}
const QStringList productIds = collectPremiumStoreProductIds(services);
if (productIds.isEmpty()) {
qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload";
return;
}
QList<QVariantMap> fetchedProducts;
QEventLoop loop;
IosController::Instance()->fetchProducts(productIds,
[&](const QList<QVariantMap> &products, const QStringList &invalidIds,
const QString &errorString) {
if (!errorString.isEmpty()) {
qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString;
}
if (!invalidIds.isEmpty()) {
qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds;
}
fetchedProducts = products;
loop.quit();
});
loop.exec();
const QHash<QString, StoreKitPlanQuote> quotesByProductId = buildStoreKitQuoteMap(fetchedProducts);
for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) {
QJsonObject serviceObject = services.at(serviceIndex).toObject();
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
continue;
}
QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject();
const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray();
QJsonArray mergedPlans;
double minMonthlyAmount = std::numeric_limits<double>::infinity();
QString minMonthlyDisplay;
for (const QJsonValue &planValue : sourcePlans) {
if (!planValue.isObject()) {
continue;
}
QJsonObject planObject = planValue.toObject();
const QString storeProductId = planObject.value(configKey::storeProductId).toString();
if (storeProductId.isEmpty()) {
continue;
}
const auto quoteIterator = quotesByProductId.constFind(storeProductId);
if (quoteIterator == quotesByProductId.cend()) {
continue;
}
const bool isTrialPlan = planObject.value(configKey::isTrial).toBool();
const StoreKitPlanQuote &quote = *quoteIterator;
planObject.insert(configKey::priceLabel, quote.displayPrice);
const double months = quote.subscriptionBillingMonths;
if (!isTrialPlan && months > oneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) {
planObject.insert(
configKey::subtitle,
QCoreApplication::translate("ServicesCatalogController", "%1/mo",
"IAP: price per month in plan subtitle")
.arg(quote.displayPricePerMonth));
}
if (!isTrialPlan && quote.priceAmount > 0.0) {
const double monthsForMin = months > monthsFallbackThreshold ? months : 1.0;
const double monthly = quote.priceAmount / monthsForMin;
if (monthly < minMonthlyAmount - monthlyPriceEpsilon) {
minMonthlyAmount = monthly;
minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice;
}
}
mergedPlans.append(planObject);
}
descriptionObject.insert(configKey::subscriptionPlans, mergedPlans);
if (minMonthlyAmount < std::numeric_limits<double>::infinity() && !minMonthlyDisplay.isEmpty()) {
descriptionObject.insert(configKey::minPriceLabel,
QCoreApplication::translate("ServicesCatalogController", "from %1 per month",
"IAP: card footer minimum monthly price from StoreKit")
.arg(minMonthlyDisplay));
}
serviceObject.insert(configKey::serviceDescription, descriptionObject);
services.replace(serviceIndex, serviceObject);
}
data.insert(apiDefs::key::services, services);
}
#endif
}
ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository)
: m_appSettingsRepository(appSettingsRepository)
{
}
ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData)
{
QJsonObject apiPayload;
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains(apiDefs::key::services.data())) {
errorCode = ErrorCode::ApiServicesMissingError;
}
}
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
servicesData = QJsonDocument::fromJson(responseBody).object();
#if defined(Q_OS_IOS) || defined(MACOS_NE)
mergeStoreKitPricesIntoPremiumPlans(servicesData);
#endif
return ErrorCode::NoError;
}
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
{
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody);
}

View File

@@ -0,0 +1,26 @@
#ifndef SERVICESCATALOGCONTROLLER_H
#define SERVICESCATALOGCONTROLLER_H
#include <QJsonObject>
#include <QByteArray>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
class ServicesCatalogController
{
public:
explicit ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository);
ErrorCode fillAvailableServices(QJsonObject &servicesData);
private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody);
SecureAppSettingsRepository* m_appSettingsRepository;
};
#endif // SERVICESCATALOGCONTROLLER_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
#ifndef SUBSCRIPTIONCONTROLLER_H
#define SUBSCRIPTIONCONTROLLER_H
#include <QJsonObject>
#include <QByteArray>
#include <QFuture>
#include <QList>
#include <QVariantMap>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/models/serverConfig.h"
class ServersController;
class SubscriptionController
{
public:
struct ProtocolData
{
QString certRequest;
QString certPrivKey;
QString wireGuardClientPrivKey;
QString wireGuardClientPubKey;
QString xrayUuid;
};
struct GatewayRequestData
{
QString osVersion;
QString appVersion;
QString appLanguage;
QString installationUuid;
QString userCountryCode;
QString serverCountryCode;
QString serviceType;
QString serviceProtocol;
QJsonObject authData;
QJsonObject toJsonObject() const;
};
explicit SubscriptionController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository);
ProtocolData generateProtocolData(const QString &protocol);
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
ErrorCode fillServerConfig(const QJsonObject &serverConfigJson, ServerConfig &serverConfig);
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
ServerConfig &serverConfig);
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &email,
ServerConfig &serverConfig);
ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
const QString &transactionId, bool isTestPurchase,
ServerConfig &serverConfig,
int *duplicateServerIndex = nullptr);
ErrorCode updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent);
ErrorCode deactivateDevice(int serverIndex, bool isRemoveEvent);
ErrorCode deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode);
ErrorCode exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig);
ErrorCode revokeNativeConfig(int serverIndex, const QString &serverCountryCode);
ErrorCode updateServiceFromTelegram(int serverIndex);
ErrorCode prepareVpnKeyExport(int serverIndex, QString &vpnKey);
ErrorCode validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers);
void removeApiConfig(int serverIndex);
void setCurrentProtocol(int serverIndex, const QString &protocolName);
bool isVlessProtocol(int serverIndex) const;
ErrorCode getAccountInfo(int serverIndex, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(int serverIndex);
struct AppStoreRestoreResult
{
bool hasInstalledConfig = false;
bool duplicateConfigAlreadyPresent = false;
int duplicateCount = 0;
int duplicateServerIndex = -1;
ErrorCode errorCode = ErrorCode::NoError;
};
ErrorCode processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
ServerConfig &serverConfig,
int *duplicateServerIndex = nullptr);
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol);
private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
bool isApiKeyExpired(int serverIndex) const;
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
const QString &serviceProtocol, const QString &userCountryCode,
const QByteArray &apiResponseBody);
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
};
#endif // SUBSCRIPTIONCONTROLLER_H

View File

@@ -0,0 +1,70 @@
#include "appSplitTunnelingController.h"
AppSplitTunnelingController::AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository)
: m_appSettingsRepository(appSettingsRepository)
{
m_currentRouteMode = m_appSettingsRepository->appsRouteMode();
if (m_currentRouteMode == AppsRouteMode::VpnAllApps) { // for old split tunneling configs
m_currentRouteMode = AppsRouteMode::VpnAllExceptApps;
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
m_appSettingsRepository->setAppsRouteMode(AppsRouteMode::VpnAllExceptApps);
} else {
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
}
}
bool AppSplitTunnelingController::addApp(const amnezia::InstalledAppInfo &appInfo)
{
if (m_apps.contains(appInfo)) {
return false;
}
m_apps.append(appInfo);
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
return true;
}
void AppSplitTunnelingController::removeApp(int index)
{
if (index < 0 || index >= m_apps.size()) {
return;
}
m_apps.removeAt(index);
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
}
void AppSplitTunnelingController::clearAppsList()
{
m_apps.clear();
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
}
void AppSplitTunnelingController::setRouteMode(AppsRouteMode routeMode)
{
m_currentRouteMode = routeMode;
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
m_appSettingsRepository->setAppsRouteMode(routeMode);
}
void AppSplitTunnelingController::toggleSplitTunneling(bool enabled)
{
m_appSettingsRepository->setAppsSplitTunnelingEnabled(enabled);
}
AppsRouteMode AppSplitTunnelingController::getRouteMode() const
{
return m_currentRouteMode;
}
bool AppSplitTunnelingController::isSplitTunnelingEnabled() const
{
return m_appSettingsRepository->isAppsSplitTunnelingEnabled();
}
QVector<amnezia::InstalledAppInfo> AppSplitTunnelingController::getApps() const
{
return m_apps;
}

View File

@@ -0,0 +1,32 @@
#ifndef APPSPLITTUNNELINGCONTROLLER_H
#define APPSPLITTUNNELINGCONTROLLER_H
#include <QVector>
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
class AppSplitTunnelingController
{
public:
explicit AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository);
bool addApp(const amnezia::InstalledAppInfo &appInfo);
void removeApp(int index);
void clearAppsList();
void setRouteMode(AppsRouteMode routeMode);
void toggleSplitTunneling(bool enabled);
AppsRouteMode getRouteMode() const;
bool isSplitTunnelingEnabled() const;
QVector<amnezia::InstalledAppInfo> getApps() const;
private:
SecureAppSettingsRepository* m_appSettingsRepository;
AppsRouteMode m_currentRouteMode;
QVector<amnezia::InstalledAppInfo> m_apps;
};
#endif // APPSPLITTUNNELINGCONTROLLER_H

View File

@@ -0,0 +1,183 @@
#include "connectionController.h"
#include <QJsonDocument>
#include "core/configurators/configuratorBase.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/utils/networkUtilities.h"
#include "version.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
ConnectionController::ConnectionController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
VpnConnection* vpnConnection,
QObject* parent)
: QObject(parent),
m_serversRepository(serversRepository),
m_appSettingsRepository(appSettingsRepository),
m_vpnConnection(vpnConnection)
{
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
#ifdef Q_OS_ANDROID
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
#endif
}
bool ConnectionController::isConnected() const
{
return m_vpnConnection && m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected;
}
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
{
if (m_vpnConnection) {
emit setConnectionStateRequested(state);
}
}
ErrorCode ConnectionController::prepareConnection(int serverIndex,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning;
}
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
container = serverConfigModel.defaultContainer();
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
auto dns = serverConfigModel.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
vpnConfiguration = createConnectionConfiguration(dns, serverConfigModel, containerConfigModel, container);
return ErrorCode::NoError;
}
ErrorCode ConnectionController::openConnection(int serverIndex)
{
QJsonObject vpnConfiguration;
DockerContainer container;
ErrorCode errorCode = prepareConnection(serverIndex, vpnConfiguration, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
emit openConnectionRequested(serverIndex, container, vpnConfiguration);
return ErrorCode::NoError;
}
void ConnectionController::closeConnection()
{
if (m_vpnConnection) {
emit closeConnectionRequested();
}
}
#ifdef Q_OS_ANDROID
void ConnectionController::restoreConnection()
{
if (m_vpnConnection) {
emit restoreConnectionRequested();
}
}
#endif
void ConnectionController::onKillSwitchModeChanged(bool enabled)
{
if (m_vpnConnection) {
emit killSwitchModeChangedRequested(enabled);
}
}
ErrorCode ConnectionController::lastConnectionError() const
{
return m_vpnConnection->lastError();
}
QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QString, QString> &dns,
const ServerConfig &serverConfig,
const ContainerConfig &containerConfig,
DockerContainer container)
{
QJsonObject vpnConfiguration {};
if (ContainerUtils::containerService(container) == ServiceType::Other) {
return vpnConfiguration;
}
Proto proto = ContainerUtils::defaultProtocol(container);
ConnectionSettings connectionSettings = {
{ dns.first, dns.second },
serverConfig.isApiConfig(),
{
m_appSettingsRepository->isSitesSplitTunnelingEnabled(),
m_appSettingsRepository->routeMode()
}
};
auto configurator = ConfiguratorBase::create(proto, nullptr);
ProtocolConfig processedConfig = configurator->processConfigWithLocalSettings(connectionSettings,
containerConfig.protocolConfig);
QJsonObject vpnConfigData = processedConfig.getClientConfigJson();
if (ContainerUtils::isAwgContainer(container) || container == DockerContainer::WireGuard) {
if (vpnConfigData[configKey::mtu].toString().isEmpty()) {
vpnConfigData[configKey::mtu] =
ContainerUtils::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
}
}
vpnConfiguration.insert(ProtocolUtils::key_proto_config_data(proto), vpnConfigData);
vpnConfiguration[configKey::vpnProto] = ProtocolUtils::protoToString(proto);
vpnConfiguration[configKey::dns1] = dns.first;
vpnConfiguration[configKey::dns2] = dns.second;
vpnConfiguration[configKey::hostName] = serverConfig.hostName();
vpnConfiguration[configKey::description] = serverConfig.description();
vpnConfiguration[configKey::configVersion] = serverConfig.configVersion();
return vpnConfiguration;
}
bool ConnectionController::isServiceReady() const
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
return Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true);
#else
return true;
#endif
}
bool ConnectionController::isContainerSupported(DockerContainer container) const
{
return ContainerUtils::isSupportedByCurrentPlatform(container);
}

View File

@@ -0,0 +1,78 @@
#ifndef CONNECTIONCONTROLLER_H
#define CONNECTIONCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QPair>
#include <memory>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/protocols/vpnProtocol.h"
#include "vpnConnection.h"
using namespace amnezia;
class ConnectionController : public QObject
{
Q_OBJECT
public:
explicit ConnectionController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
VpnConnection* vpnConnection,
QObject* parent = nullptr);
~ConnectionController() = default;
ErrorCode prepareConnection(int serverIndex,
QJsonObject& vpnConfiguration,
DockerContainer& container);
ErrorCode openConnection(int serverIndex);
void closeConnection();
#ifdef Q_OS_ANDROID
void restoreConnection();
#endif
void onKillSwitchModeChanged(bool enabled);
ErrorCode lastConnectionError() const;
bool isConnected() const;
void setConnectionState(Vpn::ConnectionState state);
QJsonObject createConnectionConfiguration(const QPair<QString, QString> &dns,
const ServerConfig &serverConfig,
const ContainerConfig &containerConfig,
DockerContainer container);
bool isServiceReady() const;
bool isContainerSupported(DockerContainer container) const;
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration);
void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled);
#ifdef Q_OS_ANDROID
void restoreConnectionRequested();
#endif
private:
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
VpnConnection* m_vpnConnection;
};
#endif

View File

@@ -2,9 +2,18 @@
#include <QDirIterator>
#include <QTranslator>
#include <QTimer>
#include "core/utils/selfhosted/sshSession.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/selfhosted/importController.h"
#include "core/controllers/coreSignalHandlers.h"
#include "core/models/serverConfig.h"
#include "logger.h"
#include "secureQSettings.h"
#if defined(Q_OS_ANDROID)
#include "core/installedAppsImageProvider.h"
#include "core/utils/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
#endif
@@ -13,158 +22,196 @@
#include <AmneziaVPN-Swift.h>
#endif
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
QQmlApplicationEngine *engine, QObject *parent)
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
{
initRepositories();
initCoreControllers();
initModels();
initControllers();
initSignalHandlers();
initAndroidController();
initAppleController();
initLogging();
initNotificationHandler();
m_translator = new QTranslator(this);
if (m_appSettingsRepository) {
updateTranslator(m_appSettingsRepository->getAppLanguage());
}
}
m_translator.reset(new QTranslator());
updateTranslator(m_settings->getAppLanguage());
void CoreController::setQmlContextProperty(const QString &name, QObject *value)
{
if (m_engine) {
m_engine->rootContext()->setContextProperty(name, value);
}
}
void CoreController::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_containersModel = new ContainersModel(this);
setQmlContextProperty("ContainersModel", m_containersModel);
m_defaultServerContainersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
m_defaultServerContainersModel = new ContainersModel(this);
setQmlContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel);
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
m_serversModel = new ServersModel(this);
setQmlContextProperty("ServersModel", m_serversModel);
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
m_languageModel = new LanguageModel(this);
setQmlContextProperty("LanguageModel", m_languageModel);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_ipSplitTunnelingModel = new IpSplitTunnelingModel(this);
setQmlContextProperty("IpSplitTunnelingModel", m_ipSplitTunnelingModel);
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
m_allowedDnsModel = new AllowedDnsModel(this);
setQmlContextProperty("AllowedDnsModel", m_allowedDnsModel);
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_appSplitTunnelingModel = new AppSplitTunnelingModel(this);
setQmlContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel);
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_protocolsModel = new ProtocolsModel(this);
setQmlContextProperty("ProtocolsModel", m_protocolsModel);
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_openVpnConfigModel = new OpenVpnConfigModel(this);
setQmlContextProperty("OpenVpnConfigModel", m_openVpnConfigModel);
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_wireGuardConfigModel = new WireGuardConfigModel(this);
setQmlContextProperty("WireGuardConfigModel", m_wireGuardConfigModel);
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_awgConfigModel = new AwgConfigModel(this);
setQmlContextProperty("AwgConfigModel", m_awgConfigModel);
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_xrayConfigModel = new XrayConfigModel(this);
setQmlContextProperty("XrayConfigModel", m_xrayConfigModel);
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
m_torConfigModel = new TorConfigModel(this);
setQmlContextProperty("TorConfigModel", m_torConfigModel);
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
m_ikev2ConfigModel = new Ikev2ConfigModel(this);
setQmlContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel);
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_sftpConfigModel = new SftpConfigModel(this);
setQmlContextProperty("SftpConfigModel", m_sftpConfigModel);
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
m_clientManagementModel = new ClientManagementModel(this);
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
m_apiServicesModel.reset(new ApiServicesModel(this));
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
m_apiServicesModel = new ApiServicesModel(this);
setQmlContextProperty("ApiServicesModel", m_apiServicesModel);
m_apiSubscriptionPlansModel.reset(new ApiSubscriptionPlansModel(this));
m_engine->rootContext()->setContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel.get());
m_apiCountryModel = new ApiCountryModel(this);
setQmlContextProperty("ApiCountryModel", m_apiCountryModel);
m_apiBenefitsModel.reset(new ApiBenefitsModel(this));
m_engine->rootContext()->setContextProperty("ApiBenefitsModel", m_apiBenefitsModel.get());
m_apiSubscriptionPlansModel = new ApiSubscriptionPlansModel(this);
setQmlContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel);
m_apiCountryModel.reset(new ApiCountryModel(this));
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
m_apiBenefitsModel = new ApiBenefitsModel(this);
setQmlContextProperty("ApiBenefitsModel", m_apiBenefitsModel);
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
m_apiAccountInfoModel = new ApiAccountInfoModel(this);
setQmlContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel);
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
m_apiDevicesModel = new ApiDevicesModel(this);
setQmlContextProperty("ApiDevicesModel", m_apiDevicesModel);
m_newsModel.reset(new NewsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
m_newsModel = new NewsModel(m_appSettingsRepository, this);
setQmlContextProperty("NewsModel", m_newsModel);
}
void CoreController::initRepositories()
{
m_serversRepository = new SecureServersRepository(m_settings, this);
m_appSettingsRepository = new SecureAppSettingsRepository(m_settings, this);
if (m_vpnConnection) {
m_vpnConnection->setRepositories(m_serversRepository, m_appSettingsRepository);
}
}
void CoreController::initCoreControllers()
{
m_serversController = new ServersController(m_serversRepository, m_appSettingsRepository, this);
m_appSplitTunnelingController = new AppSplitTunnelingController(m_appSettingsRepository);
m_usersController = new UsersController(m_serversRepository, this);
m_ipSplitTunnelingController = new IpSplitTunnelingController(m_appSettingsRepository, this);
m_allowedDnsController = new AllowedDnsController(m_appSettingsRepository);
m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository);
m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository);
m_newsController = new NewsController(m_appSettingsRepository, m_serversController);
m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this);
m_exportController = new ExportController(m_serversRepository, m_appSettingsRepository, this);
m_importCoreController = new ImportController(m_serversRepository, m_appSettingsRepository, this);
m_connectionController = new ConnectionController(m_serversRepository, m_appSettingsRepository, m_vpnConnection.get(), this);
m_settingsController = new SettingsController(m_serversRepository, m_appSettingsRepository, this);
}
void CoreController::initControllers()
{
m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
m_connectionUiController = new ConnectionUiController(m_connectionController, m_serversController, this);
setQmlContextProperty("ConnectionController", m_connectionUiController);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
if (m_engine) {
m_focusController = new FocusController(m_engine, this);
setQmlContextProperty("FocusController", m_focusController);
}
m_focusController.reset(new FocusController(m_engine, this));
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
m_awgConfigModel, m_wireGuardConfigModel, m_openVpnConfigModel, m_xrayConfigModel, m_torConfigModel,
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, this);
setQmlContextProperty("InstallController", m_installUiController);
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
m_importController = new ImportUiController(m_importCoreController, this);
setQmlContextProperty("ImportController", m_importController);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
m_exportUiController = new ExportUiController(m_exportController, this);
setQmlContextProperty("ExportController", m_exportUiController);
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
setQmlContextProperty("LanguageUiController", m_languageUiController);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
setQmlContextProperty("SettingsController", m_settingsUiController);
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_pageController = new PageController(m_serversController, m_settingsController, this);
setQmlContextProperty("PageController", m_pageController);
m_settingsController.reset(
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this);
setQmlContextProperty("ServersUiController", m_serversUiController);
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_ipSplitTunnelingUiController = new IpSplitTunnelingUiController(m_ipSplitTunnelingController, m_ipSplitTunnelingModel, this);
setQmlContextProperty("IpSplitTunnelingController", m_ipSplitTunnelingUiController);
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
m_allowedDnsUiController = new AllowedDnsUiController(m_allowedDnsController, m_allowedDnsModel, this);
setQmlContextProperty("AllowedDnsController", m_allowedDnsUiController);
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_appSplitTunnelingUiController = new AppSplitTunnelingUiController(m_appSplitTunnelingController, m_appSplitTunnelingModel, this);
setQmlContextProperty("AppSplitTunnelingController", m_appSplitTunnelingUiController);
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_systemController = new SystemController(this);
setQmlContextProperty("SystemController", m_systemController);
m_apiSettingsController.reset(
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
m_apiConfigsController.reset(
new ApiConfigsController(m_serversModel, m_apiServicesModel, m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded,
this, [this]() { m_apiSettingsController->getAccountInfo(false); });
m_subscriptionUiController = new SubscriptionUiController(m_serversController, m_apiServicesModel, m_servicesCatalogController, m_subscriptionController,
m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_apiAccountInfoModel,
m_apiCountryModel, m_apiDevicesModel, m_settingsController, this);
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
}
void CoreController::initAndroidController()
@@ -173,33 +220,16 @@ void CoreController::initAndroidController()
if (!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setSaveLogs(m_appSettingsRepository->isSaveLogs());
AndroidController::instance()->setScreenshotsEnabled(m_appSettingsRepository->isScreenshotsEnabled());
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
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::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed");
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
data.clear();
emit m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
if (m_engine) {
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
}
#endif
}
@@ -207,63 +237,36 @@ void CoreController::initAppleController()
{
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
emit m_pageController->goToPageViewConfig();
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
#endif
}
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
emit m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup();
emit m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
void CoreController::initLogging()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
bool enabled = m_appSettingsRepository->isSaveLogs();
if (enabled) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
Logger::setServiceLogsEnabled(enabled);
#endif
}
void CoreController::initSignalHandlers()
{
initErrorMessagesHandler();
initApiCountryModelUpdateHandler();
initContainerModelUpdateHandler();
initAdminConfigRevokedHandler();
initPassphraseRequestHandler();
initTranslationsUpdatedHandler();
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initStrictKillSwitchHandler();
}
void CoreController::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
m_signalHandlers = new CoreSignalHandlers(this, this);
m_signalHandlers->initAllHandlers();
// Trigger initial update after handlers are connected
m_serversUiController->updateModel();
}
void CoreController::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
QCoreApplication::removeTranslator(m_translator);
}
QStringList availableTranslations;
@@ -284,119 +287,31 @@ void CoreController::updateTranslator(const QLocale &locale)
}
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
QCoreApplication::installTranslator(m_translator);
} else {
m_settings->setAppLanguage(QLocale::English);
if (m_translator->load(QString(":/translations/amneziavpn_en.qm"))) {
QCoreApplication::installTranslator(m_translator);
}
}
m_engine->retranslate();
if (m_engine) {
m_engine->retranslate();
}
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()
{
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
emit m_pageController->showErrorMessage(errorCode);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(),
qOverload<ErrorCode>(&PageController::showErrorMessage));
if (m_languageUiController) {
emit websiteUrlChanged(m_languageUiController->getCurrentSiteUrl());
}
}
void CoreController::setQmlRoot()
{
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
}
void CoreController::initApiCountryModelUpdateHandler()
{
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
});
}
void CoreController::initContainerModelUpdateHandler()
{
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
if (m_serversModel->hasServersFromGatewayApi()) {
m_apiNewsController->fetchNews(false);
}
});
m_serversModel->resetModel();
}
void CoreController::initAdminConfigRevokedHandler()
{
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile);
}
void CoreController::initPassphraseRequestHandler()
{
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
}
void CoreController::initTranslationsUpdatedHandler()
{
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator);
connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
}
void CoreController::initAutoConnectHandler()
{
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
if (m_engine && m_systemController) {
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
}
}
void CoreController::initAmneziaDnsToggledHandler()
{
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
}
void CoreController::initPrepareConfigHandler()
{
connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
if (!m_apiConfigsController->isConfigValid()) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
m_installController->validateConfig();
});
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
if (!isValid) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
m_connectionController->openConnection();
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
&VpnConnection::onKillSwitchModeChanged);
}
QSharedPointer<PageController> CoreController::pageController() const
PageController* CoreController::pageController() const
{
return m_pageController;
}
@@ -405,9 +320,11 @@ void CoreController::openConnectionByIndex(int serverIndex)
{
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
m_serversModel->setDefaultServerIndex(serverIndex);
}
m_connectionController->toggleConnection();
if (m_serversController) {
m_serversController->setDefaultServerIndex(serverIndex);
}
m_connectionUiController->toggleConnection();
}
void CoreController::importConfigFromData(const QString &data)

View File

@@ -6,28 +6,47 @@
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.h"
#include "ui/utils/systemTrayNotificationHandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/focusController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/api/subscriptionUiController.h"
#include "ui/controllers/api/apiNewsUiController.h"
#include "ui/controllers/appSplitTunnelingUiController.h"
#include "ui/controllers/allowedDnsUiController.h"
#include "ui/controllers/connectionUiController.h"
#include "ui/controllers/selfhosted/exportUiController.h"
#include "core/controllers/selfhosted/exportController.h"
#include "ui/controllers/qml/focusController.h"
#include "ui/controllers/importUiController.h"
#include "core/controllers/selfhosted/importController.h"
#include "ui/controllers/selfhosted/installUiController.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/controllers/settingsUiController.h"
#include "ui/controllers/serversUiController.h"
#include "ui/controllers/ipSplitTunnelingUiController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/controllers/api/servicesCatalogUiController.h"
#include "ui/models/allowed_dns_model.h"
#include "ui/models/containers_model.h"
#include "core/controllers/serversController.h"
#include "core/controllers/selfhosted/usersController.h"
#include "core/controllers/appSplitTunnelingController.h"
#include "core/controllers/ipSplitTunnelingController.h"
#include "core/controllers/allowedDnsController.h"
#include "core/controllers/api/servicesCatalogController.h"
#include "core/controllers/api/subscriptionController.h"
#include "core/controllers/api/newsController.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/connectionController.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "secureQSettings.h"
#include "ui/models/allowedDnsModel.h"
#include "ui/models/containersModel.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
@@ -41,117 +60,154 @@
#include "ui/models/clientManagementModel.h"
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/protocolsModel.h"
#include "ui/models/services/torConfigModel.h"
#include "ui/models/serversModel.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/notificationhandler.h"
#include "ui/utils/notificationHandler.h"
#endif
class CoreSignalHandlers;
class TestMultipleImports;
class TestAdminSelfHostedExport;
class TestServerEdit;
class TestDefaultServerChange;
class TestServerEdgeCases;
class TestSignalOrder;
class TestServersModelSync;
class TestGatewayStacks;
class TestComplexOperations;
class TestSettingsSignals;
class TestUiServersModelAndController;
class TestSelfHostedServerSetup;
class CoreController : public QObject
{
Q_OBJECT
friend class CoreSignalHandlers;
friend class TestMultipleImports;
friend class TestAdminSelfHostedExport;
friend class TestServerEdit;
friend class TestDefaultServerChange;
friend class TestServerEdgeCases;
friend class TestSignalOrder;
friend class TestServersModelSync;
friend class TestGatewayStacks;
friend class TestComplexOperations;
friend class TestSettingsSignals;
friend class TestUiServersModelAndController;
friend class TestSelfHostedServerSetup;
public:
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
QQmlApplicationEngine *engine, QObject *parent = nullptr);
QSharedPointer<PageController> pageController() const;
PageController* pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
void updateTranslator(const QLocale &locale);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initRepositories();
void initCoreControllers();
void initModels();
void initControllers();
void initAndroidController();
void initAppleController();
void initLogging();
void initSignalHandlers();
void initNotificationHandler();
void updateTranslator(const QLocale &locale);
void initErrorMessagesHandler();
void initApiCountryModelUpdateHandler();
void initContainerModelUpdateHandler();
void initAdminConfigRevokedHandler();
void initPassphraseRequestHandler();
void initTranslationsUpdatedHandler();
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initStrictKillSwitchHandler();
void setQmlContextProperty(const QString &name, QObject *value);
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
std::shared_ptr<Settings> m_settings;
SecureQSettings* m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
QTranslator* m_translator;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<NotificationHandler> m_notificationHandler;
NotificationHandler* m_notificationHandler;
#endif
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<FocusController> m_focusController;
QSharedPointer<PageController> m_pageController; // TODO
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QScopedPointer<AllowedDnsController> m_allowedDnsController;
ConnectionUiController* m_connectionUiController;
FocusController* m_focusController;
PageController* m_pageController;
InstallUiController* m_installUiController;
ImportUiController* m_importController;
ImportController* m_importCoreController;
ExportUiController* m_exportUiController;
SettingsUiController* m_settingsUiController;
ServersUiController* m_serversUiController;
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
SystemController* m_systemController;
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
AllowedDnsUiController* m_allowedDnsUiController;
LanguageUiController* m_languageUiController;
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiNewsController> m_apiNewsController;
SubscriptionUiController* m_subscriptionUiController;
ApiNewsUiController* m_apiNewsUiController;
ServicesCatalogUiController* m_servicesCatalogUiController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<NewsModel> m_newsModel;
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
ServersController* m_serversController;
UsersController* m_usersController;
AppSplitTunnelingController* m_appSplitTunnelingController;
IpSplitTunnelingController* m_ipSplitTunnelingController;
AllowedDnsController* m_allowedDnsController;
ServicesCatalogController* m_servicesCatalogController;
SubscriptionController* m_subscriptionController;
NewsController* m_newsController;
InstallController* m_installController;
ExportController* m_exportController;
ConnectionController* m_connectionController;
SettingsController* m_settingsController;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiSubscriptionPlansModel> m_apiSubscriptionPlansModel;
QSharedPointer<ApiBenefitsModel> m_apiBenefitsModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
ContainersModel* m_containersModel;
ContainersModel* m_defaultServerContainersModel;
ServersModel* m_serversModel;
LanguageModel* m_languageModel;
ProtocolsModel* m_protocolsModel;
IpSplitTunnelingModel* m_ipSplitTunnelingModel;
NewsModel* m_newsModel;
AllowedDnsModel* m_allowedDnsModel;
AppSplitTunnelingModel* m_appSplitTunnelingModel;
ClientManagementModel* m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
ApiServicesModel* m_apiServicesModel;
ApiSubscriptionPlansModel* m_apiSubscriptionPlansModel;
ApiBenefitsModel* m_apiBenefitsModel;
ApiCountryModel* m_apiCountryModel;
ApiAccountInfoModel* m_apiAccountInfoModel;
ApiDevicesModel* m_apiDevicesModel;
OpenVpnConfigModel* m_openVpnConfigModel;
XrayConfigModel* m_xrayConfigModel;
TorConfigModel* m_torConfigModel;
WireGuardConfigModel* m_wireGuardConfigModel;
AwgConfigModel* m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
Ikev2ConfigModel* m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
SftpConfigModel* m_sftpConfigModel;
Socks5ProxyConfigModel* m_socks5ConfigModel;
CoreSignalHandlers* m_signalHandlers;
};
#endif // CORECONTROLLER_H

View File

@@ -0,0 +1,412 @@
#include "coreSignalHandlers.h"
#include <QTimer>
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/controllers/coreController.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "vpnConnection.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/controllers/connectionUiController.h"
#include "ui/controllers/settingsUiController.h"
#include "ui/controllers/serversUiController.h"
#include "ui/controllers/ipSplitTunnelingUiController.h"
#include "ui/controllers/allowedDnsUiController.h"
#include "ui/controllers/appSplitTunnelingUiController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/controllers/selfhosted/installUiController.h"
#include "ui/controllers/importUiController.h"
#include "ui/controllers/api/subscriptionUiController.h"
#include "ui/models/serversModel.h"
#include "core/controllers/serversController.h"
#include "core/controllers/ipSplitTunnelingController.h"
#include "core/controllers/appSplitTunnelingController.h"
#include "core/controllers/selfhosted/usersController.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/selfhosted/exportController.h"
#include "core/controllers/connectionController.h"
#include "ui/models/clientManagementModel.h"
#include "ui/controllers/api/apiNewsUiController.h"
#include "ui/models/api/apiCountryModel.h"
#include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/utils/notificationHandler.h"
#include "ui/utils/systemTrayNotificationHandler.h"
#endif
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
CoreSignalHandlers::CoreSignalHandlers(CoreController* coreController, QObject* parent)
: QObject(parent),
m_coreController(coreController)
{
}
void CoreSignalHandlers::initAllHandlers()
{
initErrorMessagesHandler();
initSettingsSplitTunnelingHandler();
initInstallControllerHandler();
initExportControllerHandler();
initImportControllerHandler();
initApiCountryModelUpdateHandler();
initSubscriptionRefreshHandler();
initContainerModelUpdateHandler();
initAdminConfigRevokedHandler();
initPassphraseRequestHandler();
initTranslationsUpdatedHandler();
initLanguageHandler();
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initServersModelUpdateHandler();
initClientManagementModelUpdateHandler();
initSitesModelUpdateHandler();
initAllowedDnsModelUpdateHandler();
initAppSplitTunnelingModelUpdateHandler();
initPrepareConfigHandler();
initStrictKillSwitchHandler();
initAndroidSettingsHandler();
initAndroidConnectionHandler();
initIosImportHandler();
initIosSettingsHandler();
initNotificationHandler();
}
void CoreSignalHandlers::initErrorMessagesHandler()
{
connect(m_coreController->m_connectionUiController, &ConnectionUiController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
emit m_coreController->m_pageController->showErrorMessage(errorCode);
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
});
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
qOverload<ErrorCode>(&PageController::showErrorMessage));
connect(m_coreController->m_settingsUiController, &SettingsUiController::errorOccurred, m_coreController->m_pageController,
qOverload<ErrorCode>(&PageController::showErrorMessage));
}
void CoreSignalHandlers::initSettingsSplitTunnelingHandler()
{
connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingRouteModeChanged, this, [this](RouteMode mode) {
m_coreController->m_ipSplitTunnelingController->setRouteMode(mode);
});
connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingToggled, this, [this](bool enabled) {
m_coreController->m_ipSplitTunnelingController->toggleSplitTunneling(enabled);
});
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingRouteModeChanged, this, [this](AppsRouteMode mode) {
m_coreController->m_appSplitTunnelingController->setRouteMode(mode);
});
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingToggled, this, [this](bool enabled) {
m_coreController->m_appSplitTunnelingController->toggleSplitTunneling(enabled);
});
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingClearAppsList, this, [this]() {
m_coreController->m_appSplitTunnelingController->clearAppsList();
});
}
void CoreSignalHandlers::initInstallControllerHandler()
{
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
connect(m_coreController->m_installUiController, &InstallUiController::currentContainerUpdated, m_coreController->m_connectionUiController,
&ConnectionUiController::onCurrentContainerUpdated);
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
m_coreController->m_installUiController, [this](int index) {
if (index >= 0) {
m_coreController->m_installUiController->clearProcessedServerCredentials();
}
});
}
void CoreSignalHandlers::initExportControllerHandler()
{
connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this,
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
});
connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this,
[this](int serverIndex, DockerContainer container) {
m_coreController->m_usersController->updateClients(serverIndex, container);
});
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
[this](int serverIndex, int row, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverIndex, row, container);
});
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
[this](int serverIndex, int row, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->renameClient(serverIndex, row, clientName, container);
});
}
void CoreSignalHandlers::initImportControllerHandler()
{
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (!m_coreController->m_connectionController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
m_coreController->m_serversController->setDefaultServerIndex(newServerIndex);
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerIndex(newServerIndex);
}
}
});
}
void CoreSignalHandlers::initApiCountryModelUpdateHandler()
{
connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() {
int processedIndex = m_coreController->m_serversUiController->getProcessedServerIndex();
if (processedIndex < 0 || processedIndex >= m_coreController->m_serversRepository->serversCount()) {
return;
}
ServerConfig server = m_coreController->m_serversRepository->server(processedIndex);
QJsonArray availableCountries;
QString serverCountryCode;
if (server.isApiV2()) {
const ApiV2ServerConfig* apiV2 = server.as<ApiV2ServerConfig>();
if (apiV2) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
}
}
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
});
}
void CoreSignalHandlers::initSubscriptionRefreshHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::subscriptionRefreshNeeded, this, [this]() {
const int defaultServerIndex = m_coreController->m_serversController->getDefaultServerIndex();
if (defaultServerIndex >= 0) {
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerIndex, false);
}
});
}
void CoreSignalHandlers::initContainerModelUpdateHandler()
{
connect(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded, this, [this]() {
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
m_coreController->m_apiNewsUiController->fetchNews(false);
}
});
}
void CoreSignalHandlers::initAdminConfigRevokedHandler()
{
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
[this](int serverIndex, const ContainerConfig &containerConfig, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverIndex, containerConfig, container);
});
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
});
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_serversController,
&ServersController::clearCachedProfile);
}
void CoreSignalHandlers::initPassphraseRequestHandler()
{
connect(m_coreController->m_installUiController, &InstallUiController::passphraseRequestStarted, m_coreController->m_pageController,
&PageController::showPassphraseRequestDrawer);
connect(m_coreController->m_pageController, &PageController::passphraseRequestDrawerClosed, m_coreController->m_installUiController,
&InstallUiController::setEncryptedPassphrase);
}
void CoreSignalHandlers::initTranslationsUpdatedHandler()
{
connect(m_coreController->m_languageUiController, &LanguageUiController::updateTranslations, m_coreController, &CoreController::updateTranslator);
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated);
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_connectionUiController, &ConnectionUiController::onTranslationsUpdated);
}
void CoreSignalHandlers::initLanguageHandler()
{
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appLanguageChanged, m_coreController->m_languageUiController, &LanguageUiController::onAppLanguageChanged);
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
});
}
void CoreSignalHandlers::initAutoConnectHandler()
{
if (m_coreController->m_settingsUiController->isAutoConnectEnabled() && m_coreController->m_serversController->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
}
}
void CoreSignalHandlers::initAmneziaDnsToggledHandler()
{
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::useAmneziaDnsChanged, m_coreController->m_serversUiController, &ServersUiController::updateModel);
}
void CoreSignalHandlers::initServersModelUpdateHandler()
{
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
m_coreController->m_serversUiController, &ServersUiController::updateModel);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
m_coreController->m_serversUiController, &ServersUiController::updateModel);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
m_coreController->m_serversUiController, &ServersUiController::updateModel);
connect(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged,
m_coreController->m_serversUiController, &ServersUiController::onDefaultServerChanged);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished,
m_coreController->m_serversUiController, &ServersUiController::updateModel);
}
void CoreSignalHandlers::initClientManagementModelUpdateHandler()
{
connect(m_coreController->m_usersController, &UsersController::clientsUpdated,
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
}
void CoreSignalHandlers::initSitesModelUpdateHandler()
{
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesSplitTunnelingEnabledChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::routeModeChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
}
void CoreSignalHandlers::initAllowedDnsModelUpdateHandler()
{
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::allowedDnsServersChanged, m_coreController->m_allowedDnsUiController, &AllowedDnsUiController::updateModel);
}
void CoreSignalHandlers::initAppSplitTunnelingModelUpdateHandler()
{
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsSplitTunnelingEnabledChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsRouteModeChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
}
void CoreSignalHandlers::initPrepareConfigHandler()
{
connect(m_coreController->m_connectionUiController, &ConnectionUiController::prepareConfig, this, [this]() {
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Preparing);
m_coreController->m_subscriptionUiController->validateConfig();
});
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::configValidated, this, [this](bool isValid) {
if (!isValid) {
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
m_coreController->m_installUiController->validateConfig();
});
connect(m_coreController->m_installUiController, &InstallUiController::configValidated, this, [this](bool isValid) {
if (!isValid) {
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
m_coreController->m_connectionUiController->openConnection();
});
}
void CoreSignalHandlers::initStrictKillSwitchHandler()
{
connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController,
&ConnectionController::onKillSwitchModeChanged);
}
void CoreSignalHandlers::initAndroidSettingsHandler()
{
#ifdef Q_OS_ANDROID
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
#endif
}
void CoreSignalHandlers::initAndroidConnectionHandler()
{
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_coreController->m_connectionUiController->onConnectionStateChanged(state);
m_coreController->m_connectionController->restoreConnection();
});
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
emit m_coreController->m_pageController->goToPageHome();
m_coreController->m_importController->extractConfigFromData(data);
data.clear();
emit m_coreController->m_pageController->goToPageViewConfig();
});
#endif
}
void CoreSignalHandlers::initIosImportHandler()
{
#ifdef Q_OS_IOS
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
emit m_coreController->m_pageController->goToPageHome();
m_coreController->m_importController->extractConfigFromData(data);
emit m_coreController->m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
emit m_coreController->m_pageController->goToPageHome();
m_coreController->m_pageController->goToPageSettingsBackup();
emit m_coreController->m_settingsUiController->importBackupFromOutside(filePath);
});
#endif
}
void CoreSignalHandlers::initIosSettingsHandler()
{
#ifdef Q_OS_IOS
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
}
void CoreSignalHandlers::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_coreController->m_notificationHandler = NotificationHandler::create(m_coreController);
connect(m_coreController->m_connectionController, &ConnectionController::connectionStateChanged, m_coreController->m_notificationHandler,
&NotificationHandler::setConnectionState);
connect(m_coreController->m_notificationHandler, &NotificationHandler::raiseRequested, m_coreController->m_pageController, &PageController::raiseMainWindow);
connect(m_coreController->m_notificationHandler, &NotificationHandler::connectRequested, m_coreController->m_connectionUiController,
static_cast<void (ConnectionUiController::*)()>(&ConnectionUiController::openConnection));
connect(m_coreController->m_notificationHandler, &NotificationHandler::disconnectRequested, m_coreController->m_connectionUiController,
&ConnectionUiController::closeConnection);
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
}

View File

@@ -0,0 +1,48 @@
#ifndef CORESIGNALHANDLERS_H
#define CORESIGNALHANDLERS_H
#include <QObject>
#include "core/controllers/coreController.h"
class CoreSignalHandlers : public QObject
{
Q_OBJECT
public:
explicit CoreSignalHandlers(CoreController* coreController, QObject* parent = nullptr);
void initAllHandlers();
private:
void initErrorMessagesHandler();
void initSettingsSplitTunnelingHandler();
void initInstallControllerHandler();
void initExportControllerHandler();
void initImportControllerHandler();
void initApiCountryModelUpdateHandler();
void initSubscriptionRefreshHandler();
void initContainerModelUpdateHandler();
void initAdminConfigRevokedHandler();
void initPassphraseRequestHandler();
void initTranslationsUpdatedHandler();
void initLanguageHandler();
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initServersModelUpdateHandler();
void initClientManagementModelUpdateHandler();
void initSitesModelUpdateHandler();
void initAllowedDnsModelUpdateHandler();
void initAppSplitTunnelingModelUpdateHandler();
void initPrepareConfigHandler();
void initStrictKillSwitchHandler();
void initAndroidSettingsHandler();
void initAndroidConnectionHandler();
void initIosImportHandler();
void initIosSettingsHandler();
void initNotificationHandler();
CoreController* m_coreController;
};
#endif // CORESIGNALHANDLERS_H

View File

@@ -15,27 +15,18 @@
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amnezia_application.h"
#include "core/api/apiUtils.h"
#include "core/networkUtilities.h"
#include "utilities.h"
#include "amneziaApplication.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/ipcclient.h"
#include "core/utils/ipcClient.h"
#endif
namespace
{
namespace configKey
{
constexpr char aesKey[] = "aes_key";
constexpr char aesIv[] = "aes_iv";
constexpr char aesSalt[] = "aes_salt";
constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload";
}
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
@@ -61,6 +52,7 @@ namespace
|| host == QLatin1String("[::1]");
}
#endif
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
@@ -119,9 +111,9 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
@@ -153,8 +145,8 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
requestBody[apiDefs::key::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[apiDefs::key::apiPayload] = QString(encryptedApiPayload.toBase64());
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
@@ -324,23 +316,33 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
QStringList baseUrls;
QStringList primaryBaseUrls;
QStringList fallbackBaseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
}
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
}
}
for (const auto &baseUrl : baseUrls) {
target.push_back(baseUrl + "endpoints.json");
}
};
QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
+ ".json");
}
}
for (const auto &baseUrl : baseUrls)
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
@@ -367,40 +369,48 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList baseUrls;
QStringList primaryBaseUrls;
QStringList fallbackBaseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
}
if (baseUrls.empty()) {
qDebug() << "empty storage endpoint list";
return {};
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
}
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
}
}
}
for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
for (const auto &baseUrl : baseUrls) {
target.push_back(baseUrl + "endpoints.json");
}
};
QStringList proxyStorageUrls;
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
if (proxyStorageUrls.empty()) {
qDebug() << "empty storage endpoint list";
return {};
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
@@ -614,7 +624,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);

View File

@@ -8,7 +8,9 @@
#include <QPromise>
#include <QSharedPointer>
#include "core/defs.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"

View File

@@ -0,0 +1,245 @@
#include "ipSplitTunnelingController.h"
#include "core/utils/networkUtilities.h"
#include <QJsonObject>
IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent)
: QObject(parent),
m_appSettingsRepository(appSettingsRepository)
{
m_currentRouteMode = m_appSettingsRepository->routeMode();
if (m_currentRouteMode == RouteMode::VpnAllSites) { // for old split tunneling configs
m_appSettingsRepository->setRouteMode(RouteMode::VpnOnlyForwardSites);
m_currentRouteMode = RouteMode::VpnOnlyForwardSites;
}
fillSites();
}
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip)
{
QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode);
if (existing.contains(hostname) && ip.isEmpty()) {
return false;
}
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
m_sites[i].second = ip;
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
return true;
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
return false;
}
}
m_sites.append(qMakePair(hostname, ip));
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
return true;
}
void IpSplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
{
if (replaceExisting) {
m_sites.clear();
}
for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) {
const QString &hostname = it.key();
const QString &ip = it.value();
bool found = false;
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname) {
if (!ip.isEmpty()) {
m_sites[i].second = ip;
}
found = true;
break;
}
}
if (!found) {
m_sites.append(qMakePair(hostname, ip));
}
}
if (replaceExisting) {
m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode);
}
m_appSettingsRepository->addVpnSites(m_currentRouteMode, sites);
}
bool IpSplitTunnelingController::addSite(const QString &hostname)
{
QString normalizedHostname = normalizeHostname(hostname);
if (!validateHostname(normalizedHostname)) {
return false;
}
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) {
processSite(normalizedHostname, "");
return true;
}
if (addSiteInternal(normalizedHostname, "")) {
QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo)));
return true;
}
return false;
}
bool IpSplitTunnelingController::removeSite(const QString &hostname)
{
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname) {
m_sites.removeAt(i);
m_appSettingsRepository->removeVpnSite(m_currentRouteMode, hostname);
return true;
}
}
return false;
}
void IpSplitTunnelingController::removeSites()
{
m_sites.clear();
m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode);
}
void IpSplitTunnelingController::setRouteMode(RouteMode routeMode)
{
m_currentRouteMode = routeMode;
fillSites();
m_appSettingsRepository->setRouteMode(routeMode);
}
void IpSplitTunnelingController::toggleSplitTunneling(bool enabled)
{
m_appSettingsRepository->setSitesSplitTunnelingEnabled(enabled);
}
RouteMode IpSplitTunnelingController::getRouteMode() const
{
return m_currentRouteMode;
}
bool IpSplitTunnelingController::isSplitTunnelingEnabled() const
{
return m_appSettingsRepository->isSitesSplitTunnelingEnabled();
}
QVector<QPair<QString, QString>> IpSplitTunnelingController::getCurrentSites() const
{
return m_sites;
}
void IpSplitTunnelingController::fillSites()
{
QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode);
m_sites.clear();
for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) {
m_sites.append(qMakePair(it.key(), it.value().toString()));
}
}
QString IpSplitTunnelingController::normalizeHostname(const QString &hostname) const
{
QString normalized = hostname;
normalized.replace("https://", "");
normalized.replace("http://", "");
normalized.replace("ftp://", "");
normalized = normalized.split("/", Qt::SkipEmptyParts).first();
return normalized;
}
bool IpSplitTunnelingController::validateHostname(const QString &hostname) const
{
if (hostname.isEmpty()) {
return false;
}
if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
return false;
}
return true;
}
void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo)
{
const QList<QHostAddress> &addresses = hostInfo.addresses();
QString hostname = hostInfo.hostName();
for (const QHostAddress &addr : addresses) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
processSiteAfterResolve(hostname, addr.toString());
break;
}
}
}
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip)
{
for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) {
m_sites[i].second = ip;
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
break;
}
}
}
void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip)
{
addSiteInternal(hostname, ip);
}
bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage)
{
QJsonParseError parseError;
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
errorMessage = tr("Failed to parse JSON data: %1").arg(parseError.errorString());
return false;
}
if (!jsonDocument.isArray()) {
errorMessage = tr("The JSON data is not an array");
return false;
}
QJsonArray jsonArray = jsonDocument.array();
QMap<QString, QString> sites;
for (auto jsonValue : jsonArray) {
QJsonObject jsonObject = jsonValue.toObject();
QString hostname = jsonObject.value("hostname").toString("");
QString ip = jsonObject.value("ip").toString("");
QString normalizedHostname = normalizeHostname(hostname);
if (!validateHostname(normalizedHostname)) {
qDebug() << normalizedHostname << " not look like ip adress or domain name";
continue;
}
sites.insert(normalizedHostname, ip);
}
addSites(sites, replaceExisting);
return true;
}
QByteArray IpSplitTunnelingController::exportSitesToJson() const
{
QVector<QPair<QString, QString>> sites = getCurrentSites();
QJsonArray jsonArray;
for (const auto &site : sites) {
QJsonObject jsonObject;
jsonObject["hostname"] = site.first;
jsonObject["ip"] = site.second;
jsonArray.append(jsonObject);
}
QJsonDocument jsonDocument(jsonArray);
return jsonDocument.toJson();
}

View File

@@ -0,0 +1,58 @@
#ifndef IPSPLITTUNNELINGCONTROLLER_H
#define IPSPLITTUNNELINGCONTROLLER_H
#include <QObject>
#include <QVector>
#include <QMap>
#include <QPair>
#include <QStringList>
#include <QJsonDocument>
#include <QJsonArray>
#include <QHostInfo>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
using namespace amnezia;
class IpSplitTunnelingController : public QObject
{
Q_OBJECT
public:
explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr);
bool addSite(const QString &hostname);
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
bool removeSite(const QString &hostname);
void removeSites();
void setRouteMode(RouteMode routeMode);
void toggleSplitTunneling(bool enabled);
RouteMode getRouteMode() const;
bool isSplitTunnelingEnabled() const;
QVector<QPair<QString, QString>> getCurrentSites() const;
bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage);
QByteArray exportSitesToJson() const;
private slots:
void onHostResolved(const QHostInfo &hostInfo);
private:
void fillSites();
bool addSiteInternal(const QString &hostname, const QString &ip);
QString normalizeHostname(const QString &hostname) const;
bool validateHostname(const QString &hostname) const;
void processSiteAfterResolve(const QString &hostname, const QString &ip);
void processSite(const QString &hostname, const QString &ip);
SecureAppSettingsRepository* m_appSettingsRepository;
RouteMode m_currentRouteMode;
QVector<QPair<QString, QString>> m_sites;
};
#endif // IPSPLITTUNNELINGCONTROLLER_H

View File

@@ -0,0 +1,337 @@
#include "exportController.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "core/configurators/configuratorBase.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/qrCodeUtils.h"
#include "core/utils/serialization/serialization.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
using namespace amnezia;
ExportController::ExportController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent)
: QObject(parent),
m_serversRepository(serversRepository),
m_appSettingsRepository(appSettingsRepository)
{
}
ExportController::ExportResult ExportController::generateFullAccessConfig(int serverIndex)
{
ExportResult result;
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
serverConfig.visit([](auto& arg) {
for (auto it = arg.containers.begin(); it != arg.containers.end(); ++it) {
it.value().protocolConfig.clearClientConfig();
}
});
QJsonObject serverJson = serverConfig.toJson();
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = generateVpnUrl(compressedConfig);
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
return result;
}
ExportController::ExportResult ExportController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName)
{
ExportResult result;
DockerContainer container = static_cast<DockerContainer>(containerIndex);
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
if (ContainerUtils::containerService(container) != ServiceType::Other) {
SshSession sshSession;
Proto protocol = ContainerUtils::defaultProtocol(container);
DnsSettings dnsSettings = {
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns()
};
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, containerConfig, dnsSettings, result.errorCode);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
containerConfig.protocolConfig = newProtocolConfig;
QString clientId = newProtocolConfig.clientId();
if (!clientId.isEmpty()) {
emit appendClientRequested(serverIndex, clientId, clientName, container);
}
}
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
serverConfig.visit([container, containerConfig](auto& arg) {
arg.containers.clear();
arg.containers[container] = containerConfig;
arg.defaultContainer = container;
});
if (serverConfig.isSelfHosted()) {
SelfHostedServerConfig* selfHosted = serverConfig.as<SelfHostedServerConfig>();
if (selfHosted) {
selfHosted->userName.reset();
selfHosted->password.reset();
selfHosted->port.reset();
}
}
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
serverConfig.visit([&dns](auto& arg) {
arg.dns1 = dns.first;
arg.dns2 = dns.second;
});
QJsonObject serverJson = serverConfig.toJson();
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = generateVpnUrl(compressedConfig);
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
return result;
}
ExportController::NativeConfigResult ExportController::generateNativeConfig(int serverIndex, DockerContainer container,
const ContainerConfig &containerConfig,
const QString &clientName)
{
NativeConfigResult result;
if (ContainerUtils::containerService(container) == ServiceType::Other) {
return result;
}
Proto protocol = ContainerUtils::defaultProtocol(container);
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
ContainerConfig modifiedContainerConfig = containerConfig;
modifiedContainerConfig.container = container;
DnsSettings dnsSettings = {
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns()
};
SshSession sshSession;
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, modifiedContainerConfig, dnsSettings, result.errorCode);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
ExportSettings exportSettings = { { dns.first, dns.second } };
ProtocolConfig processedConfig = configurator->processConfigWithExportSettings(exportSettings, newProtocolConfig);
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) {
result.jsonNativeConfig[configKey::config] = processedConfig.nativeConfig();
} else {
result.jsonNativeConfig = QJsonDocument::fromJson(processedConfig.nativeConfig().toUtf8()).object();
}
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) {
QString clientId = newProtocolConfig.clientId();
if (!clientId.isEmpty()) {
emit appendClientRequested(serverIndex, clientId, clientName, container);
}
}
return result;
}
ExportController::ExportResult ExportController::generateOpenVpnConfig(int serverIndex, const QString &clientName)
{
ExportResult result;
DockerContainer container = DockerContainer::OpenVpn;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
}
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes = generateQrCodesFromConfig(result.config.toUtf8());
return result;
}
ExportController::ExportResult ExportController::generateWireGuardConfig(int serverIndex, const QString &clientName)
{
ExportResult result;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::WireGuard);
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::WireGuard, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
}
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
return result;
}
ExportController::ExportResult ExportController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName)
{
ExportResult result;
DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::Awg && container != DockerContainer::Awg2) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
}
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
return result;
}
ExportController::ExportResult ExportController::generateXrayConfig(int serverIndex, const QString &clientName)
{
ExportResult result;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::Xray);
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::Xray, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
}
QStringList lines = QString(QJsonDocument(nativeResult.jsonNativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
// Parse the Xray data to extract VLESS parameters and generate string
QJsonObject xrayConfig = nativeResult.jsonNativeConfig;
QJsonArray outbounds = xrayConfig.value(amnezia::protocols::xray::outbounds).toArray();
if (outbounds.isEmpty()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
QJsonObject outbound = outbounds[0].toObject();
QJsonObject settings = outbound.value(amnezia::protocols::xray::settings).toObject();
QJsonObject streamSettings = outbound.value(amnezia::protocols::xray::streamSettings).toObject();
QJsonArray vnext = settings.value(amnezia::protocols::xray::vnext).toArray();
if (vnext.isEmpty()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
QJsonObject server = vnext[0].toObject();
QJsonArray users = server.value(amnezia::protocols::xray::users).toArray();
if (users.isEmpty()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
QJsonObject user = users[0].toObject();
amnezia::serialization::VlessServerObject vlessServer;
vlessServer.address = server.value(amnezia::protocols::xray::address).toString();
vlessServer.port = server.value(amnezia::protocols::xray::port).toInt();
vlessServer.id = user.value(amnezia::protocols::xray::id).toString();
vlessServer.flow = user.value(amnezia::protocols::xray::flow).toString("xtls-rprx-vision");
vlessServer.encryption = user.value(amnezia::protocols::xray::encryption).toString("none");
vlessServer.network = streamSettings.value(amnezia::protocols::xray::network).toString("tcp");
vlessServer.security = streamSettings.value(amnezia::protocols::xray::security).toString("reality");
if (vlessServer.security == "reality") {
QJsonObject realitySettings = streamSettings.value(amnezia::protocols::xray::realitySettings).toObject();
vlessServer.serverName = realitySettings.value(amnezia::protocols::xray::serverName).toString();
vlessServer.publicKey = realitySettings.value(amnezia::protocols::xray::publicKey).toString();
vlessServer.shortId = realitySettings.value(amnezia::protocols::xray::shortId).toString();
vlessServer.fingerprint = realitySettings.value(amnezia::protocols::xray::fingerprint).toString("chrome");
vlessServer.spiderX = realitySettings.value(amnezia::protocols::xray::spiderX).toString("");
}
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
return result;
}
void ExportController::updateClientManagementModel(int serverIndex, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit updateClientsRequested(serverIndex, container);
}
void ExportController::revokeConfig(int row, int serverIndex, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit revokeClientRequested(serverIndex, row, container);
}
void ExportController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit renameClientRequested(serverIndex, row, clientName, container);
}
QString ExportController::generateVpnUrl(const QByteArray &compressedConfig)
{
return QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
}
QList<QString> ExportController::generateQrCodesFromConfig(const QByteArray &data)
{
return qrCodeUtils::generateQrCodeImageSeries(data);
}
QString ExportController::generateSingleQrCode(const QByteArray &data)
{
auto qr = qrCodeUtils::generateQrCode(data);
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
}

View File

@@ -0,0 +1,77 @@
#ifndef EXPORTCONTROLLER_H
#define EXPORTCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QList>
#include <QString>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
class SshSession;
class VpnConfigurationsController;
using namespace amnezia;
class ExportController : public QObject
{
Q_OBJECT
public:
struct ExportResult
{
ErrorCode errorCode = ErrorCode::NoError;
QString config;
QString nativeConfigString;
QList<QString> qrCodes;
};
explicit ExportController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent = nullptr);
ExportResult generateFullAccessConfig(int serverIndex);
ExportResult generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName);
ExportResult generateOpenVpnConfig(int serverIndex, const QString &clientName);
ExportResult generateWireGuardConfig(int serverIndex, const QString &clientName);
ExportResult generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName);
ExportResult generateXrayConfig(int serverIndex, const QString &clientName);
signals:
void appendClientRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
void updateClientsRequested(int serverIndex, DockerContainer container);
void revokeClientRequested(int serverIndex, int row, DockerContainer container);
void renameClientRequested(int serverIndex, int row, const QString &clientName, DockerContainer container);
public slots:
void updateClientManagementModel(int serverIndex, int containerIndex);
void revokeConfig(int row, int serverIndex, int containerIndex);
void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex);
private:
struct NativeConfigResult
{
ErrorCode errorCode = ErrorCode::NoError;
QJsonObject jsonNativeConfig;
};
NativeConfigResult generateNativeConfig(int serverIndex, DockerContainer container,
const ContainerConfig &containerConfig,
const QString &clientName);
QString generateVpnUrl(const QByteArray &compressedConfig);
QList<QString> generateQrCodesFromConfig(const QByteArray &data);
QString generateSingleQrCode(const QByteArray &data);
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
};
#endif // EXPORTCONTROLLER_H

View File

@@ -0,0 +1,762 @@
#include "importController.h"
#include <QDataStream>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QMap>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QRegularExpressionMatchIterator>
#include <QUrl>
#include <algorithm>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/serialization/serialization.h"
#include "core/utils/utilities.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/qrCodeUtils.h"
#include "core/models/serverConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
namespace
{
ConfigTypes checkConfigFormat(const QString &config)
{
const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap";
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
const QString xrayConfigPatternInbound = "inbounds";
const QString xrayConfigPatternOutbound = "outbounds";
const QString amneziaConfigPattern = "containers";
const QString amneziaConfigPatternHostName = "hostName";
const QString amneziaConfigPatternUserName = "userName";
const QString amneziaConfigPatternPassword = "password";
const QString amneziaFreeConfigPattern = "api_key";
const QString amneziaPremiumConfigPattern = "auth_data";
const QString backupPattern = "Servers/serversList";
if (config.contains(backupPattern)) {
return ConfigTypes::Backup;
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
|| config.contains(amneziaPremiumConfigPattern)
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia;
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard;
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
return ConfigTypes::Xray;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
}
return ConfigTypes::Invalid;
}
} // namespace
ImportController::ImportController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent)
: QObject(parent),
m_serversRepository(serversRepository),
m_appSettingsRepository(appSettingsRepository)
{
}
ImportController::ImportResult ImportController::extractConfigFromData(const QString &data, const QString &configFileName)
{
ImportResult result;
result.configFileName = configFileName;
result.maliciousWarningText.clear();
QString config = data;
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("vless://")) {
configType = ConfigTypes::Xray;
result.config = extractXrayConfig(
Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
if (config.startsWith("vmess://") && config.contains("@")) {
configType = ConfigTypes::Xray;
result.config = extractXrayConfig(
Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
if (config.startsWith("vmess://")) {
configType = ConfigTypes::Xray;
result.config = extractXrayConfig(
Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
if (config.startsWith("trojan://")) {
configType = ConfigTypes::Xray;
result.config = extractXrayConfig(
Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
if (config.startsWith("ss://") && !config.contains("plugin=")) {
configType = ConfigTypes::ShadowSocks;
result.config = extractXrayConfig(
Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
if (config.startsWith("ssd://")) {
QStringList tmp;
QList<std::pair<QString, QJsonObject>> servers = serialization::ssd::Deserialize(config, &prefix, &tmp);
configType = ConfigTypes::ShadowSocks;
// Took only first config from list
if (!servers.isEmpty()) {
result.config = extractXrayConfig(servers.first().first, configType);
}
if (!result.config.empty()) {
result.configType = configType;
return result;
}
}
configType = checkConfigFormat(config);
if (configType == ConfigTypes::Invalid) {
config.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray baUncompressed = qUncompress(ba);
if (!baUncompressed.isEmpty()) {
ba = baUncompressed;
}
config = ba;
configType = checkConfigFormat(config);
}
result.configType = configType;
switch (configType) {
case ConfigTypes::OpenVpn: {
result.config = extractOpenVpnConfig(config);
if (!result.config.empty()) {
checkForMaliciousStrings(result.config, result.maliciousWarningText);
return result;
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
case ConfigTypes::Awg:
case ConfigTypes::WireGuard: {
result.config = extractWireGuardConfig(config, result.configType);
result.isNativeWireGuardConfig = (result.configType == ConfigTypes::WireGuard);
if (!result.config.empty()) {
return result;
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
case ConfigTypes::Xray: {
result.config = extractXrayConfig(config, configType);
if (!result.config.empty()) {
return result;
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
case ConfigTypes::Amnezia: {
result.config = QJsonDocument::fromJson(config.toUtf8()).object();
if (apiUtils::isServerFromApi(result.config)) {
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
apiConfig[apiDefs::key::vpnKey] = data;
result.config[apiDefs::key::apiConfig] = apiConfig;
}
processAmneziaConfig(result.config);
if (!result.config.empty()) {
checkForMaliciousStrings(result.config, result.maliciousWarningText);
return result;
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
case ConfigTypes::Backup: {
result.errorCode = ErrorCode::ImportBackupFileUseRestoreInstead;
return result;
}
case ConfigTypes::Invalid: {
result.errorCode = ErrorCode::ImportInvalidConfigError;
result.configFileName.clear();
return result;
}
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
ImportController::ImportResult ImportController::extractConfigFromQr(const QByteArray &data)
{
ImportResult result;
QString dataStr = QString::fromUtf8(data);
ConfigTypes configType = checkConfigFormat(dataStr);
if (configType != ConfigTypes::Invalid) {
return extractConfigFromData(dataStr, "");
}
QJsonObject dataObj = QJsonDocument::fromJson(data).object();
if (!dataObj.isEmpty()) {
result.config = dataObj;
result.configType = ConfigTypes::Amnezia;
return result;
}
QByteArray ba_uncompressed = qUncompress(data);
if (!ba_uncompressed.isEmpty()) {
result.config = QJsonDocument::fromJson(ba_uncompressed).object();
if (result.config.isEmpty()) {
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
result.configType = ConfigTypes::Amnezia;
return result;
}
QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray baUncompressed = qUncompress(ba);
if (!baUncompressed.isEmpty()) {
ba = baUncompressed;
}
if (!ba.isEmpty()) {
result.config = QJsonDocument::fromJson(ba).object();
if (result.config.isEmpty()) {
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
result.configType = ConfigTypes::Amnezia;
return result;
}
result.errorCode = ErrorCode::ImportInvalidConfigError;
return result;
}
void ImportController::startDecodingQr()
{
m_qrCodeChunks.clear();
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
m_isQrCodeProcessed = true;
}
ImportController::QrParseResult ImportController::parseQrCodeChunk(const QString &code)
{
QrParseResult parseResult;
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
parseResult.chunksTotal = m_totalQrCodeChunksCount;
if (!m_isQrCodeProcessed) {
return parseResult;
}
QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QDataStream s(&ba, QIODevice::ReadOnly);
qint16 magic;
s >> magic;
if (magic == qrCodeUtils::qrMagicCode) {
quint8 chunksCount;
s >> chunksCount;
if (m_totalQrCodeChunksCount != chunksCount) {
m_qrCodeChunks.clear();
}
m_totalQrCodeChunksCount = chunksCount;
quint8 chunkId;
s >> chunkId;
s >> m_qrCodeChunks[chunkId];
m_receivedQrCodeChunksCount = m_qrCodeChunks.size();
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
parseResult.chunksTotal = m_totalQrCodeChunksCount;
if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) {
QByteArray data;
for (int i = 0; i < m_totalQrCodeChunksCount; ++i) {
data.append(m_qrCodeChunks.value(i));
}
ImportResult result = extractConfigFromQr(data);
if (result.errorCode == ErrorCode::NoError) {
parseResult.success = true;
parseResult.importResult = result;
m_isQrCodeProcessed = false;
} else {
m_qrCodeChunks.clear();
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
}
}
} else {
ImportResult result = extractConfigFromQr(code.toUtf8());
if (result.errorCode != ErrorCode::NoError) {
result = extractConfigFromQr(ba);
}
if (result.errorCode == ErrorCode::NoError) {
parseResult.success = true;
parseResult.importResult = result;
m_isQrCodeProcessed = false;
}
}
return parseResult;
}
bool ImportController::isQrDecodingActive() const
{
return m_isQrCodeProcessed;
}
int ImportController::qrChunksReceived() const
{
return m_receivedQrCodeChunksCount;
}
int ImportController::qrChunksTotal() const
{
return m_totalQrCodeChunksCount;
}
void ImportController::importConfig(const QJsonObject &config)
{
ServerCredentials credentials;
credentials.hostName = config.value(configKey::hostName).toString();
credentials.port = config.value(configKey::port).toInt();
credentials.userName = config.value(configKey::userName).toString();
credentials.secretData = config.value(configKey::password).toString();
if (credentials.isValid() || config.contains(configKey::containers)) {
ServerConfig serverConfig = ServerConfig::fromJson(config);
m_serversRepository->addServer(serverConfig);
emit importFinished();
} else if (config.contains(configKey::configVersion)) {
quint16 crc = qChecksum(QJsonDocument(config).toJson());
if (m_serversRepository->hasServerWithCrc(crc)) {
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
} else {
QJsonObject configWithCrc = config;
configWithCrc.insert(configKey::crc, crc);
ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc);
m_serversRepository->addServer(serverConfig);
emit importFinished();
}
} else {
qDebug() << "Failed to import profile";
qDebug().noquote() << QJsonDocument(config).toJson();
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
}
}
QJsonObject ImportController::processNativeWireGuardConfig(const QJsonObject &config)
{
QJsonObject result = config;
auto containers = result.value(configKey::containers).toArray();
if (!containers.isEmpty()) {
auto container = containers.at(0).toObject();
auto serverProtocolConfig = container.value(ContainerUtils::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject();
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object();
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50);
clientProtocolConfig[configKey::junkPacketCount] = junkPacketCount;
clientProtocolConfig[configKey::junkPacketMinSize] = junkPacketMinSize;
clientProtocolConfig[configKey::junkPacketMaxSize] = junkPacketMaxSize;
clientProtocolConfig[configKey::initPacketJunkSize] = "0";
clientProtocolConfig[configKey::responsePacketJunkSize] = "0";
clientProtocolConfig[configKey::initPacketMagicHeader] = "1";
clientProtocolConfig[configKey::responsePacketMagicHeader] = "2";
clientProtocolConfig[configKey::underloadPacketMagicHeader] = "3";
clientProtocolConfig[configKey::transportPacketMagicHeader] = "4";
clientProtocolConfig[configKey::cookieReplyPacketJunkSize] = "0";
clientProtocolConfig[configKey::transportPacketJunkSize] = "0";
clientProtocolConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
clientProtocolConfig[configKey::isObfuscationEnabled] = true;
serverProtocolConfig[configKey::lastConfig] = QString(QJsonDocument(clientProtocolConfig).toJson());
container[configKey::wireguard] = serverProtocolConfig;
containers.replace(0, container);
result[configKey::containers] = containers;
}
return result;
}
ConfigTypes ImportController::checkConfigFormat(const QString &config) const
{
return ::checkConfigFormat(config);
}
QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
{
QJsonObject openVpnConfig;
openVpnConfig[configKey::config] = data;
QJsonObject lastConfig;
lastConfig[configKey::lastConfig] = QString(QJsonDocument(openVpnConfig).toJson());
lastConfig[configKey::isThirdPartyConfig] = true;
QJsonObject containers;
containers.insert(configKey::container, QJsonValue(configKey::amneziaOpenvpn));
containers.insert(configKey::openvpn, QJsonValue(lastConfig));
QJsonArray arr;
arr.push_back(containers);
QString hostName;
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
if (hostNameMatch.hasMatch()) {
hostName = hostNameMatch.captured(1);
}
QJsonObject config;
config[configKey::containers] = arr;
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
if (dnsMatch.hasNext()) {
config[configKey::dns1] = dnsMatch.next().captured(1);
}
if (dnsMatch.hasNext()) {
config[configKey::dns2] = dnsMatch.next().captured(1);
}
config[configKey::hostName] = hostName;
return config;
}
QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const
{
QMap<QString, QString> configMap;
auto configByLines = data.split("\n");
for (const QString &line : configByLines) {
QString trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
}
}
}
QJsonObject lastConfig;
lastConfig[configKey::config] = data;
auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) };
QString hostName;
QString port;
if (!url.host().isEmpty()) {
hostName = url.host();
} else {
qDebug() << "Key parameter" << protocols::wireguard::Endpoint << "is missing or has an invalid format";
return QJsonObject();
}
if (url.port() != -1) {
port = QString::number(url.port());
} else {
port = protocols::wireguard::defaultPort;
}
lastConfig[configKey::hostName] = hostName;
lastConfig[configKey::port] = port.toInt();
if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty()
&& !configMap.value(protocols::wireguard::Address).isEmpty()
&& !configMap.value(protocols::wireguard::PublicKey).isEmpty()) {
lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey);
lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address);
if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey);
} else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey);
}
lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey);
} else {
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
return QJsonObject();
}
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
}
if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
}
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
QString protocolName = configKey::wireguard;
QString protocolVersion;
ConfigTypes detectedType = ConfigTypes::WireGuard;
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
configKey::transportPacketMagicHeader };
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
configKey::transportPacketJunkSize,
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
configKey::specialJunk4, configKey::specialJunk5
};
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
if (hasAllRequiredFields) {
for (const QString &field : requiredJunkFields) {
lastConfig[field] = configMap.value(field);
}
for (const QString &field : optionalJunkFields) {
if (!configMap.value(field).isEmpty()) {
lastConfig[field] = configMap.value(field);
}
}
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
!configMap.value(configKey::specialJunk2).isEmpty() ||
!configMap.value(configKey::specialJunk3).isEmpty() ||
!configMap.value(configKey::specialJunk4).isEmpty() ||
!configMap.value(configKey::specialJunk5).isEmpty();
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
protocolVersion = "2";
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
protocolVersion = "1.5";
}
protocolName = configKey::awg;
detectedType = ConfigTypes::Awg;
}
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
} else {
lastConfig[configKey::mtu] = (protocolName == configKey::awg)
? protocols::awg::defaultMtu
: protocols::wireguard::defaultMtu;
}
QJsonObject wireguardConfig;
wireguardConfig[configKey::lastConfig] = QString(QJsonDocument(lastConfig).toJson());
wireguardConfig[configKey::isThirdPartyConfig] = true;
wireguardConfig[configKey::port] = port;
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
wireguardConfig[configKey::protocolVersion] = protocolVersion;
}
QJsonObject containers;
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;
containers.insert(configKey::container, QJsonValue(containerName));
containers.insert(protocolName, QJsonValue(wireguardConfig));
QJsonArray arr;
arr.push_back(containers);
QJsonObject config;
config[configKey::containers] = arr;
config[configKey::defaultContainer] = containerName;
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
const static QRegularExpression dnsRegExp(
"DNS = "
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatch dnsMatch = dnsRegExp.match(data);
if (dnsMatch.hasMatch()) {
config[configKey::dns1] = dnsMatch.captured(1);
config[configKey::dns2] = dnsMatch.captured(2);
}
config[configKey::hostName] = hostName;
configType = detectedType;
return config;
}
QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description) const
{
QJsonParseError parserErr;
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
QJsonObject xrayVpnConfig;
xrayVpnConfig[configKey::config] = jsonConf.toJson().constData();
QJsonObject lastConfig;
lastConfig[configKey::lastConfig] = jsonConf.toJson().constData();
lastConfig[configKey::isThirdPartyConfig] = true;
QJsonObject containers;
if (configType == ConfigTypes::ShadowSocks) {
containers.insert(configKey::ssxray, QJsonValue(lastConfig));
containers.insert(configKey::container, QJsonValue(configKey::amneziaSsxray));
} else {
containers.insert(configKey::container, QJsonValue(configKey::amneziaXray));
containers.insert(configKey::xray, QJsonValue(lastConfig));
}
QJsonArray arr;
arr.push_back(containers);
QString hostName;
const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)");
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
if (hostNameMatch.hasMatch()) {
hostName = hostNameMatch.captured(1);
}
QJsonObject config;
config[configKey::containers] = arr;
config[configKey::defaultContainer] = (configType == ConfigTypes::ShadowSocks)
? configKey::amneziaSsxray
: configKey::amneziaXray;
if (description.isEmpty()) {
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
} else {
config[configKey::description] = description;
}
config[configKey::hostName] = hostName;
return config;
}
void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const
{
const QJsonArray &containers = serverConfig.value(configKey::containers).toArray();
for (const QJsonValue &container : containers) {
auto containerConfig = container.toObject();
auto containerName = containerConfig[configKey::container].toString();
if (containerName == ContainerUtils::containerToString(DockerContainer::OpenVpn)) {
QString protocolConfig =
containerConfig[ProtocolUtils::protoToString(Proto::OpenVpn)].toObject()[configKey::lastConfig].toString();
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[configKey::config].toString();
// https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst
QStringList dangerousTags {
"up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify"
};
QStringList maliciousStrings;
QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts);
for (const QString &rawLine : lines) {
QString line = rawLine.trimmed();
QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty);
if (dangerousTags.contains(command, Qt::CaseInsensitive)) {
maliciousStrings << rawLine;
}
}
warningText = "This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious "
"scripts, so only add it if you fully trust the provider of this config. ";
if (!maliciousStrings.isEmpty()) {
warningText += "<br>In the imported configuration, potentially dangerous lines were found:";
for (const auto &string : maliciousStrings) {
warningText += QString("<br><i>%1</i>").arg(string);
}
}
}
}
}
void ImportController::processAmneziaConfig(QJsonObject &config) const
{
auto containers = config.value(configKey::containers).toArray();
for (auto i = 0; i < containers.size(); i++) {
auto container = containers.at(i).toObject();
auto dockerContainer = ContainerUtils::containerFromString(container.value(configKey::container).toString());
if (ContainerUtils::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) {
auto containerConfig = container.value(ContainerUtils::containerTypeToProtocolString(dockerContainer)).toObject();
auto protocolConfig = containerConfig.value(configKey::lastConfig).toString();
if (protocolConfig.isEmpty()) {
return;
}
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
jsonConfig[configKey::mtu] =
ContainerUtils::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
containerConfig[configKey::lastConfig] = QString(QJsonDocument(jsonConfig).toJson());
container[ContainerUtils::containerTypeToProtocolString(dockerContainer)] = containerConfig;
containers.replace(i, container);
config.insert(configKey::containers, containers);
}
}
}

View File

@@ -0,0 +1,91 @@
#ifndef IMPORTCONTROLLER_H
#define IMPORTCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QByteArray>
#include <QMap>
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
namespace
{
enum class ConfigTypes {
Amnezia,
OpenVpn,
WireGuard,
Awg,
Xray,
ShadowSocks,
Backup,
Invalid
};
}
using namespace amnezia;
class ImportController : public QObject
{
Q_OBJECT
public:
struct ImportResult
{
ErrorCode errorCode = ErrorCode::NoError;
QJsonObject config;
QString configFileName;
QString maliciousWarningText;
ConfigTypes configType = ConfigTypes::Invalid;
bool isNativeWireGuardConfig = false;
};
explicit ImportController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent = nullptr);
struct QrParseResult {
bool success = false;
ImportResult importResult;
int chunksReceived = 0;
int chunksTotal = 0;
};
ImportResult extractConfigFromData(const QString &data, const QString &configFileName = "");
ImportResult extractConfigFromQr(const QByteArray &data);
void startDecodingQr();
QrParseResult parseQrCodeChunk(const QString &code);
bool isQrDecodingActive() const;
int qrChunksReceived() const;
int qrChunksTotal() const;
void importConfig(const QJsonObject &config);
QJsonObject processNativeWireGuardConfig(const QJsonObject &config);
signals:
void importFinished();
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
void restoreAppConfig(const QByteArray &data);
private:
ConfigTypes checkConfigFormat(const QString &config) const;
QJsonObject extractOpenVpnConfig(const QString &data) const;
QJsonObject extractWireGuardConfig(const QString &data, ConfigTypes &configType) const;
QJsonObject extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description = "") const;
void checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const;
void processAmneziaConfig(QJsonObject &config) const;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
QMap<int, QByteArray> m_qrCodeChunks;
bool m_isQrCodeProcessed = false;
int m_totalQrCodeChunksCount = 0;
int m_receivedQrCodeChunksCount = 0;
};
#endif // IMPORTCONTROLLER_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
#ifndef INSTALLCONTROLLER_H
#define INSTALLCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QProcess>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/models/containerConfig.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
class SshSession;
class InstallerBase;
using namespace amnezia;
class InstallController : public QObject
{
Q_OBJECT
public:
explicit InstallController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent = nullptr);
~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
ErrorCode rebootServer(int serverIndex);
ErrorCode removeAllContainers(int serverIndex);
ErrorCode removeContainer(int serverIndex, DockerContainer container);
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
ErrorCode scanServerForInstalledContainers(int serverIndex);
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config);
ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto,
bool &wasContainerInstalled);
ErrorCode installContainer(int serverIndex, DockerContainer container, int port, TransportProto transportProto,
bool &wasContainerInstalled);
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
ErrorCode mountSftpDrive(const ServerCredentials &credentials, const QString &port, const QString &password, const QString &username);
void stopAllSftpMounts();
void cancelInstallation();
void clearCachedProfile(int serverIndex, DockerContainer container);
ErrorCode validateAndPrepareConfig(int serverIndex);
void validateConfig(int serverIndex);
signals:
void configValidated(bool isValid);
void validationErrorOccurred(ErrorCode errorCode);
void serverIsBusy(const bool isBusy);
void cancelInstallationRequested();
void clientRevocationRequested(int serverIndex, const ContainerConfig &containerConfig, DockerContainer container);
void clientAppendRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession);
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession);
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession);
ErrorCode isUserInSudo(const ServerCredentials &credentials, SshSession &sshSession);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, SshSession &sshSession);
ErrorCode setupServerFirewall(const ServerCredentials &credentials, SshSession &sshSession);
bool isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
ErrorCode prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession);
ErrorCode processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
const ServerCredentials &credentials, SshSession &sshSession,
int serverIndex, const QString &clientName);
void adminAppendRequested(int serverIndex, DockerContainer container,
const ContainerConfig &containerConfig, const QString &clientName);
static void updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut);
QScopedPointer<InstallerBase> createInstaller(DockerContainer container);
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
bool m_cancelInstallation = false;
#ifndef Q_OS_IOS
QList<QSharedPointer<QProcess>> m_sftpMountProcesses;
#endif
};
#endif // INSTALLCONTROLLER_H

View File

@@ -0,0 +1,807 @@
#include "usersController.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "logger.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
using namespace amnezia;
namespace
{
Logger logger("UsersController");
}
UsersController::UsersController(SecureServersRepository* serversRepository, QObject *parent)
: QObject(parent),
m_serversRepository(serversRepository)
{
}
bool UsersController::isClientExists(const QString &clientId, const QJsonArray &clientsTable)
{
for (const QJsonValue &value : std::as_const(clientsTable)) {
if (value.isObject()) {
QJsonObject obj = value.toObject();
if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) {
return true;
}
}
}
return false;
}
int UsersController::clientIndexById(const QString &clientId, const QJsonArray &clientsTable)
{
for (int i = 0; i < clientsTable.size(); ++i) {
if (clientsTable.at(i).isObject()) {
QJsonObject obj = clientsTable.at(i).toObject();
if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) {
return i;
}
}
}
return -1;
}
void UsersController::migration(const QByteArray &clientsTableString, QJsonArray &clientsTable)
{
QJsonObject clientsTableObj = QJsonDocument::fromJson(clientsTableString).object();
for (auto &clientId : clientsTableObj.keys()) {
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = clientsTableObj.value(clientId).toObject().value(configKey::clientName);
client[configKey::userData] = userData;
clientsTable.push_back(client);
}
}
ErrorCode UsersController::wgShow(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, std::vector<WgShowData> &data)
{
if (container != DockerContainer::WireGuard && !ContainerUtils::isAwgContainer(container)) {
return ErrorCode::NoError;
}
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QString showBin = (container == DockerContainer::Awg2)
? QStringLiteral("awg")
: QStringLiteral("wg");
const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1 show all'").arg(showBin);
QString script = sshSession->replaceVars(command, amnezia::genBaseVars(credentials, container, QString(), QString()));
error = sshSession->runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
logger.error() << QString("Failed to execute %1 show command").arg(showBin);
return error;
}
if (stdOut.isEmpty()) {
return error;
}
const auto getStrValue = [](const auto str) { return str.mid(str.indexOf(":") + 1).trimmed(); };
const auto parts = stdOut.split('\n');
const auto peerList = parts.filter("peer:");
const auto latestHandshakeList = parts.filter("latest handshake:");
const auto transferredDataList = parts.filter("transfer:");
const auto allowedIpsList = parts.filter("allowed ips:");
if (allowedIpsList.isEmpty() || latestHandshakeList.isEmpty() || transferredDataList.isEmpty() || peerList.isEmpty()) {
return error;
}
const auto changeHandshakeFormat = [](QString &latestHandshake) {
const std::vector<std::pair<QString, QString>> replaceMap = { { " days", "d" }, { " hours", "h" }, { " minutes", "m" },
{ " seconds", "s" }, { " day", "d" }, { " hour", "h" },
{ " minute", "m" }, { " second", "s" } };
for (const auto &item : replaceMap) {
latestHandshake.replace(item.first, item.second);
}
};
for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size() && i < allowedIpsList.size(); ++i) {
const auto transferredData = getStrValue(transferredDataList[i]).split(",");
auto latestHandshake = getStrValue(latestHandshakeList[i]);
auto serverBytesReceived = transferredData.front().trimmed();
auto serverBytesSent = transferredData.back().trimmed();
auto allowedIps = getStrValue(allowedIpsList[i]);
changeHandshakeFormat(latestHandshake);
serverBytesReceived.chop(QStringLiteral(" received").length());
serverBytesSent.chop(QStringLiteral(" sent").length());
data.push_back({ getStrValue(peerList[i]), latestHandshake, serverBytesSent, serverBytesReceived, allowedIps });
}
return error;
}
ErrorCode UsersController::getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable)
{
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
QString script = sshSession->replaceVars(getOpenVpnClientsList, amnezia::genBaseVars(credentials, container, QString(), QString()));
error = sshSession->runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to retrieve the list of issued certificates on the server";
return error;
}
if (!stdOut.isEmpty()) {
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
certsIds.removeAll("AmneziaReq.crt");
for (auto &openvpnCertId : certsIds) {
openvpnCertId.replace(".crt", "");
if (!isClientExists(openvpnCertId, clientsTable)) {
QJsonObject client;
client[configKey::clientId] = openvpnCertId;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
clientsTable.push_back(client);
count++;
}
}
}
return error;
}
ErrorCode UsersController::getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable)
{
ErrorCode error = ErrorCode::NoError;
QString configPath;
if (container == DockerContainer::Awg) {
configPath = QString::fromLatin1(protocols::awg::serverLegacyConfigPath);
} else if (container == DockerContainer::Awg2) {
configPath = QString::fromLatin1(protocols::awg::serverConfigPath);
} else {
configPath = QString::fromLatin1(protocols::wireguard::serverConfigPath);
}
const QString wireguardConfigString = sshSession->getTextFileFromContainer(container, credentials, configPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server";
return error;
}
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
QStringList wireguardKeys;
for (const auto &line : configLines) {
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
if (configPair.front() == "PublicKey") {
wireguardKeys.push_back(configPair.back());
}
}
for (auto &wireguardKey : wireguardKeys) {
if (!isClientExists(wireguardKey, clientsTable)) {
QJsonObject client;
client[configKey::clientId] = wireguardKey;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
clientsTable.push_back(client);
count++;
}
}
return error;
}
ErrorCode UsersController::getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable)
{
ErrorCode error = ErrorCode::NoError;
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = sshSession->getTextFileFromContainer(container, credentials, serverConfigPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the xray server config file from the server";
return error;
}
QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
if (serverConfig.isNull()) {
logger.error() << "Failed to parse xray server config JSON";
return ErrorCode::InternalError;
}
if (!serverConfig.object().contains(protocols::xray::inbounds) || serverConfig.object()[protocols::xray::inbounds].toArray().isEmpty()) {
logger.error() << "Invalid xray server config structure";
return ErrorCode::InternalError;
}
const QJsonObject inbound = serverConfig.object()[protocols::xray::inbounds].toArray()[0].toObject();
if (!inbound.contains(protocols::xray::settings)) {
logger.error() << "Missing settings in xray inbound config";
return ErrorCode::InternalError;
}
const QJsonObject settings = inbound[protocols::xray::settings].toObject();
if (!settings.contains(protocols::xray::clients)) {
logger.error() << "Missing clients in xray settings config";
return ErrorCode::InternalError;
}
const QJsonArray clients = settings[protocols::xray::clients].toArray();
for (const auto &clientValue : clients) {
const QJsonObject clientObj = clientValue.toObject();
if (!clientObj.contains(protocols::xray::id)) {
logger.error() << "Missing id in xray client config";
continue;
}
QString clientId = clientObj[protocols::xray::id].toString();
QString xrayDefaultUuid = sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error);
xrayDefaultUuid.replace("\n", "");
if (!isClientExists(clientId, clientsTable) && clientId != xrayDefaultUuid) {
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
clientsTable.push_back(client);
count++;
}
}
return error;
}
ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer container)
{
ErrorCode error = ErrorCode::NoError;
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn) {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container));
}
const QByteArray clientsTableString = sshSession.getTextFileFromContainer(container, credentials, clientsTableFile, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the clientsTable file from the server";
emit clientsUpdated(QJsonArray());
return error;
}
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) {
migration(clientsTableString, m_clientsTable);
int count = 0;
if (container == DockerContainer::OpenVpn) {
error = getOpenVpnClients(container, credentials, &sshSession, count, m_clientsTable);
} else if (container == DockerContainer::WireGuard || ContainerUtils::isAwgContainer(container)) {
error = getWireGuardClients(container, credentials, &sshSession, count, m_clientsTable);
} else if (container == DockerContainer::Xray) {
error = getXrayClients(container, credentials, &sshSession, count, m_clientsTable);
}
if (error != ErrorCode::NoError) {
emit clientsUpdated(QJsonArray());
return error;
}
const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson();
if (clientsTableString != newClientsTableString) {
error = sshSession.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
}
}
}
std::vector<WgShowData> data;
wgShow(container, credentials, &sshSession, data);
for (const auto &client : data) {
int i = 0;
for (const auto &it : std::as_const(m_clientsTable)) {
if (it.isObject()) {
QJsonObject obj = it.toObject();
if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == client.clientId) {
QJsonObject userData = obj[configKey::userData].toObject();
if (!client.latestHandshake.isEmpty()) {
userData[configKey::latestHandshake] = client.latestHandshake;
}
if (!client.dataReceived.isEmpty()) {
userData[configKey::dataReceived] = client.dataReceived;
}
if (!client.dataSent.isEmpty()) {
userData[configKey::dataSent] = client.dataSent;
}
if (!client.allowedIps.isEmpty()) {
userData[configKey::allowedIps] = client.allowedIps;
}
obj[configKey::userData] = userData;
m_clientsTable.replace(i, obj);
break;
}
}
++i;
}
}
emit clientsUpdated(m_clientsTable);
return error;
}
ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container)
{
ErrorCode error = ErrorCode::NoError;
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
error = updateClients(serverIndex, container);
if (error != ErrorCode::NoError) {
return error;
}
int existingIndex = clientIndexById(clientId, m_clientsTable);
if (existingIndex >= 0) {
return renameClient(serverIndex, existingIndex, clientName, container, true);
}
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = clientName;
userData[configKey::creationDate] = QDateTime::currentDateTime().toString();
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn) {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container));
}
error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
emit clientAdded(client);
emit clientsUpdated(m_clientsTable);
return error;
}
ErrorCode UsersController::renameClient(int serverIndex, const int row, const QString &clientName,
const DockerContainer container, bool addTimeStamp)
{
if (row < 0 || row >= m_clientsTable.size()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto client = m_clientsTable.at(row).toObject();
auto userData = client[configKey::userData].toObject();
userData[configKey::clientName] = clientName;
if (addTimeStamp) {
userData[configKey::creationDate] = QDateTime::currentDateTime().toString();
}
client[configKey::userData] = userData;
m_clientsTable.replace(row, client);
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn) {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container));
}
ErrorCode error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
if (addTimeStamp) {
emit clientsUpdated(m_clientsTable);
} else {
emit clientRenamed(row, clientName);
}
return error;
}
ErrorCode UsersController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, SshSession* sshSession, QJsonArray &clientsTable)
{
if (row < 0 || row >= clientsTable.size()) {
return ErrorCode::InternalError;
}
auto client = clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
"cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\"
"chmod 666 pki/crl.pem ;\\"
"cp pki/crl.pem .'")
.arg(clientId);
const QString script = sshSession->replaceVars(getOpenVpnCertData, amnezia::genBaseVars(credentials, container, QString(), QString()));
ErrorCode error = sshSession->runScript(credentials, script);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to revoke the certificate";
return error;
}
clientsTable.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
return ErrorCode::NoError;
}
ErrorCode UsersController::revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable)
{
if (row < 0 || row >= clientsTable.size()) {
return ErrorCode::InternalError;
}
ErrorCode error = ErrorCode::NoError;
QString configPath;
if (container == DockerContainer::Awg) {
configPath = QString::fromLatin1(protocols::awg::serverLegacyConfigPath);
} else if (container == DockerContainer::Awg2) {
configPath = QString::fromLatin1(protocols::awg::serverConfigPath);
} else {
configPath = QString::fromLatin1(protocols::wireguard::serverConfigPath);
}
const QString wireguardConfigString = sshSession->getTextFileFromContainer(container, credentials, configPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server";
return error;
}
auto client = clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
for (auto &section : configSections) {
if (section.contains(clientId)) {
configSections.removeOne(section);
break;
}
}
QString newWireGuardConfig = configSections.join("[");
newWireGuardConfig.insert(0, "[");
error = sshSession->uploadTextFileToContainer(container, credentials, newWireGuardConfig, configPath);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the wg conf file to the server";
return error;
}
clientsTable.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn) {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(container));
}
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
bool isAwg2 = (container == DockerContainer::Awg2);
QString command = isAwg2 ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg2 ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'"
).arg(command, iface, configPath);
error = sshSession->runScript(
credentials,
sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, QString(), QString()))
);
if (error != ErrorCode::NoError) {
logger.error() << QString("Failed to execute command '%1 syncconf %2' on the server").arg(command, iface);
return error;
}
return ErrorCode::NoError;
}
ErrorCode UsersController::revokeXray(const int row,
const DockerContainer container,
const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable)
{
if (row < 0 || row >= clientsTable.size()) {
return ErrorCode::InternalError;
}
ErrorCode error = ErrorCode::NoError;
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = sshSession->getTextFileFromContainer(container, credentials, serverConfigPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the xray server config file";
return error;
}
QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
if (serverConfig.isNull()) {
logger.error() << "Failed to parse xray server config JSON";
return ErrorCode::InternalError;
}
auto client = clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
QJsonObject configObj = serverConfig.object();
if (!configObj.contains(protocols::xray::inbounds)) {
logger.error() << "Missing inbounds in xray config";
return ErrorCode::InternalError;
}
QJsonArray inbounds = configObj[protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Empty inbounds array in xray config";
return ErrorCode::InternalError;
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(protocols::xray::settings)) {
logger.error() << "Missing settings in xray inbound config";
return ErrorCode::InternalError;
}
QJsonObject settings = inbound[protocols::xray::settings].toObject();
if (!settings.contains(protocols::xray::clients)) {
logger.error() << "Missing clients in xray settings";
return ErrorCode::InternalError;
}
QJsonArray clients = settings[protocols::xray::clients].toArray();
if (clients.isEmpty()) {
logger.error() << "Empty clients array in xray config";
return ErrorCode::InternalError;
}
for (int i = 0; i < clients.size(); ++i) {
QJsonObject clientObj = clients[i].toObject();
if (clientObj.contains(protocols::xray::id) && clientObj[protocols::xray::id].toString() == clientId) {
clients.removeAt(i);
break;
}
}
settings[protocols::xray::clients] = clients;
inbound[protocols::xray::settings] = settings;
inbounds[0] = inbound;
configObj[protocols::xray::inbounds] = inbounds;
error = sshSession->uploadTextFileToContainer(
container,
credentials,
QJsonDocument(configObj).toJson(),
serverConfigPath
);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload updated xray config";
return error;
}
clientsTable.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable")
.arg(ContainerUtils::containerTypeToString(container));
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file";
}
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
error = sshSession->runScript(
credentials,
sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString()))
);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to restart xray container";
return error;
}
return error;
}
ErrorCode UsersController::revokeClient(int serverIndex, const int index, const DockerContainer container)
{
if (index < 0 || index >= m_clientsTable.size()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
QString clientId = m_clientsTable.at(index).toObject().value(configKey::clientId).toString();
ErrorCode errorCode = ErrorCode::NoError;
switch(container)
{
case DockerContainer::OpenVpn: {
errorCode = revokeOpenVpn(index, container, credentials, serverIndex, &sshSession, m_clientsTable);
break;
}
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Awg2: {
errorCode = revokeWireGuard(index, container, credentials, &sshSession, m_clientsTable);
break;
}
case DockerContainer::Xray: {
errorCode = revokeXray(index, container, credentials, &sshSession, m_clientsTable);
break;
}
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
}
if (errorCode == ErrorCode::NoError) {
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
ContainerConfig containerCfg = m_serversRepository->containerConfig(serverIndex, container);
QString containerClientId = containerCfg.protocolConfig.clientId();
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
emit adminConfigRevoked(serverIndex, container);
}
emit clientRevoked(index);
emit clientsUpdated(m_clientsTable);
}
return errorCode;
}
ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container)
{
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
ErrorCode errorCode = ErrorCode::NoError;
errorCode = updateClients(serverIndex, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
Proto protocol = containerConfig.getProtocolType();
switch(container)
{
case DockerContainer::OpenVpn:
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Awg2:
case DockerContainer::Xray: {
protocol = ContainerUtils::defaultProtocol(container);
break;
}
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
}
QString clientId = containerConfig.protocolConfig.clientId();
int row = clientIndexById(clientId, m_clientsTable);
if (row < 0) {
return errorCode;
}
switch (container)
{
case DockerContainer::OpenVpn: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, &sshSession, m_clientsTable);
break;
}
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Awg2: {
errorCode = revokeWireGuard(row, container, credentials, &sshSession, m_clientsTable);
break;
}
case DockerContainer::Xray: {
errorCode = revokeXray(row, container, credentials, &sshSession, m_clientsTable);
break;
}
default:
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
if (errorCode == ErrorCode::NoError) {
emit adminConfigRevoked(serverIndex, container);
emit clientRevoked(row);
emit clientsUpdated(m_clientsTable);
}
return errorCode;
}

View File

@@ -0,0 +1,76 @@
#ifndef USERSCONTROLLER_H
#define USERSCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
class UsersController : public QObject
{
Q_OBJECT
public:
struct WgShowData
{
QString clientId;
QString latestHandshake;
QString dataReceived;
QString dataSent;
QString allowedIps;
};
explicit UsersController(SecureServersRepository* serversRepository, QObject *parent = nullptr);
signals:
void clientsUpdated(const QJsonArray &clients);
void clientAdded(const QJsonObject &client);
void clientRenamed(int row, const QString &newName);
void clientRevoked(int row);
void adminConfigRevoked(int serverIndex, DockerContainer container);
public slots:
ErrorCode updateClients(int serverIndex, const DockerContainer container);
ErrorCode appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container);
ErrorCode renameClient(int serverIndex, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
ErrorCode revokeClient(int serverIndex, const int index, const DockerContainer container);
ErrorCode revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container);
private:
bool isClientExists(const QString &clientId, const QJsonArray &clientsTable);
int clientIndexById(const QString &clientId, const QJsonArray &clientsTable);
void migration(const QByteArray &clientsTableString, QJsonArray &clientsTable);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
SshSession* sshSession, QJsonArray &clientsTable);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable);
ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable);
ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable);
ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable);
ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
SshSession* sshSession, int &count, QJsonArray &clientsTable);
ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, std::vector<WgShowData> &data);
SecureServersRepository* m_serversRepository;
QJsonArray m_clientsTable;
};
#endif // USERSCONTROLLER_H

View File

@@ -1,887 +0,0 @@
#include "serverController.h"
#include <QCryptographicHash>
#include <QDir>
#include <QEventLoop>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QPointer>
#include <QTemporaryFile>
#include <QThread>
#include <QTimer>
#include <QtConcurrent>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sys/stat.h>
#include <chrono>
#include <thread>
#include "containers/containers_defs.h"
#include "core/networkUtilities.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "logger.h"
#include "settings.h"
#include "utilities.h"
#include "vpnConfigurationController.h"
namespace
{
Logger logger("ServerController");
}
ServerController::ServerController(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings)
{
}
ServerController::~ServerController()
{
m_sshClient.disconnectFromHost();
}
ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
{
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
return error;
}
script.replace("\r", "");
qDebug() << "ServerController::Run script";
QString totalLine;
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
QString currentLine = lines.at(i);
if (totalLine.isEmpty()) {
totalLine = currentLine;
} else {
totalLine = totalLine + "\n" + currentLine;
}
QString lineToExec;
if (currentLine.endsWith("\\")) {
continue;
} else {
lineToExec = totalLine;
totalLine.clear();
}
if (lineToExec.startsWith("#")) {
continue;
}
qDebug().noquote() << lineToExec;
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
if (error != ErrorCode::NoError) {
return error;
}
}
qDebug().noquote() << "ServerController::runScript finished\n";
return ErrorCode::NoError;
}
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
{
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
if (e)
return e;
QString runner =
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
return e;
}
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
const QString &path, libssh::ScpOverwriteMode overwriteMode)
{
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
if (e)
return e;
QString stdOut;
auto cbReadStd = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
// mkdir
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container)));
if (e)
return e;
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
if (e)
return e;
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
if (e)
return e;
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
if (e)
return e;
} else
return ErrorCode::NotImplementedError;
if (stdOut.contains("Error") && stdOut.contains("No such container")) {
return ErrorCode::ServerContainerMissingError;
}
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
return e;
}
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
ErrorCode &errorCode)
{
errorCode = ErrorCode::NoError;
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
errorCode = runScript(credentials, script, cbReadStdOut);
return QByteArray::fromHex(stdOut.toUtf8());
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::ScpOverwriteMode overwriteMode)
{
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
return error;
}
QTemporaryFile localFile;
localFile.open();
localFile.write(data);
localFile.close();
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
if (error != ErrorCode::NoError) {
return error;
}
return ErrorCode::NoError;
}
ErrorCode ServerController::rebootServer(const ServerCredentials &credentials)
{
QString script = QString("sudo reboot");
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
return runScript(credentials, script, cbReadStdOut, cbReadStdErr);
}
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
{
return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
}
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
{
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container)));
}
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate)
{
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
ErrorCode e = ErrorCode::NoError;
e = isUserInSudo(credentials, container);
if (e)
return e;
e = isServerDpkgBusy(credentials, container);
if (e)
return e;
e = installDockerWorker(credentials, container);
if (e)
return e;
qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished";
if (!isUpdate) {
e = isServerPortBusy(credentials, container, config);
if (e)
return e;
}
if (!isUpdate) {
e = isServerPortBusy(credentials, container, config);
if (e)
return e;
}
e = prepareHostWorker(credentials, container, config);
if (e)
return e;
qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished";
removeContainer(credentials, container);
qDebug().noquote() << "ServerController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
e = buildContainerWorker(credentials, container, config);
if (e)
return e;
qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished";
e = runContainerWorker(credentials, container, config);
if (e)
return e;
qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished";
e = configureContainerWorker(credentials, container, config);
if (e)
return e;
qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished";
setupServerFirewall(credentials);
qDebug().noquote() << "ServerController::setupContainer setupServerFirewall finished";
return startupContainerWorker(credentials, container, config);
}
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
QJsonObject &newConfig)
{
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
if (reinstallRequired) {
return setupContainer(credentials, container, newConfig, true);
} else {
ErrorCode e = configureContainerWorker(credentials, container, newConfig);
if (e)
return e;
return startupContainerWorker(credentials, container, newConfig);
}
}
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::OpenVpn) {
if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)
!= newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto))
return true;
if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort))
return true;
}
if (container == DockerContainer::Cloak) {
if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort))
return true;
}
if (container == DockerContainer::ShadowSocks) {
if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
return true;
}
if (ContainerProps::isAwgContainer(container)) {
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
!= newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize))
|| (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)
!= newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize))
|| (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)
!= newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize))
|| (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)
!= newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize))
|| (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)
!= newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader))
|| (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|| (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
!= newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|| (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
return true;
}
if (container == DockerContainer::WireGuard) {
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
return true;
}
if (container == DockerContainer::Socks5Proxy) {
return true;
}
if (container == DockerContainer::Xray) {
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
return true;
}
}
return false;
}
ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &client) {
stdOut += data + "\n";
if (data.contains("Automatically restart Docker daemon?")) {
return client.writeResponse("yes");
}
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode error =
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
return ErrorCode::ServerDockerFailedError;
return error;
}
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
// create folder on host
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
}
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
QString dockerFilePath = amnezia::server::getDockerfileFolder(container) + "/Dockerfile";
QString scriptString = QString("sudo rm %1").arg(dockerFilePath);
ErrorCode errorCode = runScript(credentials, replaceVars(scriptString, genVarsForScript(credentials, container)));
if (errorCode)
return errorCode;
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerFilePath);
if (errorCode)
return errorCode;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode error =
runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
if (stdOut.contains("doesn't work on cgroups v2"))
return ErrorCode::ServerDockerOnCgroupsV2;
if (stdOut.contains("cgroup mountpoint does not exist"))
return ErrorCode::ServerCgroupMountpoint;
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
return ErrorCode::DockerPullRateLimit;
return error;
}
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut);
if (stdOut.contains("address already in use"))
return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("is already in use by container"))
return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("invalid publish"))
return ErrorCode::ServerDockerFailedError;
return e;
}
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode e = runContainerScript(credentials, container,
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
return e;
}
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
if (script.isEmpty()) {
return ErrorCode::NoError;
}
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)),
"/opt/amnezia/start.sh");
if (e)
return e;
return runScript(credentials,
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && "
"/opt/amnezia/start.sh\"",
genVarsForScript(credentials, container, config)));
}
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config)
{
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
const QJsonObject &socks5ProxyConfig = config.value(ProtocolProps::protoToString(Proto::Socks5Proxy)).toObject();
Vars vars;
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
// OpenVPN vars
vars.append({ { "$OPENVPN_SUBNET_IP",
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
vars.append({ { "$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
vars.append({ { "$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
vars.append({ { "$OPENVPN_TRANSPORT_PROTO",
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
vars.append({ { "$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } });
if (!isTlsAuth) {
// erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig
vars.append({ { "$OPENVPN_TA_KEY", "" } });
}
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
openvpnConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig) } });
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } });
// ShadowSocks vars
vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
vars.append({ { "$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
// Cloak vars
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
// Xray vars
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
vars.append({ { "$XRAY_SERVER_PORT", xrayConfig.value(config_key::port).toString(protocols::xray::defaultPort) } });
// Wireguard vars
vars.append({ { "$WIREGUARD_SUBNET_IP",
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
vars.append({ { "$WIREGUARD_SUBNET_MASK",
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
vars.append({ { "$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
// IPsec vars
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } });
vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } });
vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } });
vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } });
vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } });
vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } });
vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } });
vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } });
vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } });
vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } });
vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } });
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
// Sftp vars
vars.append({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
// Amnezia wireguard vars
vars.append({ { "$AWG_SUBNET_IP",
amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
// Socks5 proxy vars
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
auto username = socks5ProxyConfig.value(config_key::userName).toString();
auto password = socks5ProxyConfig.value(config_key::password).toString();
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
} else {
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
}
return vars;
}
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
return stdOut;
}
void ServerController::cancelInstallation()
{
m_cancelInstallation = true;
}
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
}
QString ServerController::replaceVars(const QString &script, const Vars &vars)
{
QString s = script;
for (const QPair<QString, QString> &var : vars) {
s.replace(var.first, var.second);
}
return s;
}
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
if (container == DockerContainer::Dns) {
return ErrorCode::NoError;
}
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;
};
const Proto protocol = ContainerProps::defaultProtocol(container);
const QString containerString = ProtocolProps::protoToString(protocol);
const QJsonObject containerConfig = config.value(containerString).toObject();
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
QString defaultPort("%1");
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
// TODO reimplement with netstat
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
for (auto &port : fixedPorts) {
script = script.append("|:%1").arg(port);
}
if (transportProto == "tcpandudp") {
QString tcpProtoScript = script;
QString udpProtoScript = script;
tcpProtoScript.append("' | grep -i tcp");
udpProtoScript.append("' | grep -i udp");
tcpProtoScript.append(" | grep LISTEN");
ErrorCode errorCode =
runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
errorCode = runScript(credentials, replaceVars(udpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (!stdOut.isEmpty()) {
return ErrorCode::ServerPortAlreadyAllocatedError;
}
return ErrorCode::NoError;
}
script = script.append("' | grep -i %1").arg(transportProto);
if (transportProto == "tcp") {
script = script.append(" | grep LISTEN");
}
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (!stdOut.isEmpty()) {
return ErrorCode::ServerPortAlreadyAllocatedError;
}
return ErrorCode::NoError;
}
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
{
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;
};
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required"))
return ErrorCode::ServerUserPasswordRequired;
return error;
}
ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container)
{
m_cancelInstallation = false;
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;
};
QFutureWatcher<ErrorCode> watcher;
QFuture<ErrorCode> future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() {
// max 100 attempts
for (int i = 0; i < 30; ++i) {
if (m_cancelInstallation) {
return ErrorCode::ServerCancelInstallation;
}
stdOut.clear();
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr);
if (stdOut.contains("Packet manager not found"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
return ErrorCode::NoError;
if (stdOut.isEmpty()) {
return ErrorCode::NoError;
} else {
#ifdef MZ_DEBUG
qDebug().noquote() << stdOut;
#endif
emit serverIsBusy(true);
QThread::msleep(10000);
}
}
return ErrorCode::ServerPacketManagerError;
});
QEventLoop wait;
QObject::connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
emit serverIsBusy(false);
return future.result();
}
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
const std::function<QString()> &callback)
{
auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback);
return error;
}

View File

@@ -1,87 +0,0 @@
#ifndef SERVERCONTROLLER_H
#define SERVERCONTROLLER_H
#include <QJsonObject>
#include <QObject>
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "core/sshclient.h"
class Settings;
class VpnConfigurator;
using namespace amnezia;
class ServerController : public QObject
{
Q_OBJECT
public:
ServerController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
~ServerController();
typedef QList<QPair<QString, QString>> Vars;
ErrorCode rebootServer(const ServerCredentials &credentials);
ErrorCode removeAllContainers(const ServerCredentials &credentials);
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false);
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
QJsonObject &newConfig);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject());
ErrorCode uploadTextFileToContainer(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);
QString replaceVars(const QString &script, const Vars &vars);
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
const QJsonObject &config = QJsonObject());
ErrorCode runScript(const ServerCredentials &credentials, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode);
void cancelInstallation();
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
const std::function<QString()> &callback);
private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
bool m_cancelInstallation = false;
libssh::Client m_sshClient;
signals:
void serverIsBusy(const bool isBusy);
};
#endif // SERVERCONTROLLER_H

View File

@@ -0,0 +1,205 @@
#include "serversController.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <AmneziaVPN-Swift.h>
#endif
ServersController::ServersController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent)
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
{
recomputeGatewayStacks();
}
void ServersController::addServer(const ServerConfig &server)
{
m_serversRepository->addServer(server);
}
void ServersController::editServer(int index, const ServerConfig &server)
{
m_serversRepository->editServer(index, server);
}
void ServersController::removeServer(int index)
{
m_serversRepository->removeServer(index);
}
void ServersController::setDefaultServerIndex(int index)
{
m_serversRepository->setDefaultServer(index);
}
void ServersController::setDefaultContainer(int serverIndex, DockerContainer container)
{
m_serversRepository->setDefaultContainer(serverIndex, container);
}
void ServersController::updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config)
{
m_serversRepository->setContainerConfig(serverIndex, container, config);
}
void ServersController::clearCachedProfile(int serverIndex, DockerContainer container)
{
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
}
QJsonArray ServersController::getServersArray() const
{
QJsonArray result;
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& server : servers) {
result.append(server.toJson());
}
return result;
}
QVector<ServerConfig> ServersController::getServers() const
{
return m_serversRepository->servers();
}
ContainerConfig ServersController::getContainerConfig(int serverIndex, DockerContainer container) const
{
return m_serversRepository->containerConfig(serverIndex, container);
}
int ServersController::getDefaultServerIndex() const
{
return m_serversRepository->defaultServerIndex();
}
int ServersController::getServersCount() const
{
return m_serversRepository->serversCount();
}
ServerConfig ServersController::getServerConfig(int serverIndex) const
{
return m_serversRepository->server(serverIndex);
}
ServerCredentials ServersController::getServerCredentials(int serverIndex) const
{
return m_serversRepository->serverCredentials(serverIndex);
}
QPair<QString, QString> ServersController::getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const
{
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
return serverConfig.getDnsPair(isAmneziaDnsEnabled,
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
}
ServersController::GatewayStacksData ServersController::gatewayStacks() const
{
return m_gatewayStacks;
}
void ServersController::recomputeGatewayStacks()
{
GatewayStacksData computed;
bool hasNewTags = false;
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& serverConfig : servers) {
if (serverConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
if (!apiV2) continue;
const QString userCountryCode = apiV2->apiConfig.userCountryCode;
const QString serviceType = apiV2->serviceType();
if (!userCountryCode.isEmpty()) {
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
hasNewTags = true;
}
computed.userCountryCodes.insert(userCountryCode);
}
if (!serviceType.isEmpty()) {
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
hasNewTags = true;
}
computed.serviceTypes.insert(serviceType);
}
}
}
m_gatewayStacks = std::move(computed);
if (hasNewTags) {
emit gatewayStacksExpanded();
}
}
bool ServersController::GatewayStacksData::operator==(const GatewayStacksData &other) const
{
return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes;
}
QJsonObject ServersController::GatewayStacksData::toJson() const
{
QJsonObject json;
QJsonArray userCountryCodesArray;
for (const QString &code : userCountryCodes) {
userCountryCodesArray.append(code);
}
json[apiDefs::key::userCountryCode] = userCountryCodesArray;
QJsonArray serviceTypesArray;
for (const QString &type : serviceTypes) {
serviceTypesArray.append(type);
}
json[apiDefs::key::serviceType] = serviceTypesArray;
return json;
}
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const
{
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& serverConfig : servers) {
if (serverConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
if (!apiV2) return false;
if (apiV2->apiConfig.userCountryCode == userCountryCode
&& apiV2->serviceType() == serviceType
&& apiV2->serviceProtocol() == serviceProtocol) {
return true;
}
}
}
return false;
}
bool ServersController::hasInstalledContainers(int serverIndex) const
{
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
QMap<DockerContainer, ContainerConfig> containers = serverConfig.containers();
for (auto it = containers.begin(); it != containers.end(); ++it) {
DockerContainer container = it.key();
if (ContainerUtils::containerService(container) == ServiceType::Vpn) {
return true;
}
if (container == DockerContainer::SSXray) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,96 @@
#ifndef SERVERSCONTROLLER_H
#define SERVERSCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include <QSet>
#include <QVector>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
class SshSession;
class InstallController;
using namespace amnezia;
/**
* @brief Core business logic controller for server operations
*
* This controller contains pure business logic for managing servers.
*/
class ServersController : public QObject
{
Q_OBJECT
public:
struct GatewayStacksData
{
QSet<QString> userCountryCodes;
QSet<QString> serviceTypes;
bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); }
bool operator==(const GatewayStacksData &other) const;
QJsonObject toJson() const;
};
public:
explicit ServersController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository = nullptr,
QObject *parent = nullptr);
~ServersController() = default;
// Server management
void addServer(const ServerConfig &server);
void editServer(int index, const ServerConfig &server);
void removeServer(int index);
void setDefaultServerIndex(int index);
// Container management
void setDefaultContainer(int serverIndex, DockerContainer container);
void updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config);
// Cache management
void clearCachedProfile(int serverIndex, DockerContainer container);
// Getters
QJsonArray getServersArray() const;
QVector<ServerConfig> getServers() const;
int getDefaultServerIndex() const;
int getServersCount() const;
ServerConfig getServerConfig(int serverIndex) const;
ServerCredentials getServerCredentials(int serverIndex) const;
ContainerConfig getContainerConfig(int serverIndex, DockerContainer container) const;
QPair<QString, QString> getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const;
GatewayStacksData gatewayStacks() const;
// Validation
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const;
bool hasInstalledContainers(int serverIndex) const;
signals:
void gatewayStacksExpanded();
public slots:
void recomputeGatewayStacks();
private:
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
GatewayStacksData m_gatewayStacks;
};
#endif // SERVERSCONTROLLER_H

View File

@@ -0,0 +1,366 @@
#include "settingsController.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <QOperatingSystemVersion>
#include "version.h"
#include "ui/utils/qAutoStart.h"
#include "logger.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
QString getPlatformName()
{
#if defined(Q_OS_WINDOWS)
return "Windows";
#elif defined(Q_OS_ANDROID)
return "Android";
#elif defined(Q_OS_LINUX)
return "Linux";
#elif defined(Q_OS_MACX)
return "MacOS";
#elif defined(Q_OS_IOS)
return "iOS";
#else
return "Unknown";
#endif
}
SettingsController::SettingsController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject* parent)
: QObject(parent),
m_serversRepository(serversRepository),
m_appSettingsRepository(appSettingsRepository)
{
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
m_isDevModeEnabled = m_appSettingsRepository->isDevGatewayEnv();
}
void SettingsController::toggleAmneziaDns(bool enable)
{
m_appSettingsRepository->setUseAmneziaDns(enable);
}
bool SettingsController::isAmneziaDnsEnabled() const
{
return m_appSettingsRepository->useAmneziaDns();
}
QString SettingsController::getPrimaryDns() const
{
return m_appSettingsRepository->primaryDns();
}
void SettingsController::setPrimaryDns(const QString &dns)
{
m_appSettingsRepository->setPrimaryDns(dns);
}
QString SettingsController::getSecondaryDns() const
{
return m_appSettingsRepository->secondaryDns();
}
void SettingsController::setSecondaryDns(const QString &dns)
{
m_appSettingsRepository->setSecondaryDns(dns);
}
bool SettingsController::isLoggingEnabled() const
{
return m_appSettingsRepository->isSaveLogs();
}
void SettingsController::toggleLogging(bool enable)
{
m_appSettingsRepository->setSaveLogs(enable);
#ifndef Q_OS_ANDROID
if (!enable) {
Logger::deInit();
} else {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
Logger::setServiceLogsEnabled(enable);
if (enable) {
m_appSettingsRepository->setLogEnableDate(QDateTime::currentDateTime());
}
}
void SettingsController::clearLogs()
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(false);
Logger::clearServiceLogs();
#endif
}
QByteArray SettingsController::backupAppConfig() const
{
QByteArray data = m_appSettingsRepository->backupAppConfig();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject config = doc.object();
config["AppPlatform"] = getPlatform();
config["Conf/autoStart"] = isAutoStartEnabled();
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled();
return QJsonDocument(config).toJson();
}
ErrorCode SettingsController::restoreAppConfigFromData(const QByteArray &data)
{
if (!m_appSettingsRepository->restoreAppConfig(data)) {
return ErrorCode::RestoreBackupInvalidError;
}
m_serversRepository->invalidateCache();
QJsonObject newConfigData = QJsonDocument::fromJson(data).object();
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) || defined(Q_OS_MACX)
bool autoStart = false;
if (newConfigData.contains("Conf/autoStart")) {
autoStart = newConfigData["Conf/autoStart"].toBool();
}
toggleAutoStart(autoStart);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
bool appSplittunnelingEnabled =
newConfigData.value("Conf/appsSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
emit appSplitTunnelingRouteModeChanged(static_cast<AppsRouteMode>(appSplitTunnelingRouteMode));
#if defined(Q_OS_WINDOWS)
emit appSplitTunnelingRouteModeChanged(AppsRouteMode::VpnAllExceptApps);
#endif
if (newConfigData.contains("AppPlatform")) {
if (newConfigData.value("AppPlatform").toString() != getPlatform()) {
emit appSplitTunnelingClearAppsList();
}
}
emit appSplitTunnelingToggled(appSplittunnelingEnabled);
#endif
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
bool siteSplittunnelingEnabled =
newConfigData.value("Conf/sitesSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
emit siteSplitTunnelingRouteModeChanged(static_cast<RouteMode>(siteSplitTunnelingRouteMode));
emit siteSplitTunnelingToggled(siteSplittunnelingEnabled);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
m_appSettingsRepository->setAutoConnect(false);
m_appSettingsRepository->setStartMinimized(false);
m_appSettingsRepository->setKillSwitchEnabled(false);
m_appSettingsRepository->setStrictKillSwitchEnabled(false);
#endif
return ErrorCode::NoError;
}
QString SettingsController::getAppVersion() const
{
return m_appVersion;
}
void SettingsController::clearSettings()
{
int serverCount = m_serversRepository->serversCount();
m_appSettingsRepository->clearSettings();
m_serversRepository->setServersArray(QJsonArray());
m_serversRepository->setDefaultServer(0);
emit siteSplitTunnelingRouteModeChanged(RouteMode::VpnOnlyForwardSites);
emit siteSplitTunnelingToggled(false);
emit appSplitTunnelingRouteModeChanged(AppsRouteMode::VpnAllExceptApps);
emit appSplitTunnelingToggled(false);
toggleAutoStart(false);
}
bool SettingsController::isAutoConnectEnabled() const
{
return m_appSettingsRepository->isAutoConnect();
}
void SettingsController::toggleAutoConnect(bool enable)
{
m_appSettingsRepository->setAutoConnect(enable);
}
bool SettingsController::isAutoStartEnabled() const
{
return Autostart::isAutostart();
}
void SettingsController::toggleAutoStart(bool enable)
{
Autostart::setAutostart(enable);
if (!enable) {
toggleStartMinimized(false);
}
}
bool SettingsController::isStartMinimizedEnabled() const
{
return m_appSettingsRepository->isStartMinimized();
}
void SettingsController::toggleStartMinimized(bool enable)
{
m_appSettingsRepository->setStartMinimized(enable);
}
bool SettingsController::isScreenshotsEnabled() const
{
return m_appSettingsRepository->isScreenshotsEnabled();
}
void SettingsController::toggleScreenshotsEnabled(bool enable)
{
m_appSettingsRepository->setScreenshotsEnabled(enable);
}
bool SettingsController::isNewsNotificationsEnabled() const
{
return m_appSettingsRepository->isNewsNotifications();
}
void SettingsController::toggleNewsNotificationsEnabled(bool enable)
{
m_appSettingsRepository->setNewsNotifications(enable);
}
bool SettingsController::isKillSwitchEnabled() const
{
return m_appSettingsRepository->isKillSwitchEnabled();
}
void SettingsController::toggleKillSwitch(bool enable)
{
m_appSettingsRepository->setKillSwitchEnabled(enable);
}
bool SettingsController::isStrictKillSwitchEnabled() const
{
return m_appSettingsRepository->isStrictKillSwitchEnabled();
}
void SettingsController::toggleStrictKillSwitch(bool enable)
{
m_appSettingsRepository->setStrictKillSwitchEnabled(enable);
}
QString SettingsController::getInstallationUuid(bool createIfNotExists) const
{
return m_appSettingsRepository->getInstallationUuid(createIfNotExists);
}
void SettingsController::enableDevMode()
{
m_isDevModeEnabled = true;
}
bool SettingsController::isDevModeEnabled() const
{
return m_isDevModeEnabled;
}
void SettingsController::resetGatewayEndpoint()
{
m_appSettingsRepository->resetGatewayEndpoint();
}
void SettingsController::setGatewayEndpoint(const QString &endpoint)
{
m_appSettingsRepository->setGatewayEndpoint(endpoint);
}
QString SettingsController::getGatewayEndpoint() const
{
return m_appSettingsRepository->isDevGatewayEnv() ? "Dev endpoint" : m_appSettingsRepository->getGatewayEndpoint();
}
bool SettingsController::isDevGatewayEnv() const
{
return m_appSettingsRepository->isDevGatewayEnv();
}
void SettingsController::toggleDevGatewayEnv(bool enabled)
{
m_appSettingsRepository->toggleDevGatewayEnv(enabled);
if (enabled) {
m_appSettingsRepository->setDevGatewayEndpoint();
} else {
m_appSettingsRepository->resetGatewayEndpoint();
}
}
bool SettingsController::isHomeAdLabelVisible() const
{
return m_appSettingsRepository->isHomeAdLabelVisible();
}
void SettingsController::disableHomeAdLabel()
{
m_appSettingsRepository->disableHomeAdLabel();
}
void SettingsController::checkIfNeedDisableLogs()
{
if (m_appSettingsRepository->isSaveLogs()) {
m_loggingDisableDate = m_appSettingsRepository->getLogEnableDate().addDays(14);
if (m_loggingDisableDate <= QDateTime::currentDateTime()) {
toggleLogging(false);
clearLogs();
}
}
}
QString SettingsController::getPlatform() const
{
return getPlatformName();
}
QLocale SettingsController::getAppLanguage() const
{
return m_appSettingsRepository->getAppLanguage();
}
void SettingsController::setAppLanguage(const QLocale &locale)
{
m_appSettingsRepository->setAppLanguage(locale);
}
bool SettingsController::isPremV1MigrationReminderActive() const
{
return m_appSettingsRepository->isPremV1MigrationReminderActive();
}
void SettingsController::disablePremV1MigrationReminder()
{
m_appSettingsRepository->disablePremV1MigrationReminder();
}
QString SettingsController::nextAvailableServerName() const
{
return m_appSettingsRepository->nextAvailableServerName();
}

View File

@@ -0,0 +1,112 @@
#ifndef SETTINGSCONTROLLER_H
#define SETTINGSCONTROLLER_H
#include <QObject>
#include <QJsonObject>
#include <QByteArray>
#include <QDateTime>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
using namespace amnezia;
class SettingsController : public QObject
{
Q_OBJECT
public:
explicit SettingsController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject* parent = nullptr);
~SettingsController() = default;
void toggleAmneziaDns(bool enable);
bool isAmneziaDnsEnabled() const;
QString getPrimaryDns() const;
void setPrimaryDns(const QString &dns);
QString getSecondaryDns() const;
void setSecondaryDns(const QString &dns);
bool isLoggingEnabled() const;
void toggleLogging(bool enable);
void clearLogs();
QByteArray backupAppConfig() const;
ErrorCode restoreAppConfigFromData(const QByteArray &data);
QString getAppVersion() const;
void clearSettings();
bool isAutoConnectEnabled() const;
void toggleAutoConnect(bool enable);
bool isAutoStartEnabled() const;
void toggleAutoStart(bool enable);
bool isStartMinimizedEnabled() const;
void toggleStartMinimized(bool enable);
bool isScreenshotsEnabled() const;
void toggleScreenshotsEnabled(bool enable);
bool isNewsNotificationsEnabled() const;
void toggleNewsNotificationsEnabled(bool enable);
bool isKillSwitchEnabled() const;
void toggleKillSwitch(bool enable);
bool isStrictKillSwitchEnabled() const;
void toggleStrictKillSwitch(bool enable);
QString getInstallationUuid(bool createIfNotExists = true) const;
void enableDevMode();
bool isPremV1MigrationReminderActive() const;
void disablePremV1MigrationReminder();
QString nextAvailableServerName() const;
bool isDevModeEnabled() const;
void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint);
QString getGatewayEndpoint() const;
bool isDevGatewayEnv() const;
void toggleDevGatewayEnv(bool enabled);
bool isHomeAdLabelVisible() const;
void disableHomeAdLabel();
void checkIfNeedDisableLogs();
QLocale getAppLanguage() const;
void setAppLanguage(const QLocale &locale);
signals:
void siteSplitTunnelingRouteModeChanged(RouteMode mode);
void siteSplitTunnelingToggled(bool enabled);
void appSplitTunnelingRouteModeChanged(AppsRouteMode mode);
void appSplitTunnelingToggled(bool enabled);
void appSplitTunnelingClearAppsList();
private:
QString getPlatform() const;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
QString m_appVersion;
QDateTime m_loggingDisableDate;
bool m_isDevModeEnabled = false;
};
#endif

View File

@@ -1,146 +0,0 @@
#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> &settings,
QSharedPointer<ServerController> serverController, QObject *parent)
: QObject { parent }, m_settings(settings), m_serverController(serverController)
{
}
QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator(const Proto protocol)
{
switch (protocol) {
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(m_settings, m_serverController));
case Proto::ShadowSocks: return QScopedPointer<ConfiguratorBase>(new ShadowSocksConfigurator(m_settings, m_serverController));
case Proto::Cloak: return QScopedPointer<ConfiguratorBase>(new CloakConfigurator(m_settings, m_serverController));
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(m_settings, m_serverController, false));
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
default: return QScopedPointer<ConfiguratorBase>();
}
}
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<QString, QString> &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<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container)
{
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();
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
// add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] =
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
}
}
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);
}
}

View File

@@ -1,37 +0,0 @@
#ifndef VPNCONFIGIRATIONSCONTROLLER_H
#define VPNCONFIGIRATIONSCONTROLLER_H
#include <QObject>
#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> &settings, QSharedPointer<ServerController> serverController,
QObject *parent = nullptr);
public slots:
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
QJsonObject &containerConfig);
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container);
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
signals:
private:
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServerController> m_serverController;
};
#endif // VPNCONFIGIRATIONSCONTROLLER_H

View File

@@ -0,0 +1,200 @@
#include "awgInstaller.h"
#include <QPair>
#include <QRandomGenerator>
#include <QSet>
#include <QStringList>
#include <QVector>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/awgProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
AwgInstaller::AwgInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ContainerConfig AwgInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto)
{
ContainerConfig config = createBaseConfig(container, port, transportProto);
bool isAwg2 = (container == DockerContainer::Awg2);
if (auto* awgConfig = config.getAwgProtocolConfig()) {
generateAwgParameters(awgConfig->serverConfig, isAwg2);
if (isAwg2) {
awgConfig->serverConfig.protocolVersion = "2";
}
}
return config;
}
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig, bool isAwg2)
{
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50);
int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150);
int s3 = QRandomGenerator::global()->bounded(0, 64);
int s4 = QRandomGenerator::global()->bounded(0, 20);
// Ensure all values are unique and don't create equal packet sizes
QSet<int> usedValues;
usedValues.insert(s1);
while (usedValues.contains(s2) || s1 + amnezia::AwgConstant::messageInitiationSize == s2 + amnezia::AwgConstant::messageResponseSize) {
s2 = QRandomGenerator::global()->bounded(15, 150);
}
usedValues.insert(s2);
while (usedValues.contains(s3) || s1 + amnezia::AwgConstant::messageInitiationSize == s3 + amnezia::AwgConstant::messageCookieReplySize
|| s2 + amnezia::AwgConstant::messageResponseSize == s3 + amnezia::AwgConstant::messageCookieReplySize) {
s3 = QRandomGenerator::global()->bounded(0, 64);
}
usedValues.insert(s3);
while (usedValues.contains(s4)) {
s4 = QRandomGenerator::global()->bounded(0, 20);
}
QString initPacketJunkSize = QString::number(s1);
QString responsePacketJunkSize = QString::number(s2);
QString cookieReplyPacketJunkSize = QString::number(s3);
QString transportPacketJunkSize = QString::number(s4);
QString initPacketMagicHeader;
QString responsePacketMagicHeader;
QString underloadPacketMagicHeader;
QString transportPacketMagicHeader;
if (isAwg2) {
// AWG 2.0: use range format for magic headers
QVector<QPair<QString, QString>> headersValue;
int min = 5;
auto max = (std::numeric_limits<qint32>::max)();
while (headersValue.size() != 4) {
auto first = QRandomGenerator::global()->bounded(min, max);
auto second = QRandomGenerator::global()->bounded(first, max);
min = second;
headersValue.push_back(QPair<QString, QString>(QString::number(first), QString::number(second)));
}
initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second;
responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second;
underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second;
transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second;
} else {
// AWG legacy: use single values for magic headers
QSet<QString> headersValue;
while (headersValue.size() != 4) {
auto max = (std::numeric_limits<qint32>::max)();
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max)));
}
auto headersValueList = headersValue.values();
initPacketMagicHeader = headersValueList.at(0);
responsePacketMagicHeader = headersValueList.at(1);
underloadPacketMagicHeader = headersValueList.at(2);
transportPacketMagicHeader = headersValueList.at(3);
}
serverConfig.junkPacketCount = junkPacketCount;
serverConfig.junkPacketMinSize = junkPacketMinSize;
serverConfig.junkPacketMaxSize = junkPacketMaxSize;
serverConfig.initPacketJunkSize = initPacketJunkSize;
serverConfig.responsePacketJunkSize = responsePacketJunkSize;
serverConfig.initPacketMagicHeader = initPacketMagicHeader;
serverConfig.responsePacketMagicHeader = responsePacketMagicHeader;
serverConfig.underloadPacketMagicHeader = underloadPacketMagicHeader;
serverConfig.transportPacketMagicHeader = transportPacketMagicHeader;
serverConfig.cookieReplyPacketJunkSize = cookieReplyPacketJunkSize;
serverConfig.transportPacketJunkSize = transportPacketJunkSize;
serverConfig.specialJunk1 = protocols::awg::defaultSpecialJunk1;
serverConfig.specialJunk2 = protocols::awg::defaultSpecialJunk2;
serverConfig.specialJunk3 = protocols::awg::defaultSpecialJunk3;
serverConfig.specialJunk4 = protocols::awg::defaultSpecialJunk4;
serverConfig.specialJunk5 = protocols::awg::defaultSpecialJunk5;
}
ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
// Use appropriate config path based on container type
QString configPath = protocols::awg::serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = protocols::awg::serverLegacyConfigPath;
}
QString serverConfig = sshSession->getTextFileFromContainer(container, credentials, configPath, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QMap<QString, QString> 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());
}
}
}
if (auto* awgConfig = config.getAwgProtocolConfig()) {
QString addressValue = serverConfigMap.value("Address");
QStringList addressParts = addressValue.split("/");
awgConfig->serverConfig.subnetAddress = addressParts.value(0);
if (addressParts.size() > 1) {
awgConfig->serverConfig.subnetCidr = addressParts.value(1);
}
awgConfig->serverConfig.junkPacketCount = serverConfigMap.value(configKey::junkPacketCount);
awgConfig->serverConfig.junkPacketMinSize = serverConfigMap.value(configKey::junkPacketMinSize);
awgConfig->serverConfig.junkPacketMaxSize = serverConfigMap.value(configKey::junkPacketMaxSize);
awgConfig->serverConfig.initPacketJunkSize = serverConfigMap.value(configKey::initPacketJunkSize);
awgConfig->serverConfig.responsePacketJunkSize = serverConfigMap.value(configKey::responsePacketJunkSize);
awgConfig->serverConfig.initPacketMagicHeader = serverConfigMap.value(configKey::initPacketMagicHeader);
awgConfig->serverConfig.responsePacketMagicHeader = serverConfigMap.value(configKey::responsePacketMagicHeader);
awgConfig->serverConfig.underloadPacketMagicHeader = serverConfigMap.value(configKey::underloadPacketMagicHeader);
awgConfig->serverConfig.transportPacketMagicHeader = serverConfigMap.value(configKey::transportPacketMagicHeader);
// hack to parse i1-i5 from commented lines in server config
awgConfig->serverConfig.specialJunk1 = serverConfigMap.value(QString("# ") + configKey::specialJunk1);
awgConfig->serverConfig.specialJunk2 = serverConfigMap.value(QString("# ") + configKey::specialJunk2);
awgConfig->serverConfig.specialJunk3 = serverConfigMap.value(QString("# ") + configKey::specialJunk3);
awgConfig->serverConfig.specialJunk4 = serverConfigMap.value(QString("# ") + configKey::specialJunk4);
awgConfig->serverConfig.specialJunk5 = serverConfigMap.value(QString("# ") + configKey::specialJunk5);
// AWG 2.0 specific fields
if (container == DockerContainer::Awg2) {
awgConfig->serverConfig.protocolVersion = "2";
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
}
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,21 @@
#ifndef AWGINSTALLER_H
#define AWGINSTALLER_H
#include "installerBase.h"
class AwgInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit AwgInstaller(QObject *parent = nullptr);
amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override;
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
private:
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig, bool isAwg2 = false);
};
#endif // AWGINSTALLER_H

View File

@@ -0,0 +1,116 @@
#include "installerBase.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/protocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/wireGuardProtocolConfig.h"
#include "core/models/protocols/openVpnProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/torProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
InstallerBase::InstallerBase(QObject *parent)
: QObject(parent)
{
}
ContainerConfig InstallerBase::generateConfig(DockerContainer container, int port, TransportProto transportProto)
{
return createBaseConfig(container, port, transportProto);
}
ErrorCode InstallerBase::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
Q_UNUSED(container);
Q_UNUSED(credentials);
Q_UNUSED(sshSession);
Q_UNUSED(config);
return ErrorCode::NoError;
}
ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int port, TransportProto transportProto)
{
ContainerConfig config;
config.container = container;
Proto protocol = ContainerUtils::defaultProtocol(container);
QString portStr = QString::number(port);
QString transportProtoStr = ProtocolUtils::transportProtoToString(transportProto, protocol);
switch (protocol) {
case Proto::Awg: {
AwgProtocolConfig awgConfig;
awgConfig.serverConfig.port = portStr;
awgConfig.serverConfig.transportProto = transportProtoStr;
config.protocolConfig = awgConfig;
break;
}
case Proto::WireGuard: {
WireGuardProtocolConfig wgConfig;
wgConfig.serverConfig.port = portStr;
wgConfig.serverConfig.transportProto = transportProtoStr;
config.protocolConfig = wgConfig;
break;
}
case Proto::OpenVpn: {
OpenVpnProtocolConfig ovpnConfig;
ovpnConfig.serverConfig.port = portStr;
ovpnConfig.serverConfig.transportProto = transportProtoStr;
config.protocolConfig = ovpnConfig;
break;
}
case Proto::Xray:
case Proto::SSXray: {
XrayProtocolConfig xrayConfig;
xrayConfig.serverConfig.port = portStr;
xrayConfig.serverConfig.transportProto = transportProtoStr;
config.protocolConfig = xrayConfig;
break;
}
case Proto::Sftp: {
SftpProtocolConfig sftpConfig;
sftpConfig.port = portStr;
config.protocolConfig = sftpConfig;
break;
}
case Proto::Socks5Proxy: {
Socks5ProxyProtocolConfig socks5Config;
socks5Config.port = portStr;
config.protocolConfig = socks5Config;
break;
}
case Proto::Ikev2: {
Ikev2ProtocolConfig ikev2Config;
config.protocolConfig = ikev2Config;
break;
}
case Proto::TorWebSite: {
TorProtocolConfig torConfig;
config.protocolConfig = torConfig;
break;
}
case Proto::Dns: {
DnsProtocolConfig dnsConfig;
config.protocolConfig = dnsConfig;
break;
}
default: {
break;
}
}
return config;
}

View File

@@ -0,0 +1,32 @@
#ifndef INSTALLERBASE_H
#define INSTALLERBASE_H
#include <QObject>
#include <QJsonObject>
#include "core/utils/containerEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
class InstallerBase : public QObject
{
Q_OBJECT
public:
explicit InstallerBase(QObject *parent = nullptr);
virtual amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto);
virtual amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* sshSession, amnezia::ContainerConfig &config);
amnezia::ContainerConfig createBaseConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto);
protected:
};
#endif // INSTALLERBASE_H

View File

@@ -0,0 +1,73 @@
#include "openvpnInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
using namespace amnezia;
using namespace ProtocolUtils;
OpenVpnInstaller::OpenVpnInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ErrorCode OpenVpnInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
QString serverConfig = sshSession->getTextFileFromContainer(container, credentials,
protocols::openvpn::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
continue;
} else {
QStringList parts = trimmedLine.split(" ");
if (parts.count() >= 2) {
QString key = parts[0];
QString value = parts.mid(1).join(" ");
serverConfigMap.insert(key, value);
}
}
}
if (auto* ovpnConfig = config.getOpenVpnProtocolConfig()) {
QString serverValue = serverConfigMap.value("server");
if (!serverValue.isEmpty()) {
QStringList serverParts = serverValue.split(" ");
if (serverParts.count() >= 1) {
ovpnConfig->serverConfig.subnetAddress = serverParts[0];
}
}
ovpnConfig->serverConfig.ncpDisable = serverConfig.contains("ncp-disable");
ovpnConfig->serverConfig.tlsAuth = serverConfig.contains("tls-auth");
QString cipher = serverConfigMap.value("cipher");
if (!cipher.isEmpty()) {
ovpnConfig->serverConfig.cipher = cipher;
}
QString hash = serverConfigMap.value("auth");
if (!hash.isEmpty()) {
ovpnConfig->serverConfig.hash = hash;
}
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,17 @@
#ifndef OPENVPNINSTALLER_H
#define OPENVPNINSTALLER_H
#include "installerBase.h"
class OpenVpnInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit OpenVpnInstaller(QObject *parent = nullptr);
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // OPENVPNINSTALLER_H

View File

@@ -0,0 +1,69 @@
#include "sftpInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/sftpProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
SftpInstaller::SftpInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ContainerConfig SftpInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto)
{
ContainerConfig config = createBaseConfig(container, port, transportProto);
if (auto* sftpConfig = config.getSftpProtocolConfig()) {
sftpConfig->userName = protocols::sftp::defaultUserName;
sftpConfig->password = Utils::getRandomString(16);
}
return config;
}
ErrorCode SftpInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
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 containerName = ContainerUtils::containerToString(container);
QString script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(containerName);
errorCode = sshSession->runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto sftpInfo = stdOut.split(":");
if (sftpInfo.size() < 2) {
return ErrorCode::ServerContainerMissingError;
}
if (auto* sftpConfig = config.getSftpProtocolConfig()) {
sftpConfig->userName = sftpInfo.at(0).trimmed();
sftpConfig->password = sftpInfo.at(1).trimmed();
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,18 @@
#ifndef SFTPINSTALLER_H
#define SFTPINSTALLER_H
#include "installerBase.h"
class SftpInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit SftpInstaller(QObject *parent = nullptr);
amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override;
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // SFTPINSTALLER_H

View File

@@ -0,0 +1,42 @@
#include "socks5Installer.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/utilities.h"
using namespace amnezia;
using namespace ProtocolUtils;
Socks5Installer::Socks5Installer(QObject *parent)
: InstallerBase(parent)
{
}
ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int port, TransportProto transportProto)
{
ContainerConfig config = createBaseConfig(container, port, transportProto);
if (auto* socks5Config = config.getSocks5ProxyProtocolConfig()) {
socks5Config->userName = protocols::socks5Proxy::defaultUserName;
socks5Config->password = Utils::getRandomString(16);
}
return config;
}
ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
Q_UNUSED(container);
Q_UNUSED(credentials);
Q_UNUSED(sshSession);
Q_UNUSED(config);
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,18 @@
#ifndef SOCKS5INSTALLER_H
#define SOCKS5INSTALLER_H
#include "installerBase.h"
class Socks5Installer : public InstallerBase
{
Q_OBJECT
public:
explicit Socks5Installer(QObject *parent = nullptr);
amnezia::ContainerConfig generateConfig(amnezia::DockerContainer container, int port, amnezia::TransportProto transportProto) override;
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // SOCKS5INSTALLER_H

View File

@@ -0,0 +1,57 @@
#include "torInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/protocols/torProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
TorInstaller::TorInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ErrorCode TorInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
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 containerName = ContainerUtils::containerToString(container);
QString script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(containerName);
errorCode = sshSession->runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (stdOut.isEmpty()) {
return ErrorCode::ServerContainerMissingError;
}
QString onion = stdOut;
onion.replace("\n", "");
if (auto* torConfig = config.getTorProtocolConfig()) {
torConfig->serverConfig.site = onion;
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,17 @@
#ifndef TORINSTALLER_H
#define TORINSTALLER_H
#include "installerBase.h"
class TorInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit TorInstaller(QObject *parent = nullptr);
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // TORINSTALLER_H

View File

@@ -0,0 +1,51 @@
#include "wireguardInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
using namespace amnezia;
using namespace ProtocolUtils;
WireguardInstaller::WireguardInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ErrorCode WireguardInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
QString serverConfig = sshSession->getTextFileFromContainer(container, credentials,
protocols::wireguard::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QMap<QString, QString> 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());
}
}
}
if (auto* wgConfig = config.getWireGuardProtocolConfig()) {
wgConfig->serverConfig.subnetAddress = serverConfigMap.value("Address").remove("/24");
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,17 @@
#ifndef WIREGUARDINSTALLER_H
#define WIREGUARDINSTALLER_H
#include "installerBase.h"
class WireguardInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit WireguardInstaller(QObject *parent = nullptr);
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // WIREGUARDINSTALLER_H

View File

@@ -0,0 +1,80 @@
#include "xrayInstaller.h"
#include <QJsonDocument>
#include <QJsonArray>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "logger.h"
namespace {
Logger logger("XrayInstaller");
}
using namespace amnezia;
using namespace ProtocolUtils;
XrayInstaller::XrayInstaller(QObject *parent)
: InstallerBase(parent)
{
}
ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, ContainerConfig &config)
{
ErrorCode errorCode = ErrorCode::NoError;
QString currentConfig = sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
return ErrorCode::InternalError;
}
QJsonObject serverConfig = doc.object();
if (!serverConfig.contains(protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
return ErrorCode::InternalError;
}
QJsonArray inbounds = serverConfig[protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
return ErrorCode::InternalError;
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(protocols::xray::streamSettings)) {
logger.error() << "Inbound missing 'streamSettings' field";
return ErrorCode::InternalError;
}
QJsonObject streamSettings = inbound[protocols::xray::streamSettings].toObject();
QJsonObject realitySettings = streamSettings[protocols::xray::realitySettings].toObject();
if (!realitySettings.contains(protocols::xray::serverNames)) {
logger.error() << "Settings missing 'serverNames' field";
return ErrorCode::InternalError;
}
QString siteName = realitySettings[protocols::xray::serverNames][0].toString();
if (auto* xrayConfig = config.getXrayProtocolConfig()) {
xrayConfig->serverConfig.site = siteName;
}
return ErrorCode::NoError;
}

View File

@@ -0,0 +1,17 @@
#ifndef XRAYINSTALLER_H
#define XRAYINSTALLER_H
#include "installerBase.h"
class XrayInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit XrayInstaller(QObject *parent = nullptr);
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession* serverController, amnezia::ContainerConfig &config) override;
};
#endif // XRAYINSTALLER_H

View File

@@ -0,0 +1,223 @@
#include "apiConfig.h"
#include <QJsonDocument>
#include <QDateTime>
#include "core/utils/api/apiUtils.h"
#include "core/utils/constants/apiKeys.h"
namespace amnezia
{
QJsonObject ApiConfig::Subscription::toJson() const
{
QJsonObject obj;
if (!endDate.isEmpty()) {
obj[apiDefs::key::endDate] = endDate;
}
return obj;
}
ApiConfig::Subscription ApiConfig::Subscription::fromJson(const QJsonObject& json)
{
Subscription sub;
sub.endDate = json.value(apiDefs::key::endDate).toString();
return sub;
}
QJsonObject ApiConfig::ServiceInfo::toJson() const
{
QJsonObject obj;
obj[apiDefs::key::isAdVisible] = isAdVisible;
obj[apiDefs::key::isRenewalAvailable] = isRenewalAvailable;
if (!adHeader.isEmpty()) {
obj[apiDefs::key::adHeader] = adHeader;
}
if (!adDescription.isEmpty()) {
obj[apiDefs::key::adDescription] = adDescription;
}
if (!adEndpoint.isEmpty()) {
obj[apiDefs::key::adEndpoint] = adEndpoint;
}
return obj;
}
ApiConfig::ServiceInfo ApiConfig::ServiceInfo::fromJson(const QJsonObject& json)
{
ServiceInfo info;
info.isAdVisible = json.value(apiDefs::key::isAdVisible).toBool(false);
info.isRenewalAvailable = json.value(apiDefs::key::isRenewalAvailable).toBool(false);
info.adHeader = json.value(apiDefs::key::adHeader).toString();
info.adDescription = json.value(apiDefs::key::adDescription).toString();
info.adEndpoint = json.value(apiDefs::key::adEndpoint).toString();
return info;
}
QJsonObject ApiConfig::PublicKeyInfo::toJson() const
{
QJsonObject obj;
if (!expiresAt.isEmpty()) {
obj[apiDefs::key::expiresAt] = expiresAt;
}
return obj;
}
ApiConfig::PublicKeyInfo ApiConfig::PublicKeyInfo::fromJson(const QJsonObject& json)
{
PublicKeyInfo info;
info.expiresAt = json.value(apiDefs::key::expiresAt).toString();
return info;
}
bool ApiConfig::isPremium() const
{
return serviceType == "amnezia-premium";
}
bool ApiConfig::isFree() const
{
return serviceType == "amnezia-free";
}
bool ApiConfig::isExternalPremium() const
{
return serviceType == "external-premium";
}
bool ApiConfig::isSubscriptionExpired() const
{
if (subscription.endDate.isEmpty()) {
return false;
}
QDateTime endDate = QDateTime::fromString(subscription.endDate, Qt::ISODateWithMs);
if (!endDate.isValid()) {
endDate = QDateTime::fromString(subscription.endDate, Qt::ISODate);
}
if (!endDate.isValid()) {
return false;
}
return endDate < QDateTime::currentDateTimeUtc();
}
QJsonObject ApiConfig::toJson() const
{
QJsonObject obj;
if (!serviceType.isEmpty()) {
obj[apiDefs::key::serviceType] = serviceType;
}
if (!serviceProtocol.isEmpty()) {
obj[QLatin1String("service_protocol")] = serviceProtocol;
}
if (!userCountryCode.isEmpty()) {
obj[QLatin1String("user_country_code")] = userCountryCode;
}
if (!serverCountryCode.isEmpty()) {
obj[apiDefs::key::serverCountryCode] = serverCountryCode;
}
if (!serverCountryName.isEmpty()) {
obj[apiDefs::key::serverCountryName] = serverCountryName;
}
if (!vpnKey.isEmpty()) {
obj[apiDefs::key::vpnKey] = vpnKey;
}
QJsonObject subscriptionObj = subscription.toJson();
if (!subscriptionObj.isEmpty()) {
obj[apiDefs::key::subscription] = subscriptionObj;
}
if (activeDeviceCount > 0) {
obj[apiDefs::key::activeDeviceCount] = activeDeviceCount;
}
if (maxDeviceCount > 0) {
obj[apiDefs::key::maxDeviceCount] = maxDeviceCount;
}
if (issuedConfigs > 0) {
obj[apiDefs::key::issuedConfigs] = issuedConfigs;
}
if (!availableCountries.isEmpty()) {
obj[apiDefs::key::availableCountries] = availableCountries;
}
if (!supportedProtocols.isEmpty()) {
obj[apiDefs::key::supportedProtocols] = supportedProtocols;
}
QJsonObject serviceInfoObj = serviceInfo.toJson();
if (!serviceInfoObj.isEmpty()) {
obj[apiDefs::key::serviceInfo] = serviceInfoObj;
}
QJsonObject publicKeyObj = publicKey.toJson();
if (!publicKeyObj.isEmpty()) {
obj[apiDefs::key::publicKey] = publicKeyObj;
}
if (!stackType.isEmpty()) {
obj[apiDefs::key::stackType] = stackType;
}
if (!cliVersion.isEmpty()) {
obj[apiDefs::key::cliVersion] = cliVersion;
}
if (isTestPurchase) {
obj[apiDefs::key::isTestPurchase] = isTestPurchase;
}
if (isInAppPurchase) {
obj[apiDefs::key::isInAppPurchase] = isInAppPurchase;
}
if (subscriptionExpiredByServer) {
obj[apiDefs::key::subscriptionExpiredByServer] = subscriptionExpiredByServer;
}
return obj;
}
ApiConfig ApiConfig::fromJson(const QJsonObject& json)
{
ApiConfig config;
config.serviceType = json.value(apiDefs::key::serviceType).toString();
config.serviceProtocol = json.value(QLatin1String("service_protocol")).toString();
config.userCountryCode = json.value(QLatin1String("user_country_code")).toString();
config.serverCountryCode = json.value(apiDefs::key::serverCountryCode).toString();
config.serverCountryName = json.value(apiDefs::key::serverCountryName).toString();
config.vpnKey = json.value(apiDefs::key::vpnKey).toString();
QJsonObject subscriptionObj = json.value(apiDefs::key::subscription).toObject();
if (!subscriptionObj.isEmpty()) {
config.subscription = Subscription::fromJson(subscriptionObj);
}
config.activeDeviceCount = json.value(apiDefs::key::activeDeviceCount).toInt(0);
config.maxDeviceCount = json.value(apiDefs::key::maxDeviceCount).toInt(0);
config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0);
config.availableCountries = json.value(apiDefs::key::availableCountries).toArray();
config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray();
QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject();
if (!serviceInfoObj.isEmpty()) {
config.serviceInfo = ServiceInfo::fromJson(serviceInfoObj);
}
QJsonObject publicKeyObj = json.value(apiDefs::key::publicKey).toObject();
if (!publicKeyObj.isEmpty()) {
config.publicKey = PublicKeyInfo::fromJson(publicKeyObj);
}
config.stackType = json.value(apiDefs::key::stackType).toString();
config.cliVersion = json.value(apiDefs::key::cliVersion).toString();
config.isTestPurchase = json.value(apiDefs::key::isTestPurchase).toBool(false);
config.isInAppPurchase = json.value(apiDefs::key::isInAppPurchase).toBool(false);
config.subscriptionExpiredByServer = json.value(apiDefs::key::subscriptionExpiredByServer).toBool(false);
return config;
}
} // namespace amnezia

View File

@@ -0,0 +1,77 @@
#ifndef APICONFIG_H
#define APICONFIG_H
#include <QJsonObject>
#include <QJsonArray>
#include <QString>
#include <QDateTime>
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
namespace amnezia
{
struct ApiConfig
{
QString serviceType;
QString serviceProtocol;
QString userCountryCode;
QString serverCountryCode;
QString serverCountryName;
QString vpnKey;
struct Subscription {
QString endDate;
QJsonObject toJson() const;
static Subscription fromJson(const QJsonObject& json);
};
Subscription subscription;
int activeDeviceCount;
int maxDeviceCount;
int issuedConfigs;
QJsonArray availableCountries;
QJsonArray supportedProtocols;
struct ServiceInfo {
bool isAdVisible = false;
bool isRenewalAvailable = false;
QString adHeader;
QString adDescription;
QString adEndpoint;
QJsonObject toJson() const;
static ServiceInfo fromJson(const QJsonObject& json);
};
ServiceInfo serviceInfo;
struct PublicKeyInfo {
QString expiresAt;
QJsonObject toJson() const;
static PublicKeyInfo fromJson(const QJsonObject& json);
};
PublicKeyInfo publicKey;
QString stackType;
QString cliVersion;
bool isTestPurchase;
bool isInAppPurchase = false;
bool subscriptionExpiredByServer = false;
bool isPremium() const;
bool isFree() const;
bool isExternalPremium() const;
bool isSubscriptionExpired() const;
QJsonObject toJson() const;
static ApiConfig fromJson(const QJsonObject& json);
};
} // namespace amnezia
#endif // APICONFIG_H

View File

@@ -0,0 +1,140 @@
#include "apiV1ServerConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/api/apiUtils.h"
namespace amnezia
{
using namespace ContainerEnumNS;
bool ApiV1ServerConfig::isPremium() const
{
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
return apiEndpoint.contains(premiumV1Endpoint);
}
bool ApiV1ServerConfig::isFree() const
{
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
return apiEndpoint.contains(freeV2Endpoint);
}
QString ApiV1ServerConfig::vpnKey() const
{
QJsonObject json = toJson();
return apiUtils::getPremiumV1VpnKey(json);
}
bool ApiV1ServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig ApiV1ServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
}
return containers.value(container);
}
QJsonObject ApiV1ServerConfig::toJson() const
{
QJsonObject obj;
if (!name.isEmpty()) {
obj[configKey::name] = name;
}
if (!description.isEmpty()) {
obj[configKey::description] = description;
}
if (!protocol.isEmpty()) {
obj[apiDefs::key::protocol] = protocol;
}
if (!apiEndpoint.isEmpty()) {
obj[apiDefs::key::apiEndpoint] = apiEndpoint;
}
if (!apiKey.isEmpty()) {
obj[apiDefs::key::apiKey] = apiKey;
}
obj[configKey::configVersion] = configVersion;
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
QJsonArray containersArray;
for (auto it = containers.begin(); it != containers.end(); ++it) {
QJsonObject containerObj = it.value().toJson();
containersArray.append(containerObj);
}
if (!containersArray.isEmpty()) {
obj[configKey::containers] = containersArray;
}
if (defaultContainer != DockerContainer::None) {
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
}
if (!dns1.isEmpty()) {
obj[configKey::dns1] = dns1;
}
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (crc > 0) {
obj[configKey::crc] = crc;
}
return obj;
}
ApiV1ServerConfig ApiV1ServerConfig::fromJson(const QJsonObject& json)
{
ApiV1ServerConfig config;
config.name = json.value(configKey::name).toString();
config.description = json.value(configKey::description).toString();
config.protocol = json.value(apiDefs::key::protocol).toString();
config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString();
config.apiKey = json.value(apiDefs::key::apiKey).toString();
config.configVersion = json.value(configKey::configVersion).toInt(1);
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
QJsonObject containerObj = val.toObject();
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
QString containerStr = containerObj.value(configKey::container).toString();
DockerContainer container = ContainerUtils::containerFromString(containerStr);
config.containers.insert(container, containerConfig);
}
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.crc = json.value(configKey::crc).toInt(0);
return config;
}
} // namespace amnezia

View File

@@ -0,0 +1,47 @@
#ifndef APIV1SERVERCONFIG_H
#define APIV1SERVERCONFIG_H
#include <QJsonObject>
#include <QMap>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/containerConfig.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
namespace amnezia
{
using namespace ContainerEnumNS;
struct ApiV1ServerConfig {
QString description;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;
QString dns1;
QString dns2;
QString name;
QString protocol;
QString apiEndpoint;
QString apiKey;
int crc;
int configVersion;
bool isPremium() const;
bool isFree() const;
QString vpnKey() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QJsonObject toJson() const;
static ApiV1ServerConfig fromJson(const QJsonObject& json);
};
} // namespace amnezia
#endif // APIV1SERVERCONFIG_H

View File

@@ -0,0 +1,170 @@
#include "apiV2ServerConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/api/apiUtils.h"
#include "core/models/api/apiConfig.h"
#include "core/models/api/authData.h"
namespace amnezia
{
using namespace ContainerEnumNS;
QString ApiV2ServerConfig::vpnKey() const
{
if (!apiConfig.vpnKey.isEmpty()) {
return apiConfig.vpnKey;
}
QJsonObject json = toJson();
return apiUtils::getPremiumV2VpnKey(json);
}
QString ApiV2ServerConfig::serviceType() const
{
return apiConfig.serviceType;
}
QString ApiV2ServerConfig::serviceProtocol() const
{
return apiConfig.serviceProtocol;
}
bool ApiV2ServerConfig::isPremium() const
{
return apiConfig.isPremium();
}
bool ApiV2ServerConfig::isFree() const
{
return apiConfig.isFree();
}
bool ApiV2ServerConfig::isExternalPremium() const
{
return apiConfig.isExternalPremium();
}
bool ApiV2ServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
}
return containers.value(container);
}
QJsonObject ApiV2ServerConfig::toJson() const
{
QJsonObject obj;
if (!name.isEmpty()) {
obj[configKey::name] = name;
}
if (nameOverriddenByUser) {
obj[configKey::nameOverriddenByUser] = true;
}
if (!description.isEmpty()) {
obj[configKey::description] = description;
}
obj[configKey::configVersion] = configVersion;
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
QJsonArray containersArray;
for (auto it = containers.begin(); it != containers.end(); ++it) {
QJsonObject containerObj = it.value().toJson();
containersArray.append(containerObj);
}
if (!containersArray.isEmpty()) {
obj[configKey::containers] = containersArray;
}
if (defaultContainer != DockerContainer::None) {
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
}
if (!dns1.isEmpty()) {
obj[configKey::dns1] = dns1;
}
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (crc > 0) {
obj[configKey::crc] = crc;
}
QJsonObject apiConfigObj = apiConfig.toJson();
if (!apiConfigObj.isEmpty()) {
obj[apiDefs::key::apiConfig] = apiConfigObj;
}
QJsonObject authDataObj = authData.toJson();
if (!authDataObj.isEmpty()) {
obj[QLatin1String("auth_data")] = authDataObj;
}
return obj;
}
ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
{
ApiV2ServerConfig config;
config.name = json.value(configKey::name).toString();
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
config.description = json.value(configKey::description).toString();
config.configVersion = json.value(configKey::configVersion).toInt(2);
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
QJsonObject containerObj = val.toObject();
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
QString containerStr = containerObj.value(configKey::container).toString();
DockerContainer container = ContainerUtils::containerFromString(containerStr);
config.containers.insert(container, containerConfig);
}
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.crc = json.value(configKey::crc).toInt(0);
QJsonObject apiConfigObj = json.value(apiDefs::key::apiConfig).toObject();
if (!apiConfigObj.isEmpty()) {
config.apiConfig = ApiConfig::fromJson(apiConfigObj);
}
QJsonObject authDataObj = json.value(QLatin1String("auth_data")).toObject();
if (!authDataObj.isEmpty()) {
config.authData = AuthData::fromJson(authDataObj);
}
return config;
}
} // namespace amnezia

Some files were not shown because too many files have changed in this diff Show More