mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Merge branch 'dev' into feature/local-proxy-integration
This commit is contained in:
Submodule client/3rd-prebuilt deleted from 51bb4703a4
+70
-47
@@ -29,6 +29,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}")
|
||||
@@ -69,11 +70,15 @@ 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}/client_scripts/clientScripts.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
|
||||
)
|
||||
|
||||
# -- i18n begin
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
@@ -85,20 +90,10 @@ set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
||||
|
||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||
|
||||
set(QM_FILE_LIST "")
|
||||
foreach(FILE ${AMNEZIAVPN_QM_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${FILE} NAME)
|
||||
list(APPEND QM_FILE_LIST "<file>${QM_FILE_NAME}</file>")
|
||||
endforeach()
|
||||
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
qt6_add_translations(${PROJECT}
|
||||
TS_FILES ${AMNEZIAVPN_TS_FILES}
|
||||
RESOURCE_PREFIX "/translations"
|
||||
)
|
||||
# -- i18n end
|
||||
|
||||
set(IS_CI ${CI})
|
||||
@@ -177,6 +172,10 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
if (BUILD_VPN_KEYCHAIN)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--keychain ${BUILD_VPN_KEYCHAIN}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -202,39 +201,15 @@ elseif(APPLE)
|
||||
include(cmake/macos.cmake)
|
||||
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_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
# deploy artifacts required to run the application to the debug build folder
|
||||
if(WIN32)
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x64")
|
||||
else()
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x32")
|
||||
endif()
|
||||
elseif(LINUX)
|
||||
set(DEPLOY_PLATFORM_PATH "linux/client")
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
endif()
|
||||
|
||||
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).
|
||||
@@ -246,3 +221,51 @@ if(COMMAND qt_finalize_executable)
|
||||
else()
|
||||
qt_finalize_target(${PROJECT})
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
if(WIN32)
|
||||
set(deploy_tool_options "--force-openssl --force")
|
||||
endif()
|
||||
|
||||
qt_generate_deploy_qml_app_script(
|
||||
TARGET ${PROJECT}
|
||||
OUTPUT_SCRIPT QT_DEPLOY_SCRIPT
|
||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||
DEPLOY_TOOL_OPTIONS ${deploy_tool_options}
|
||||
)
|
||||
install(SCRIPT ${QT_DEPLOY_SCRIPT}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
|
||||
endif()
|
||||
if (LINUX AND NOT ANDROID)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
|
||||
endif()
|
||||
|
||||
if(OVPN_SCRIPTS)
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${OVPN_SCRIPTS}
|
||||
"$<TARGET_FILE_DIR:${PROJECT}>"
|
||||
)
|
||||
|
||||
install(FILES ${OVPN_SCRIPTS}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
PERMISSIONS
|
||||
OWNER_READ OWNER_EXECUTE
|
||||
GROUP_READ GROUP_EXECUTE
|
||||
WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -39,6 +39,7 @@ android {
|
||||
|
||||
// keeps language resources for only the locales specified below
|
||||
resourceConfigurations += listOf("en", "ru", "b+zh+Hans")
|
||||
ndk.abiFilters += qtTargetAbiList.split(",")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -52,50 +53,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
register("release") {
|
||||
storeFile = providers.environmentVariable("ANDROID_KEYSTORE_PATH").orNull?.let { file(it) }
|
||||
storePassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
|
||||
keyAlias = providers.environmentVariable("ANDROID_KEYSTORE_KEY_ALIAS").orNull
|
||||
keyPassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// exclude coroutine debug resource from release build
|
||||
packaging {
|
||||
resources.excludes += "DebugProbesKt.bin"
|
||||
}
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
|
||||
create("fdroid") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = null
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include(*qtTargetAbiList.split(',').toTypedArray())
|
||||
isUniversalApk = false
|
||||
}
|
||||
}
|
||||
|
||||
// fix for Qt Creator to allow deploying the application to a device
|
||||
// to enable this fix, add the line outputBaseName=android-build to local.properties
|
||||
if (outputBaseName.isNotEmpty()) {
|
||||
applicationVariants.all {
|
||||
outputs.map { it as BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
if (output.outputFileName.endsWith(".apk")) {
|
||||
output.outputFileName = "$outputBaseName-${buildType.name}.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +74,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)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
[versions]
|
||||
agp = "8.5.2"
|
||||
agp = "8.6.1"
|
||||
kotlin = "1.9.24"
|
||||
androidx-core = "1.13.1"
|
||||
androidx-activity = "1.9.1"
|
||||
androidx-annotation = "1.8.2"
|
||||
androidx-biometric = "1.2.0-alpha05"
|
||||
androidx-camera = "1.3.4"
|
||||
androidx-camera = "1.5.3"
|
||||
androidx-fragment = "1.8.2"
|
||||
androidx-security-crypto = "1.1.0-alpha06"
|
||||
androidx-datastore = "1.1.1"
|
||||
|
||||
@@ -26,7 +26,6 @@ plugins {
|
||||
id("settings-property-delegate")
|
||||
}
|
||||
|
||||
rootProject.name = "AmneziaVPN"
|
||||
rootProject.buildFileName = "build.gradle.kts"
|
||||
|
||||
include(":qt")
|
||||
@@ -35,7 +34,6 @@ include(":protocolApi")
|
||||
include(":wireguard")
|
||||
include(":awg")
|
||||
include(":openvpn")
|
||||
include(":cloak")
|
||||
include(":xray")
|
||||
include(":xray:libXray")
|
||||
|
||||
@@ -48,15 +46,7 @@ val qtMinSdkVersion: String by gradleProperties
|
||||
// set default values for all modules
|
||||
configure<SettingsExtension> {
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
compileSdk = androidCompileSdkVersion.substringAfter('-').toInt()
|
||||
compileSdk = androidCompileSdkVersion.split('-')[1].toInt()
|
||||
minSdk = qtMinSdkVersion.toInt()
|
||||
ndkVersion = androidNdkVersion
|
||||
}
|
||||
|
||||
// stop Gradle running by androiddeployqt
|
||||
gradle.taskGraph.whenReady {
|
||||
if (providers.environmentVariable("ANDROIDDEPLOYQT_RUN").isPresent
|
||||
&& !providers.systemProperty("explicitRun").isPresent) {
|
||||
allTasks.forEach { it.enabled = false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +214,7 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
private fun loadLibs() {
|
||||
listOf(
|
||||
"rsapss",
|
||||
"crypto_3",
|
||||
"ssl_3",
|
||||
"ssh"
|
||||
"rsapss"
|
||||
).forEach {
|
||||
loadSharedLibrary(this.applicationContext, it)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) })
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/client_scripts">
|
||||
<file>mac_installer.sh</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
EXTRACT_DIR="$1"
|
||||
INSTALLER_PATH="$2"
|
||||
|
||||
set -e
|
||||
|
||||
echo "[AmneziaVPN] Installer package: $INSTALLER_PATH"
|
||||
|
||||
if [ ! -f "$INSTALLER_PATH" ]; then
|
||||
echo "[AmneziaVPN] ERROR: Installer package not found: $INSTALLER_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PKG_PATH="$INSTALLER_PATH"
|
||||
echo "[AmneziaVPN] Using PKG: $PKG_PATH"
|
||||
|
||||
# Optional: basic signature/gatekeeper checks (non-fatal)
|
||||
if command -v pkgutil >/dev/null 2>&1; then
|
||||
pkgutil --check-signature "$PKG_PATH" || true
|
||||
fi
|
||||
if command -v spctl >/dev/null 2>&1; then
|
||||
spctl -a -vvv -t install "$PKG_PATH" || true
|
||||
fi
|
||||
|
||||
# Run installer with admin privileges via AppleScript (prompts for password)
|
||||
echo "[AmneziaVPN] Running installer..."
|
||||
OSA_CMD='do shell script "/usr/sbin/installer -pkg '"$PKG_PATH"' -target /" with administrator privileges'
|
||||
osascript -e "$OSA_CMD"
|
||||
|
||||
STATUS=$?
|
||||
if [ $STATUS -ne 0 ]; then
|
||||
echo "[AmneziaVPN] ERROR: installer exited with status $STATUS"
|
||||
exit $STATUS
|
||||
fi
|
||||
|
||||
echo "[AmneziaVPN] Cleaning up..."
|
||||
rm -f "$INSTALLER_PATH" || true
|
||||
rm -rf "$EXTRACT_DIR" 2>/dev/null || true
|
||||
|
||||
echo "[AmneziaVPN] Installation completed successfully"
|
||||
exit 0
|
||||
@@ -8,81 +8,11 @@ include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||
|
||||
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
||||
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
||||
|
||||
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
|
||||
|
||||
if(WIN32)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include")
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86_64/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86_64")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
elseif(APPLE AND NOT IOS)
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
endif()
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(IOS)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libz.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/ios/iphone/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libcrypto.a")
|
||||
elseif(ANDROID)
|
||||
set(abi ${CMAKE_ANDROID_ARCH_ABI})
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/android/${abi}")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/android/${abi}/libssh.so")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/android/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libcrypto.a")
|
||||
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/android/${abi}")
|
||||
elseif(LINUX)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/linux/x86_64")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libz.a")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libssh.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/linux/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
||||
endif()
|
||||
|
||||
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
||||
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
||||
|
||||
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${LIBSSH_LIB_PATH}
|
||||
${ZLIB_LIB_PATH}
|
||||
)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${OPENSSL_LIB_SSL_PATH}
|
||||
${OPENSSL_LIB_CRYPTO_PATH}
|
||||
)
|
||||
|
||||
add_compile_definitions(_WINSOCKAPI_)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
|
||||
if(ANDROID)
|
||||
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
||||
@@ -106,12 +36,13 @@ endif()
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
include_directories(
|
||||
${OPENSSL_INCLUDE_DIR}
|
||||
${LIBSSH_INCLUDE_DIR}/include
|
||||
${LIBSSH_ROOT_DIR}/include
|
||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
||||
)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
list(APPEND LIBS OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
find_package(libssh REQUIRED)
|
||||
list(APPEND LIBS ssh::ssh)
|
||||
|
||||
+14
-18
@@ -31,29 +31,25 @@ 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})
|
||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||
)
|
||||
endforeach()
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/xray/android/libxray.aar
|
||||
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
find_package(awg-android REQUIRED)
|
||||
set(LIBS ${LIBS} amnezia::awg-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${AMNEZIA_ANDROID_LIBWG_PATH} ${AMNEZIA_ANDROID_LIBWG_QUICK_PATH})
|
||||
|
||||
find_package(amnezia-libxray REQUIRED)
|
||||
file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
|
||||
find_package(openvpn-pt-android REQUIRED)
|
||||
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
message("Client iOS build")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
@@ -132,17 +130,8 @@ target_sources(${PROJECT} PRIVATE
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(ios/networkextension)
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||
|
||||
|
||||
@@ -23,16 +23,13 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
@@ -140,7 +139,6 @@ target_sources(${PROJECT} PRIVATE
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
@@ -153,19 +151,6 @@ message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Signing OpenVPNAdapter framework"
|
||||
)
|
||||
|
||||
+157
-76
@@ -1,34 +1,70 @@
|
||||
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/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/installers/mtProxyInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.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/controllers/updateController.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 +83,68 @@ 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/installers/mtProxyInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.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/controllers/updateController.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/serverConfigUtils.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 +165,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)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
file(GLOB LOCAL_PROXY_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/local-proxy/*.h)
|
||||
@@ -133,28 +210,35 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||
)
|
||||
|
||||
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}
|
||||
)
|
||||
@@ -162,6 +246,7 @@ set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${CORE_MODELS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
@@ -173,11 +258,11 @@ endif()
|
||||
|
||||
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}
|
||||
@@ -190,37 +275,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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,173 +0,0 @@
|
||||
#include "xray_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include "logger.h"
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
}
|
||||
|
||||
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_serverController->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get server config file";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse current config as JSON
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains("inbounds")) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig["inbounds"].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains("settings")) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject settings = inbound["settings"].toObject();
|
||||
if (!settings.contains("clients")) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray clients = settings["clients"].toArray();
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{"id", clientId},
|
||||
{"flow", "xtls-rprx-vision"}
|
||||
};
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings["clients"] = clients;
|
||||
inbound["settings"] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig["inbounds"] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_serverController->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_serverController->runScript(
|
||||
credentials,
|
||||
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
|
||||
);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
return "";
|
||||
}
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
// Get client ID from prepareServerConfig
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
if (config.isEmpty()) {
|
||||
logger.error() << "Failed to get config template";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString xrayPublicKey =
|
||||
m_serverController->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 "";
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayShortId =
|
||||
m_serverController->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 "";
|
||||
}
|
||||
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 "";
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayClientId);
|
||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
+48
-22
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
+84
-71
@@ -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()
|
||||
@@ -228,7 +241,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
connData.clientId = Utils::getRandomString(32);
|
||||
|
||||
int ret = 0;
|
||||
int nVersion = 1;
|
||||
int nVersion = 0;
|
||||
|
||||
QByteArray clientIdUtf8 = connData.clientId.toUtf8();
|
||||
|
||||
@@ -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
|
||||
+107
-60
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,533 @@
|
||||
#include "xrayConfigurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUuid>
|
||||
#include "logger.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");
|
||||
|
||||
QString normalizeXhttpMode(const QString &m) {
|
||||
const QString t = m.trimmed();
|
||||
if (t.isEmpty() || t.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("auto");
|
||||
}
|
||||
if (t.compare(QLatin1String("Packet-up"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("packet-up");
|
||||
if (t.compare(QLatin1String("Stream-up"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("stream-up");
|
||||
if (t.compare(QLatin1String("Stream-one"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("stream-one");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
// Xray-core: empty → path; "None" in UI → omit (core default path)
|
||||
QString normalizeSessionSeqPlacement(const QString &p)
|
||||
{
|
||||
if (p.isEmpty() || p.compare(QLatin1String("None"), Qt::CaseInsensitive) == 0)
|
||||
return {};
|
||||
return p.toLower();
|
||||
}
|
||||
|
||||
QString normalizeUplinkDataPlacement(const QString &p)
|
||||
{
|
||||
if (p.isEmpty() || p.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("body");
|
||||
if (p.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("auto");
|
||||
if (p.compare(QLatin1String("Query"), Qt::CaseInsensitive) == 0)
|
||||
// "Query" is not valid for uplink payload in splithttp; closest documented mode
|
||||
return QStringLiteral("header");
|
||||
return p.toLower();
|
||||
}
|
||||
|
||||
// splithttp: cookie | header | query | queryInHeader (not "body")
|
||||
QString normalizeXPaddingPlacement(const QString &p)
|
||||
{
|
||||
QString t = p.trimmed();
|
||||
if (t.isEmpty())
|
||||
return QString::fromLatin1(amnezia::protocols::xray::defaultXPaddingPlacement).toLower();
|
||||
if (t.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("queryInHeader");
|
||||
if (t.contains(QLatin1String("queryInHeader"), Qt::CaseInsensitive)
|
||||
|| t.compare(QLatin1String("Query in header"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("queryInHeader");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
// splithttp: repeat-x | tokenish
|
||||
QString normalizeXPaddingMethod(const QString &m)
|
||||
{
|
||||
QString t = m.trimmed();
|
||||
if (t.isEmpty() || t.compare(QLatin1String("Repeat-x"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("repeat-x");
|
||||
if (t.compare(QLatin1String("Tokenish"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("tokenish");
|
||||
if (t.compare(QLatin1String("Random"), Qt::CaseInsensitive) == 0
|
||||
|| t.compare(QLatin1String("Zero"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("repeat-x");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
void putIntRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
|
||||
const char *fallbackMax)
|
||||
{
|
||||
if (minV.isEmpty() && maxV.isEmpty())
|
||||
return;
|
||||
if (minV.isEmpty())
|
||||
minV = QString::fromLatin1(fallbackMin);
|
||||
if (maxV.isEmpty())
|
||||
maxV = QString::fromLatin1(fallbackMax);
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = minV.toInt();
|
||||
r[QStringLiteral("to")] = maxV.toInt();
|
||||
obj[QString::fromUtf8(key)] = r;
|
||||
}
|
||||
|
||||
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
|
||||
void sanitizeXrayNativeConfig(amnezia::ProtocolConfig &pc)
|
||||
{
|
||||
QString c = pc.nativeConfig();
|
||||
if (c.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
bool changed = false;
|
||||
if (c.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.replace(QLatin1String("Mozilla/5.0"), QString::fromLatin1(amnezia::protocols::xray::defaultFingerprint),
|
||||
Qt::CaseInsensitive);
|
||||
changed = true;
|
||||
}
|
||||
const QString legacyListen = QString::fromLatin1(amnezia::protocols::xray::defaultLocalAddr);
|
||||
const QString listenOk = QString::fromLatin1(amnezia::protocols::xray::defaultLocalListenAddr);
|
||||
if (c.contains(legacyListen)) {
|
||||
c.replace(legacyListen, listenOk);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
pc.setNativeConfig(c);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig)
|
||||
{
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
sanitizeXrayNativeConfig(protocolConfig);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Get flow value from settings (default xtls-rprx-vision)
|
||||
QString flowValue = "xtls-rprx-vision";
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
if (!xrayCfg->serverConfig.flow.isEmpty()) {
|
||||
flowValue = xrayCfg->serverConfig.flow;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get server config file";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse current config as JSON
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
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[amnezia::protocols::xray::clients].toArray();
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{amnezia::protocols::xray::id, clientId},
|
||||
};
|
||||
clientConfig[amnezia::protocols::xray::id] = clientId;
|
||||
if (!flowValue.isEmpty()) {
|
||||
clientConfig[amnezia::protocols::xray::flow] = flowValue;
|
||||
}
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings[amnezia::protocols::xray::clients] = clients;
|
||||
inbound[amnezia::protocols::xray::settings] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
|
||||
);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
return "";
|
||||
}
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
|
||||
{
|
||||
QJsonObject streamSettings;
|
||||
const auto &xhttp = srv.xhttp;
|
||||
const auto &mkcp = srv.mkcp;
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
QString networkValue = QStringLiteral("tcp");
|
||||
if (srv.transport == QLatin1String("xhttp"))
|
||||
networkValue = QStringLiteral("xhttp");
|
||||
else if (srv.transport == QLatin1String("mkcp"))
|
||||
networkValue = QStringLiteral("kcp");
|
||||
streamSettings[px::network] = networkValue;
|
||||
|
||||
streamSettings[px::security] = srv.security;
|
||||
|
||||
if (srv.security == QLatin1String("tls")) {
|
||||
QJsonObject tlsSettings;
|
||||
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
|
||||
tlsSettings[px::serverName] = sniEff;
|
||||
const QString alpnEff = srv.alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : srv.alpn;
|
||||
QJsonArray alpnArray;
|
||||
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
|
||||
const QString t = a.trimmed();
|
||||
if (!t.isEmpty())
|
||||
alpnArray.append(t);
|
||||
}
|
||||
if (!alpnArray.isEmpty())
|
||||
tlsSettings[QStringLiteral("alpn")] = alpnArray;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
tlsSettings[px::fingerprint] = fpEff;
|
||||
streamSettings[QStringLiteral("tlsSettings")] = tlsSettings;
|
||||
}
|
||||
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
QJsonObject realSettings;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
realSettings[px::fingerprint] = fpEff;
|
||||
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
|
||||
realSettings[px::serverName] = sniEff;
|
||||
streamSettings[px::realitySettings] = realSettings;
|
||||
}
|
||||
|
||||
// XHTTP — JSON must match Xray-core SplitHTTPConfig (flat xPadding fields, see transport_internet.go)
|
||||
if (srv.transport == QLatin1String("xhttp")) {
|
||||
QJsonObject xo;
|
||||
const QString hostEff = xhttp.host.isEmpty() ? QString::fromLatin1(px::defaultXhttpHost) : xhttp.host;
|
||||
xo[QStringLiteral("host")] = hostEff;
|
||||
if (!xhttp.path.isEmpty())
|
||||
xo[QStringLiteral("path")] = xhttp.path;
|
||||
xo[QStringLiteral("mode")] = normalizeXhttpMode(xhttp.mode);
|
||||
|
||||
if (xhttp.headersTemplate.compare(QLatin1String("HTTP"), Qt::CaseInsensitive) == 0) {
|
||||
QJsonObject headers;
|
||||
headers[QStringLiteral("Host")] = hostEff;
|
||||
xo[QStringLiteral("headers")] = headers;
|
||||
}
|
||||
|
||||
const QString methodEff =
|
||||
xhttp.uplinkMethod.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkMethod) : xhttp.uplinkMethod;
|
||||
xo[QStringLiteral("uplinkHTTPMethod")] = methodEff.toUpper();
|
||||
|
||||
xo[QStringLiteral("noGRPCHeader")] = xhttp.disableGrpc;
|
||||
xo[QStringLiteral("noSSEHeader")] = xhttp.disableSse;
|
||||
|
||||
const QString sessPl = normalizeSessionSeqPlacement(xhttp.sessionPlacement);
|
||||
if (!sessPl.isEmpty())
|
||||
xo[QStringLiteral("sessionPlacement")] = sessPl;
|
||||
const QString seqPl = normalizeSessionSeqPlacement(xhttp.seqPlacement);
|
||||
if (!seqPl.isEmpty())
|
||||
xo[QStringLiteral("seqPlacement")] = seqPl;
|
||||
if (!xhttp.sessionKey.isEmpty())
|
||||
xo[QStringLiteral("sessionKey")] = xhttp.sessionKey;
|
||||
if (!xhttp.seqKey.isEmpty())
|
||||
xo[QStringLiteral("seqKey")] = xhttp.seqKey;
|
||||
|
||||
xo[QStringLiteral("uplinkDataPlacement")] = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
|
||||
if (!xhttp.uplinkDataKey.isEmpty())
|
||||
xo[QStringLiteral("uplinkDataKey")] = xhttp.uplinkDataKey;
|
||||
|
||||
const QString ucs = xhttp.uplinkChunkSize.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkChunkSize)
|
||||
: xhttp.uplinkChunkSize;
|
||||
if (!ucs.isEmpty() && ucs != QLatin1String("0")) {
|
||||
const int v = ucs.toInt();
|
||||
QJsonObject chunkR;
|
||||
chunkR[QStringLiteral("from")] = v;
|
||||
chunkR[QStringLiteral("to")] = v;
|
||||
xo[QStringLiteral("uplinkChunkSize")] = chunkR;
|
||||
}
|
||||
|
||||
if (!xhttp.scMaxBufferedPosts.isEmpty())
|
||||
xo[QStringLiteral("scMaxBufferedPosts")] = xhttp.scMaxBufferedPosts.toLongLong();
|
||||
|
||||
putIntRangeIfAny(xo, "scMaxEachPostBytes", xhttp.scMaxEachPostBytesMin, xhttp.scMaxEachPostBytesMax,
|
||||
px::defaultXhttpScMaxEachPostBytesMin, px::defaultXhttpScMaxEachPostBytesMax);
|
||||
putIntRangeIfAny(xo, "scMinPostsIntervalMs", xhttp.scMinPostsIntervalMsMin, xhttp.scMinPostsIntervalMsMax,
|
||||
px::defaultXhttpScMinPostsIntervalMsMin, px::defaultXhttpScMinPostsIntervalMsMax);
|
||||
putIntRangeIfAny(xo, "scStreamUpServerSecs", xhttp.scStreamUpServerSecsMin, xhttp.scStreamUpServerSecsMax,
|
||||
px::defaultXhttpScStreamUpServerSecsMin, px::defaultXhttpScStreamUpServerSecsMax);
|
||||
|
||||
const auto &pad = xhttp.xPadding;
|
||||
xo[QStringLiteral("xPaddingObfsMode")] = pad.obfsMode;
|
||||
if (pad.obfsMode) {
|
||||
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
|
||||
QJsonObject br;
|
||||
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
|
||||
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
|
||||
: pad.bytesMax.toInt();
|
||||
xo[QStringLiteral("xPaddingBytes")] = br;
|
||||
}
|
||||
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
|
||||
xo[QStringLiteral("xPaddingHeader")] = pad.header.isEmpty() ? QStringLiteral("X-Padding") : pad.header;
|
||||
xo[QStringLiteral("xPaddingPlacement")] = normalizeXPaddingPlacement(
|
||||
pad.placement.isEmpty() ? QString::fromLatin1(px::defaultXPaddingPlacement) : pad.placement);
|
||||
xo[QStringLiteral("xPaddingMethod")] = normalizeXPaddingMethod(
|
||||
pad.method.isEmpty() ? QString::fromLatin1(px::defaultXPaddingMethod) : pad.method);
|
||||
}
|
||||
|
||||
// xmux: Xray has no "enabled" flag; omit object when UI disables multiplex tuning.
|
||||
if (xhttp.xmux.enabled) {
|
||||
QJsonObject mux;
|
||||
auto addMuxRange = [&](const char *key, const QString &a, const QString &b) {
|
||||
if (a.isEmpty() && b.isEmpty())
|
||||
return;
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = a.isEmpty() ? 0 : a.toInt();
|
||||
r[QStringLiteral("to")] = b.isEmpty() ? 0 : b.toInt();
|
||||
mux[QString::fromUtf8(key)] = r;
|
||||
};
|
||||
addMuxRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
|
||||
addMuxRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);
|
||||
addMuxRange("cMaxReuseTimes", xhttp.xmux.cMaxReuseTimesMin, xhttp.xmux.cMaxReuseTimesMax);
|
||||
addMuxRange("hMaxRequestTimes", xhttp.xmux.hMaxRequestTimesMin, xhttp.xmux.hMaxRequestTimesMax);
|
||||
addMuxRange("hMaxReusableSecs", xhttp.xmux.hMaxReusableSecsMin, xhttp.xmux.hMaxReusableSecsMax);
|
||||
if (!xhttp.xmux.hKeepAlivePeriod.isEmpty())
|
||||
mux[QStringLiteral("hKeepAlivePeriod")] = xhttp.xmux.hKeepAlivePeriod.toLongLong();
|
||||
if (!mux.isEmpty())
|
||||
xo[QStringLiteral("xmux")] = mux;
|
||||
}
|
||||
|
||||
streamSettings[QStringLiteral("xhttpSettings")] = xo;
|
||||
}
|
||||
|
||||
if (srv.transport == QLatin1String("mkcp")) {
|
||||
QJsonObject kcpObj;
|
||||
const QString ttiEff = mkcp.tti.isEmpty() ? QString::fromLatin1(px::defaultMkcpTti) : mkcp.tti;
|
||||
const QString upEff = mkcp.uplinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpUplinkCapacity)
|
||||
: mkcp.uplinkCapacity;
|
||||
const QString downEff = mkcp.downlinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpDownlinkCapacity)
|
||||
: mkcp.downlinkCapacity;
|
||||
const QString rbufEff = mkcp.readBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpReadBufferSize)
|
||||
: mkcp.readBufferSize;
|
||||
const QString wbufEff = mkcp.writeBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpWriteBufferSize)
|
||||
: mkcp.writeBufferSize;
|
||||
kcpObj[QStringLiteral("tti")] = ttiEff.toInt();
|
||||
kcpObj[QStringLiteral("uplinkCapacity")] = upEff.toInt();
|
||||
kcpObj[QStringLiteral("downlinkCapacity")] = downEff.toInt();
|
||||
kcpObj[QStringLiteral("readBufferSize")] = rbufEff.toInt();
|
||||
kcpObj[QStringLiteral("writeBufferSize")] = wbufEff.toInt();
|
||||
kcpObj[QStringLiteral("congestion")] = mkcp.congestion;
|
||||
streamSettings[QStringLiteral("kcpSettings")] = kcpObj;
|
||||
}
|
||||
|
||||
return streamSettings;
|
||||
}
|
||||
|
||||
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
const XrayServerConfig *serverConfig = nullptr;
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
serverConfig = &xrayCfg->serverConfig;
|
||||
}
|
||||
|
||||
if (!serverConfig) {
|
||||
logger.error() << "No XrayProtocolConfig found";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
const XrayServerConfig &srv = *serverConfig;
|
||||
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
// Fetch server keys (Reality only)
|
||||
QString xrayPublicKey;
|
||||
QString xrayShortId;
|
||||
|
||||
if (srv.security == "reality") {
|
||||
xrayPublicKey = m_sshSession->getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||
logger.error() << "Failed to get public key";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
xrayShortId = m_sshSession->getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::shortidPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||
logger.error() << "Failed to get short ID";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayShortId.replace("\n", "");
|
||||
}
|
||||
|
||||
// Build outbound
|
||||
QJsonObject userObj;
|
||||
userObj[amnezia::protocols::xray::id] = xrayClientId;
|
||||
userObj[amnezia::protocols::xray::encryption] = "none";
|
||||
if (!srv.flow.isEmpty()) {
|
||||
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
||||
}
|
||||
|
||||
QJsonObject vnextEntry;
|
||||
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
|
||||
vnextEntry[amnezia::protocols::xray::port] = srv.port.toInt();
|
||||
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
|
||||
|
||||
QJsonObject outboundSettings;
|
||||
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound["protocol"] = "vless";
|
||||
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
||||
|
||||
// Build streamSettings
|
||||
QJsonObject streamObj = buildStreamSettings(srv, xrayClientId);
|
||||
|
||||
// Inject Reality keys
|
||||
if (srv.security == "reality") {
|
||||
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
||||
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
||||
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
||||
rs[amnezia::protocols::xray::spiderX] = "";
|
||||
streamObj[amnezia::protocols::xray::realitySettings] = rs;
|
||||
}
|
||||
|
||||
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
|
||||
|
||||
// Build full client config
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
|
||||
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
||||
inboundObj["protocol"] = "socks";
|
||||
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
|
||||
|
||||
QJsonObject clientJson;
|
||||
clientJson["log"] = QJsonObject { { "loglevel", "error" } };
|
||||
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
|
||||
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
|
||||
|
||||
QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||
|
||||
// Return
|
||||
XrayProtocolConfig protocolConfig;
|
||||
protocolConfig.serverConfig = srv;
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
qDebug() << "config:" << config;
|
||||
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
|
||||
clientConfig.id = xrayClientId;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef XRAY_CONFIGURATOR_H
|
||||
#define XRAY_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/models/protocols/xrayProtocolConfig.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;
|
||||
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
|
||||
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
|
||||
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
|
||||
const QString &clientId) const;
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "newsController.h"
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSet>
|
||||
#include <QSharedPointer>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
NewsController::NewsController(SecureAppSettingsRepository *appSettingsRepository,
|
||||
SecureServersRepository *serversRepository)
|
||||
: m_appSettingsRepository(appSettingsRepository),
|
||||
m_serversRepository(serversRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject NewsController::getServicesList() const
|
||||
{
|
||||
if (!m_serversRepository) {
|
||||
return {};
|
||||
}
|
||||
QSet<QString> userCountryCodes;
|
||||
QSet<QString> serviceTypes;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!apiV2->apiConfig.userCountryCode.isEmpty()) {
|
||||
userCountryCodes.insert(apiV2->apiConfig.userCountryCode);
|
||||
}
|
||||
const QString serviceType = apiV2->serviceType();
|
||||
if (!serviceType.isEmpty()) {
|
||||
serviceTypes.insert(serviceType);
|
||||
}
|
||||
}
|
||||
if (userCountryCodes.isEmpty() && serviceTypes.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
{
|
||||
if (!m_serversRepository) {
|
||||
qWarning() << "SecureServersRepository is null, skip fetchNews";
|
||||
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
|
||||
}
|
||||
|
||||
const QJsonObject services = getServicesList();
|
||||
if (services.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());
|
||||
|
||||
if (services.contains(apiDefs::key::userCountryCode)) {
|
||||
payload.insert(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode));
|
||||
}
|
||||
if (services.contains(apiDefs::key::serviceType)) {
|
||||
payload.insert(apiDefs::key::serviceType, services.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);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef NEWSCONTROLLER_H
|
||||
#define NEWSCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
|
||||
class NewsController
|
||||
{
|
||||
public:
|
||||
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
|
||||
SecureServersRepository* serversRepository);
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
|
||||
|
||||
private:
|
||||
QJsonObject getServicesList() const;
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
SecureServersRepository* m_serversRepository;
|
||||
};
|
||||
|
||||
#endif // NEWSCONTROLLER_H
|
||||
@@ -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/serverConfigUtils.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 "e = *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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,958 @@
|
||||
#include "subscriptionController.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QSysInfo>
|
||||
#include <QUuid>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "core/configurators/openVpnConfigurator.h"
|
||||
#include "core/configurators/wireguardConfigurator.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "version.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/api/apiConfig.h"
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace
|
||||
{
|
||||
QString getSubscriptionStatusForRenewal(const ApiConfig &apiConfig)
|
||||
{
|
||||
if (apiConfig.subscriptionExpiredByServer) {
|
||||
return QStringLiteral("expired");
|
||||
}
|
||||
|
||||
if (!apiConfig.subscription.endDate.isEmpty()) {
|
||||
if (apiUtils::isSubscriptionExpired(apiConfig.subscription.endDate)) {
|
||||
return QStringLiteral("expired");
|
||||
}
|
||||
if (apiUtils::isSubscriptionExpiringSoon(apiConfig.subscription.endDate)) {
|
||||
return QStringLiteral("expire_soon");
|
||||
}
|
||||
}
|
||||
|
||||
return QStringLiteral("active");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SubscriptionController::SubscriptionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository)
|
||||
: m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject SubscriptionController::GatewayRequestData::toJsonObject() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
if (!osVersion.isEmpty()) {
|
||||
obj[apiDefs::key::osVersion] = osVersion;
|
||||
}
|
||||
if (!appVersion.isEmpty()) {
|
||||
obj[apiDefs::key::appVersion] = appVersion;
|
||||
}
|
||||
if (!appLanguage.isEmpty()) {
|
||||
obj[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
if (!installationUuid.isEmpty()) {
|
||||
obj[apiDefs::key::uuid] = installationUuid;
|
||||
}
|
||||
if (!userCountryCode.isEmpty()) {
|
||||
obj[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
}
|
||||
if (!serverCountryCode.isEmpty()) {
|
||||
obj[apiDefs::key::serverCountryCode] = serverCountryCode;
|
||||
}
|
||||
if (!serviceType.isEmpty()) {
|
||||
obj[apiDefs::key::serviceType] = serviceType;
|
||||
}
|
||||
if (!serviceProtocol.isEmpty()) {
|
||||
obj[apiDefs::key::serviceProtocol] = serviceProtocol;
|
||||
}
|
||||
if (!authData.isEmpty()) {
|
||||
obj[apiDefs::key::authData] = authData;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
SubscriptionController::ProtocolData SubscriptionController::generateProtocolData(const QString &protocol)
|
||||
{
|
||||
ProtocolData protocolData;
|
||||
if (protocol == configKey::awg) {
|
||||
auto connData = WireguardConfigurator::genClientKeys();
|
||||
protocolData.wireGuardClientPubKey = connData.clientPubKey;
|
||||
protocolData.wireGuardClientPrivKey = connData.clientPrivKey;
|
||||
} else if (protocol == configKey::vless) {
|
||||
protocolData.xrayUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
return protocolData;
|
||||
}
|
||||
|
||||
void SubscriptionController::appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload)
|
||||
{
|
||||
if (protocol == configKey::awg) {
|
||||
apiPayload[apiDefs::key::publicKey] = protocolData.wireGuardClientPubKey;
|
||||
} else if (protocol == configKey::vless) {
|
||||
apiPayload[apiDefs::key::publicKey] = protocolData.xrayUuid;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
const ProtocolData &protocolData, QJsonObject &serverConfigJson)
|
||||
{
|
||||
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(configKey::config).toString();
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
qDebug() << "empty vpn key";
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(ba);
|
||||
if (!ba_uncompressed.isEmpty()) {
|
||||
ba = ba_uncompressed;
|
||||
}
|
||||
|
||||
QString configStr = ba;
|
||||
if (protocol == configKey::awg) {
|
||||
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
|
||||
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
auto containers = newServerConfig.value(configKey::containers).toArray();
|
||||
if (containers.isEmpty()) {
|
||||
qDebug() << "missing containers field";
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
auto container = containers.at(0).toObject();
|
||||
QString containerName = ContainerUtils::containerTypeToString(DockerContainer::Awg);
|
||||
auto serverProtocolConfig = container.value(containerName).toObject();
|
||||
auto clientProtocolConfig =
|
||||
QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object();
|
||||
|
||||
// TODO looks like this block can be removed after v1 configs EOL
|
||||
|
||||
serverProtocolConfig[configKey::junkPacketCount] = clientProtocolConfig.value(configKey::junkPacketCount);
|
||||
serverProtocolConfig[configKey::junkPacketMinSize] = clientProtocolConfig.value(configKey::junkPacketMinSize);
|
||||
serverProtocolConfig[configKey::junkPacketMaxSize] = clientProtocolConfig.value(configKey::junkPacketMaxSize);
|
||||
serverProtocolConfig[configKey::initPacketJunkSize] = clientProtocolConfig.value(configKey::initPacketJunkSize);
|
||||
serverProtocolConfig[configKey::responsePacketJunkSize] = clientProtocolConfig.value(configKey::responsePacketJunkSize);
|
||||
serverProtocolConfig[configKey::initPacketMagicHeader] = clientProtocolConfig.value(configKey::initPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::responsePacketMagicHeader] = clientProtocolConfig.value(configKey::responsePacketMagicHeader);
|
||||
serverProtocolConfig[configKey::underloadPacketMagicHeader] = clientProtocolConfig.value(configKey::underloadPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::transportPacketMagicHeader] = clientProtocolConfig.value(configKey::transportPacketMagicHeader);
|
||||
|
||||
serverProtocolConfig[configKey::cookieReplyPacketJunkSize] = clientProtocolConfig.value(configKey::cookieReplyPacketJunkSize);
|
||||
serverProtocolConfig[configKey::transportPacketJunkSize] = clientProtocolConfig.value(configKey::transportPacketJunkSize);
|
||||
serverProtocolConfig[configKey::specialJunk1] = clientProtocolConfig.value(configKey::specialJunk1);
|
||||
serverProtocolConfig[configKey::specialJunk2] = clientProtocolConfig.value(configKey::specialJunk2);
|
||||
serverProtocolConfig[configKey::specialJunk3] = clientProtocolConfig.value(configKey::specialJunk3);
|
||||
serverProtocolConfig[configKey::specialJunk4] = clientProtocolConfig.value(configKey::specialJunk4);
|
||||
serverProtocolConfig[configKey::specialJunk5] = clientProtocolConfig.value(configKey::specialJunk5);
|
||||
|
||||
//
|
||||
|
||||
container[containerName] = serverProtocolConfig;
|
||||
containers.replace(0, container);
|
||||
newServerConfig[configKey::containers] = containers;
|
||||
configStr = QString(QJsonDocument(newServerConfig).toJson());
|
||||
}
|
||||
|
||||
serverConfigJson = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &userCountryCode,
|
||||
const QByteArray &apiResponseBody)
|
||||
{
|
||||
QJsonObject apiConfig = serverConfigJson.value(apiDefs::key::apiConfig).toObject();
|
||||
|
||||
apiConfig[apiDefs::key::serviceType] = serviceType;
|
||||
apiConfig[apiDefs::key::serviceProtocol] = serviceProtocol;
|
||||
apiConfig[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
|
||||
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
|
||||
}
|
||||
if (responseObj.contains(apiDefs::key::serviceInfo)) {
|
||||
apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject());
|
||||
}
|
||||
}
|
||||
|
||||
serverConfigJson[apiDefs::key::apiConfig] = apiConfig;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email)
|
||||
{
|
||||
const QString trimmedEmail = email.trimmed();
|
||||
if (trimmedEmail.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload.insert(apiDefs::key::email, trimmedEmail);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
|
||||
QString key = responseObject.value(apiDefs::key::config).toString();
|
||||
if (key.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
|
||||
key.replace(QStringLiteral("vpn://"), QString());
|
||||
QByteArray configBytes = QByteArray::fromBase64(key.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray uncompressed = qUncompress(configBytes);
|
||||
if (!uncompressed.isEmpty()) {
|
||||
configBytes = uncompressed;
|
||||
}
|
||||
|
||||
if (configBytes.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
|
||||
QJsonObject configObject = QJsonDocument::fromJson(configBytes).object();
|
||||
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
int *duplicateServerIndex)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload[apiDefs::key::transactionId] = transactionId;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
// Parse the subscription response
|
||||
QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
|
||||
QString key = responseObject.value(QStringLiteral("key")).toString();
|
||||
if (key.isEmpty()) {
|
||||
qWarning().noquote() << "[IAP] Subscription response does not contain a key field";
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
}
|
||||
|
||||
QString normalizedKey = key;
|
||||
normalizedKey.replace(QStringLiteral("vpn://"), QString());
|
||||
|
||||
// Check if server with this VPN key already exists
|
||||
for (int i = 0; i < m_serversRepository->serversCount(); ++i) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(m_serversRepository->serverIdAt(i));
|
||||
QString existingVpnKey = apiV2.has_value() ? apiV2->vpnKey() : QString();
|
||||
existingVpnKey.replace(QStringLiteral("vpn://"), QString());
|
||||
if (!existingVpnKey.isEmpty() && existingVpnKey == normalizedKey) {
|
||||
if (duplicateServerIndex) {
|
||||
*duplicateServerIndex = i;
|
||||
}
|
||||
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists";
|
||||
return ErrorCode::ApiConfigAlreadyAdded;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray configString = QByteArray::fromBase64(normalizedKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray configUncompressed = qUncompress(configString);
|
||||
if (!configUncompressed.isEmpty()) {
|
||||
configString = configUncompressed;
|
||||
}
|
||||
|
||||
if (configString.isEmpty()) {
|
||||
qWarning().noquote() << "[IAP] Subscription response config payload is empty";
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
}
|
||||
|
||||
QJsonObject configObject = QJsonDocument::fromJson(configString).object();
|
||||
|
||||
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
|
||||
|
||||
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
|
||||
ApiV2ServerConfig* apiV2 = &apiV2ServerConfig;
|
||||
apiV2->apiConfig.vpnKey = normalizedKey;
|
||||
apiV2->apiConfig.isTestPurchase = isTestPurchase;
|
||||
apiV2->apiConfig.isInAppPurchase = true;
|
||||
apiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
apiV2->crc = crc;
|
||||
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
newCountryCode,
|
||||
apiV2->serviceType(),
|
||||
serviceProtocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
if (isConnectEvent) {
|
||||
apiPayload[apiDefs::key::isConnectEvent] = true;
|
||||
}
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
|
||||
|
||||
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
|
||||
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
newApiV2->apiConfig.isInAppPurchase = apiV2->apiConfig.isInAppPurchase;
|
||||
newApiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
|
||||
newApiV2->authData = apiV2->authData;
|
||||
newApiV2->crc = apiV2->crc;
|
||||
|
||||
if (apiV2->nameOverriddenByUser) {
|
||||
newApiV2->name = apiV2->name;
|
||||
newApiV2->displayName = apiV2->displayName;
|
||||
newApiV2->nameOverriddenByUser = true;
|
||||
}
|
||||
|
||||
m_serversRepository->editServer(serverId, newApiV2Config.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(newApiV2Config.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (!apiV2->isPremium() && !apiV2->isExternalPremium()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
apiV2->apiConfig.serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
apiV2->containers.clear();
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (!apiV2->isPremium() && !apiV2->isExternalPremium()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
uuid,
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (uuid == m_appSettingsRepository->getInstallationUuid(true)) {
|
||||
apiV2->containers.clear();
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString protocol = configKey::awg;
|
||||
ProtocolData protocolData = generateProtocolData(protocol);
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
protocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(protocol, protocolData, apiPayload);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
|
||||
nativeConfig = jsonConfig.value(configKey::config).toString();
|
||||
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::revokeNativeConfig(const QString &serverId, const QString &serverCountryCode)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString protocol = configKey::awg;
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
protocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, QString &vpnKey)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
vpnKey = apiV2->vpnKey();
|
||||
if (vpnKey.isEmpty()) {
|
||||
vpnKey = apiUtils::getPremiumV2VpnKey(apiV2->toJson());
|
||||
if (vpnKey.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
apiV2->apiConfig.vpnKey = vpnKey;
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
|
||||
{
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (!hasInstalledContainers) {
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
if (isApiKeyExpired(serverId)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void SubscriptionController::removeApiConfig(const QString &serverId)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
QString description = apiV2->description;
|
||||
QString hostName = apiV2->hostName;
|
||||
QString vpncName = QString("%1 (%2) %3")
|
||||
.arg(description)
|
||||
.arg(hostName)
|
||||
.arg("");
|
||||
|
||||
AmneziaVPN::removeVPNC(vpncName.toStdString());
|
||||
#endif
|
||||
|
||||
apiV2->dns1.clear();
|
||||
apiV2->dns2.clear();
|
||||
apiV2->containers.clear();
|
||||
apiV2->hostName.clear();
|
||||
apiV2->defaultContainer = DockerContainer::None;
|
||||
apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{};
|
||||
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
bool SubscriptionController::removeServer(const QString &serverId)
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
qWarning().noquote() << "SubscriptionController::removeServer: not an Api V2 server, id" << serverId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ErrorCode revokeError = deactivateDevice(serverId);
|
||||
if (revokeError != ErrorCode::NoError && revokeError != ErrorCode::ApiNotFoundError) {
|
||||
qWarning().noquote() << "SubscriptionController::removeServer: deactivateDevice failed (error"
|
||||
<< static_cast<int>(revokeError) << "); removing locally anyway.";
|
||||
}
|
||||
|
||||
m_serversRepository->removeServer(serverId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SubscriptionController::isApiKeyExpired(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const QString expiresAt = apiV2->apiConfig.publicKey.expiresAt;
|
||||
|
||||
if (expiresAt.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC();
|
||||
if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SubscriptionController::setCurrentProtocol(const QString &serverId, const QString &protocolName)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value()) {
|
||||
apiV2->apiConfig.serviceProtocol = protocolName;
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
int *duplicateServerIndex)
|
||||
{
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
bool purchaseOk = false;
|
||||
QString originalTransactionId;
|
||||
QString storeTransactionId;
|
||||
QString storeProductId;
|
||||
QString purchaseError;
|
||||
QEventLoop waitPurchase;
|
||||
|
||||
IosController::Instance()->purchaseProduct(productId,
|
||||
[&](bool success, const QString &txId, const QString &purchasedProductId,
|
||||
const QString &originalTxId, const QString &errorString) {
|
||||
purchaseOk = success;
|
||||
originalTransactionId = originalTxId;
|
||||
storeTransactionId = txId;
|
||||
storeProductId = purchasedProductId;
|
||||
purchaseError = errorString;
|
||||
waitPurchase.quit();
|
||||
});
|
||||
waitPurchase.exec();
|
||||
|
||||
if (!purchaseOk || originalTransactionId.isEmpty()) {
|
||||
qDebug() << "IAP purchase failed:" << purchaseError;
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
}
|
||||
qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId
|
||||
<< "originalTransactionId =" << originalTransactionId << "productId =" << storeProductId;
|
||||
|
||||
bool isTestPurchase = IosController::Instance()->isTestFlight();
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
return importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
|
||||
originalTransactionId, isTestPurchase, duplicateServerIndex);
|
||||
#else
|
||||
Q_UNUSED(userCountryCode);
|
||||
Q_UNUSED(serviceType);
|
||||
Q_UNUSED(serviceProtocol);
|
||||
Q_UNUSED(productId);
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
#endif
|
||||
}
|
||||
|
||||
SubscriptionController::AppStoreRestoreResult SubscriptionController::processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol)
|
||||
{
|
||||
AppStoreRestoreResult result;
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
bool restoreSuccess = false;
|
||||
QList<QVariantMap> restoredTransactions;
|
||||
QString restoreError;
|
||||
QEventLoop waitRestore;
|
||||
|
||||
IosController::Instance()->restorePurchases([&](bool success, const QList<QVariantMap> &transactions, const QString &errorString) {
|
||||
restoreSuccess = success;
|
||||
restoredTransactions = transactions;
|
||||
restoreError = errorString;
|
||||
waitRestore.quit();
|
||||
});
|
||||
waitRestore.exec();
|
||||
|
||||
if (!restoreSuccess) {
|
||||
qWarning().noquote() << "[IAP] Restore failed:" << restoreError;
|
||||
result.errorCode = ErrorCode::ApiPurchaseError;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (restoredTransactions.isEmpty()) {
|
||||
qInfo().noquote() << "[IAP] Restore completed, but no transactions were returned";
|
||||
result.errorCode = ErrorCode::ApiNoPurchasedSubscriptionsError;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isTestPurchase = IosController::Instance()->isTestFlight();
|
||||
QSet<QString> processedTransactions;
|
||||
|
||||
for (const QVariantMap &transaction : restoredTransactions) {
|
||||
const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString();
|
||||
const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString();
|
||||
const QString transactionProductId = transaction.value(QStringLiteral("productId")).toString();
|
||||
|
||||
if (originalTransactionId.isEmpty()) {
|
||||
qWarning().noquote() << "[IAP] Skipping restored transaction without originalTransactionId" << transactionId;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (processedTransactions.contains(originalTransactionId)) {
|
||||
result.duplicateCount++;
|
||||
continue;
|
||||
}
|
||||
processedTransactions.insert(originalTransactionId);
|
||||
|
||||
qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId
|
||||
<< "originalTransactionId =" << originalTransactionId << "productId =" << transactionProductId;
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
int currentDuplicateServerIndex = -1;
|
||||
ErrorCode errorCode = importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
|
||||
originalTransactionId, isTestPurchase,
|
||||
¤tDuplicateServerIndex);
|
||||
|
||||
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
|
||||
result.duplicateConfigAlreadyPresent = true;
|
||||
if (result.duplicateServerIndex < 0) {
|
||||
result.duplicateServerIndex = currentDuplicateServerIndex;
|
||||
}
|
||||
qInfo().noquote() << "[IAP] Skipping restored transaction" << originalTransactionId
|
||||
<< "because subscription config with the same vpn_key already exists";
|
||||
} else if (errorCode != ErrorCode::NoError) {
|
||||
qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId;
|
||||
result.errorCode = errorCode;
|
||||
} else {
|
||||
result.hasInstalledConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.hasInstalledConfig) {
|
||||
result.errorCode = result.duplicateConfigAlreadyPresent ? ErrorCode::ApiConfigAlreadyAdded : ErrorCode::ApiPurchaseError;
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
Q_UNUSED(userCountryCode);
|
||||
Q_UNUSED(serviceType);
|
||||
Q_UNUSED(serviceProtocol);
|
||||
result.errorCode = ErrorCode::ApiPurchaseError;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonObject &accountInfo)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
"",
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/account_info"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
accountInfo = QJsonDocument::fromJson(responseBody).object();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const QString &serverId)
|
||||
{
|
||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QString>>>::create();
|
||||
promise->start();
|
||||
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
"",
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
|
||||
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
|
||||
auto *watcher = new QFutureWatcher<QPair<ErrorCode, QByteArray>>();
|
||||
QObject::connect(watcher, &QFutureWatcher<QPair<ErrorCode, QByteArray>>::finished,
|
||||
[promise, watcher, gatewayController]() {
|
||||
const auto [errorCode, responseBody] = watcher->result();
|
||||
watcher->deleteLater();
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
promise->addResult(qMakePair(errorCode, QString()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object();
|
||||
const QString url = responseJson.value("renewal_url").toString();
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, url));
|
||||
promise->finish();
|
||||
});
|
||||
watcher->setFuture(postFuture);
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
#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"
|
||||
|
||||
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 importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
ErrorCode deactivateDevice(const QString &serverId);
|
||||
|
||||
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig);
|
||||
|
||||
ErrorCode revokeNativeConfig(const QString &serverId, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
|
||||
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
|
||||
|
||||
void removeApiConfig(const QString &serverId);
|
||||
|
||||
bool removeServer(const QString &serverId);
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
|
||||
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,
|
||||
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(const QString &serverId) 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
#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 "core/utils/serverConfigUtils.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.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(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
QString description;
|
||||
int configVersion = 0;
|
||||
bool isApiConfig = false;
|
||||
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
|
||||
isApiConfig = true;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::InternalError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (m_appSettingsRepository->useAmneziaDns()) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else {
|
||||
dns.first = m_appSettingsRepository->primaryDns();
|
||||
}
|
||||
}
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_appSettingsRepository->secondaryDns();
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::openConnection(const QString &serverId)
|
||||
{
|
||||
QJsonObject vpnConfiguration;
|
||||
DockerContainer container;
|
||||
|
||||
ErrorCode errorCode = prepareConnection(serverId, vpnConfiguration, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit localProxyStoppedBecauseVpnTurnedOn(tr("Local proxy stopped because VPN was turned on"));
|
||||
}
|
||||
#endif
|
||||
|
||||
emit openConnectionRequested(serverId, 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,
|
||||
bool isApiConfig,
|
||||
const QString &hostName,
|
||||
const QString &description,
|
||||
int configVersion,
|
||||
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 },
|
||||
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] = hostName;
|
||||
vpnConfiguration[configKey::description] = description;
|
||||
vpnConfiguration[configKey::configVersion] = 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);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#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(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
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,
|
||||
bool isApiConfig,
|
||||
const QString &hostName,
|
||||
const QString &description,
|
||||
int configVersion,
|
||||
const ContainerConfig &containerConfig,
|
||||
DockerContainer container);
|
||||
|
||||
bool isServiceReady() const;
|
||||
|
||||
bool isContainerSupported(DockerContainer container) const;
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
void localProxyStoppedBecauseVpnTurnedOn(const QString &message);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
#endif
|
||||
|
||||
private:
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -5,9 +5,17 @@
|
||||
#include <QDebug>
|
||||
#include <QTranslator>
|
||||
#include <QStandardPaths>
|
||||
#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 "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
|
||||
|
||||
@@ -16,35 +24,47 @@
|
||||
#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();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
initLocalProxy();
|
||||
#endif
|
||||
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(m_settings->getAppLanguage());
|
||||
m_translator = new QTranslator(this);
|
||||
if (m_appSettingsRepository) {
|
||||
updateTranslator(m_appSettingsRepository->getAppLanguage());
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::setQmlContextProperty(const QString &name, QObject *value)
|
||||
{
|
||||
if (m_engine) {
|
||||
m_engine->rootContext()->setContextProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void CoreController::initLocalProxy()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
constexpr quint16 kLocalProxyApiPort = 49490;
|
||||
|
||||
m_proxyServer.reset(new ProxyServer(m_settings, this));
|
||||
m_proxyServer.reset(new ProxyServer(m_serversRepository, m_appSettingsRepository, this));
|
||||
|
||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
|
||||
if (m_settings && m_settings->isLocalProxyHttpEnabled()) {
|
||||
m_settings->setLocalProxyHttpEnabled(false);
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -53,7 +73,7 @@ void CoreController::initLocalProxy()
|
||||
return;
|
||||
}
|
||||
|
||||
const bool httpEnabled = m_settings->isLocalProxyHttpEnabled();
|
||||
const bool httpEnabled = m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
|
||||
if (!httpEnabled) {
|
||||
qInfo() << "Local proxy: HTTP API disabled";
|
||||
@@ -63,15 +83,15 @@ void CoreController::initLocalProxy()
|
||||
|
||||
if (!m_proxyServer->start(kLocalProxyApiPort)) {
|
||||
qWarning() << "Local proxy: failed to start on port" << kLocalProxyApiPort;
|
||||
m_settings->setLocalProxyHttpEnabled(false);
|
||||
emit m_settings->localProxyStartFailed(tr("Local proxy failed to start. Check if the port is available."));
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Local proxy failed to start. Check if the port is available."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_proxyServer->syncSettings()) {
|
||||
qWarning() << "Local proxy: failed to start proxy core (Xray)";
|
||||
m_settings->setLocalProxyHttpEnabled(false);
|
||||
emit m_settings->localProxyStartFailed(tr("Couldn’t start the proxy due to an internal error. Try restarting the app."));
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Couldn’t start the proxy due to an internal error. Try restarting the app."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,147 +100,209 @@ void CoreController::initLocalProxy()
|
||||
|
||||
syncLocalProxy();
|
||||
|
||||
connect(m_settings.get(), &Settings::localProxySettingsChanged, this, syncLocalProxy);
|
||||
#endif
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this, syncLocalProxy);
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverEdited, this, [this](const QString &serverId) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()
|
||||
&& m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->bumpLocalProxyRestartToken();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverRemoved, this, [this](const QString &serverId, int) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(QString());
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
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_xrayConfigSnapshotsModel = new XrayConfigSnapshotsModel(m_appSettingsRepository, m_xrayConfigModel, this);
|
||||
setQmlContextProperty("XrayConfigSnapshotsModel", m_xrayConfigSnapshotsModel);
|
||||
|
||||
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_mtProxyConfigModel = new MtProxyConfigModel(this);
|
||||
setQmlContextProperty("MtProxyConfigModel", m_mtProxyConfigModel);
|
||||
|
||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||
m_telemtConfigModel = new TelemtConfigModel(this);
|
||||
setQmlContextProperty("TelemtConfigModel", m_telemtConfigModel);
|
||||
|
||||
m_apiSubscriptionPlansModel.reset(new ApiSubscriptionPlansModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel.get());
|
||||
m_clientManagementModel = new ClientManagementModel(this);
|
||||
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
|
||||
|
||||
m_apiBenefitsModel.reset(new ApiBenefitsModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiBenefitsModel", m_apiBenefitsModel.get());
|
||||
m_apiServicesModel = new ApiServicesModel(this);
|
||||
setQmlContextProperty("ApiServicesModel", m_apiServicesModel);
|
||||
|
||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||
m_apiCountryModel = new ApiCountryModel(this);
|
||||
setQmlContextProperty("ApiCountryModel", m_apiCountryModel);
|
||||
|
||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||
m_apiSubscriptionPlansModel = new ApiSubscriptionPlansModel(this);
|
||||
setQmlContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel);
|
||||
|
||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||
m_apiBenefitsModel = new ApiBenefitsModel(this);
|
||||
setQmlContextProperty("ApiBenefitsModel", m_apiBenefitsModel);
|
||||
|
||||
m_newsModel.reset(new NewsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
||||
m_apiAccountInfoModel = new ApiAccountInfoModel(this);
|
||||
setQmlContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel);
|
||||
|
||||
m_apiDevicesModel = new ApiDevicesModel(this);
|
||||
setQmlContextProperty("ApiDevicesModel", m_apiDevicesModel);
|
||||
|
||||
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_serversRepository);
|
||||
m_updateController = new UpdateController(m_appSettingsRepository, this);
|
||||
|
||||
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());
|
||||
connect(m_connectionController.get(), &ConnectionController::localProxyStoppedBecauseVpnTurnedOn, m_pageController.get(),
|
||||
if (m_engine) {
|
||||
m_focusController = new FocusController(m_engine, this);
|
||||
setQmlContextProperty("FocusController", m_focusController);
|
||||
}
|
||||
|
||||
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, m_mtProxyConfigModel, m_telemtConfigModel, this);
|
||||
setQmlContextProperty("InstallController", m_installUiController);
|
||||
|
||||
m_importController = new ImportUiController(m_importCoreController, this);
|
||||
setQmlContextProperty("ImportController", m_importController);
|
||||
|
||||
m_exportUiController = new ExportUiController(m_exportController, this);
|
||||
setQmlContextProperty("ExportController", m_exportUiController);
|
||||
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
setQmlContextProperty("PageController", m_pageController);
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_connectionController, &ConnectionController::localProxyStoppedBecauseVpnTurnedOn, m_pageController,
|
||||
&PageController::showNotificationMessage);
|
||||
#endif
|
||||
|
||||
m_focusController.reset(new FocusController(m_engine, this));
|
||||
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
|
||||
m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this);
|
||||
setQmlContextProperty("ServersUiController", m_serversUiController);
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
m_ipSplitTunnelingUiController = new IpSplitTunnelingUiController(m_ipSplitTunnelingController, m_ipSplitTunnelingModel, this);
|
||||
setQmlContextProperty("IpSplitTunnelingController", m_ipSplitTunnelingUiController);
|
||||
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||
m_allowedDnsUiController = new AllowedDnsUiController(m_allowedDnsController, m_allowedDnsModel, this);
|
||||
setQmlContextProperty("AllowedDnsController", m_allowedDnsUiController);
|
||||
|
||||
connect(m_installController.get(), &InstallController::profileCleared,
|
||||
m_protocolsModel.get(), &ProtocolsModel::updateModel);
|
||||
m_appSplitTunnelingUiController = new AppSplitTunnelingUiController(m_appSplitTunnelingController, m_appSplitTunnelingModel, this);
|
||||
setQmlContextProperty("AppSplitTunnelingController", m_appSplitTunnelingUiController);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
m_systemController = new SystemController(this);
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
m_networkReachabilityController = new NetworkReachabilityController(this);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
|
||||
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_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
|
||||
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
|
||||
|
||||
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
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_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||
|
||||
m_apiSettingsController.reset(
|
||||
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||
|
||||
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_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||
m_updateUiController = new UpdateUiController(m_updateController, this);
|
||||
setQmlContextProperty("UpdateController", m_updateUiController);
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
@@ -229,33 +311,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
|
||||
}
|
||||
|
||||
@@ -263,63 +328,39 @@ 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();
|
||||
m_signalHandlers = new CoreSignalHandlers(this, this);
|
||||
m_signalHandlers->initAllHandlers();
|
||||
|
||||
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
|
||||
// Trigger initial update after handlers are connected
|
||||
m_serversUiController->updateModel();
|
||||
if (m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
QCoreApplication::removeTranslator(m_translator);
|
||||
}
|
||||
|
||||
QStringList availableTranslations;
|
||||
@@ -340,130 +381,49 @@ 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;
|
||||
}
|
||||
|
||||
void CoreController::openConnectionByIndex(int serverIndex)
|
||||
{
|
||||
const QString serverId =
|
||||
m_serversUiController ? m_serversUiController->getServerId(serverIndex) : QString();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
m_serversModel->setDefaultServerIndex(serverIndex);
|
||||
}
|
||||
m_connectionController->toggleConnection();
|
||||
if (m_serversController) {
|
||||
m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
m_connectionUiController->toggleConnection();
|
||||
}
|
||||
|
||||
void CoreController::importConfigFromData(const QString &data)
|
||||
|
||||
@@ -6,28 +6,50 @@
|
||||
#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/updateUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
#include "ui/controllers/networkReachabilityController.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/controllers/updateController.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,119 +63,166 @@
|
||||
#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/protocols/xrayConfigSnapshotsModel.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/services/mtProxyConfigModel.h"
|
||||
#include "ui/models/services/telemtConfigModel.h"
|
||||
|
||||
#include "ui/models/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/proxyserver.h"
|
||||
#include "ui/notificationhandler.h"
|
||||
#include "core/local-proxy/proxyserver.h"
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
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 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();
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void initLocalProxy();
|
||||
|
||||
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();
|
||||
#endif
|
||||
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;
|
||||
NetworkReachabilityController* m_networkReachabilityController;
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
UpdateUiController* m_updateUiController;
|
||||
|
||||
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;
|
||||
UpdateController* m_updateController;
|
||||
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;
|
||||
XrayConfigSnapshotsModel* m_xrayConfigSnapshotsModel;
|
||||
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;
|
||||
MtProxyConfigModel* m_mtProxyConfigModel;
|
||||
TelemtConfigModel* m_telemtConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<ProxyServer> m_proxyServer;
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
#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/utils/serverConfigUtils.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/controllers/updateUiController.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();
|
||||
initAdminConfigRevokedHandler();
|
||||
initPassphraseRequestHandler();
|
||||
initTranslationsUpdatedHandler();
|
||||
initLanguageHandler();
|
||||
initAutoConnectHandler();
|
||||
initAmneziaDnsToggledHandler();
|
||||
initServersModelUpdateHandler();
|
||||
initClientManagementModelUpdateHandler();
|
||||
initSitesModelUpdateHandler();
|
||||
initAllowedDnsModelUpdateHandler();
|
||||
initAppSplitTunnelingModelUpdateHandler();
|
||||
initPrepareConfigHandler();
|
||||
initUnsupportedConnectDrawerHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
initAndroidSettingsHandler();
|
||||
initAndroidConnectionHandler();
|
||||
initIosImportHandler();
|
||||
initIosSettingsHandler();
|
||||
initNotificationHandler();
|
||||
initUpdateFoundHandler();
|
||||
}
|
||||
|
||||
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_serversUiController, &ServersUiController::processedServerIndexChanged,
|
||||
m_coreController->m_installUiController, [this](int serverIndex) {
|
||||
if (serverIndex >= 0) {
|
||||
m_coreController->m_installUiController->clearProcessedServerCredentials();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initExportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this,
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this,
|
||||
[this](const QString &serverId, DockerContainer container) {
|
||||
m_coreController->m_usersController->updateClients(serverId, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->renameClient(serverId, 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;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() {
|
||||
const QString processedServerId = m_coreController->m_serversUiController->getProcessedServerId();
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
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 QString defaultServerId = m_coreController->m_serversController->getDefaultServerId();
|
||||
if (!defaultServerId.isEmpty()) {
|
||||
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerId, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
|
||||
&InstallController::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->getDefaultServerId().isEmpty()) {
|
||||
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, this,
|
||||
[this](const QString &serverId) {
|
||||
if (m_coreController->m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, this, [this]() {
|
||||
m_coreController->m_serversUiController->updateModel();
|
||||
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const QString serverId = m_coreController->m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfigUtils::ConfigType kind = m_coreController->m_serversRepository->serverKind(serverId);
|
||||
|
||||
if (serverConfigUtils::isApiV2Subscription(kind) || serverConfigUtils::isLegacyApiSubscription(kind)) {
|
||||
m_coreController->m_subscriptionUiController->validateConfig();
|
||||
} else {
|
||||
m_coreController->m_installUiController->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_connectionUiController->openConnection();
|
||||
});
|
||||
|
||||
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::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
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, this,
|
||||
[](const QString &/*serverId*/, int removedIndex) {
|
||||
AndroidController::instance()->resetLastServer(removedIndex);
|
||||
});
|
||||
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
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
|
||||
&UpdateUiController::checkForUpdates);
|
||||
|
||||
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
|
||||
const QString version = m_coreController->m_updateUiController->getVersion();
|
||||
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);
|
||||
m_coreController->m_newsModel->setUpdateNotification(
|
||||
updateId, m_coreController->m_updateUiController->getHeaderText(), m_coreController->m_updateUiController->getChangelogText());
|
||||
emit m_coreController->m_pageController->showChangelogDrawer();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#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 initAdminConfigRevokedHandler();
|
||||
void initPassphraseRequestHandler();
|
||||
void initTranslationsUpdatedHandler();
|
||||
void initLanguageHandler();
|
||||
void initAutoConnectHandler();
|
||||
void initAmneziaDnsToggledHandler();
|
||||
void initServersModelUpdateHandler();
|
||||
void initClientManagementModelUpdateHandler();
|
||||
void initSitesModelUpdateHandler();
|
||||
void initAllowedDnsModelUpdateHandler();
|
||||
void initAppSplitTunnelingModelUpdateHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initUnsupportedConnectDrawerHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
void initAndroidSettingsHandler();
|
||||
void initAndroidConnectionHandler();
|
||||
void initIosImportHandler();
|
||||
void initIosSettingsHandler();
|
||||
void initNotificationHandler();
|
||||
void initUpdateFoundHandler();
|
||||
|
||||
CoreController* m_coreController;
|
||||
};
|
||||
|
||||
#endif // CORESIGNALHANDLERS_H
|
||||
|
||||
@@ -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.");
|
||||
@@ -49,6 +40,8 @@ namespace
|
||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
|
||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
@@ -97,9 +90,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;
|
||||
@@ -131,8 +124,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;
|
||||
@@ -246,7 +239,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
connect(reply, &QNetworkReply::finished, this, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
@@ -284,23 +277,34 @@ 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(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.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) {
|
||||
@@ -327,40 +331,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) {
|
||||
@@ -562,7 +574,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]);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
#include "exportController.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "core/configurators/configuratorBase.h"
|
||||
#include "core/utils/selfhosted/sshSession.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/selfhosted/selfHostedAdminServerConfig.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(const QString &serverId)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
for (auto it = adminConfig->containers.begin(); it != adminConfig->containers.end(); ++it) {
|
||||
it.value().protocolConfig.clearClientConfig();
|
||||
}
|
||||
|
||||
QJsonObject serverJson = adminConfig->toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(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(serverId, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
|
||||
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
adminConfig->containers.clear();
|
||||
adminConfig->containers[container] = containerConfig;
|
||||
adminConfig->defaultContainer = container;
|
||||
adminConfig->userName.clear();
|
||||
adminConfig->password.clear();
|
||||
adminConfig->port = 0;
|
||||
|
||||
adminConfig->dns1 = dns.first;
|
||||
adminConfig->dns2 = dns.second;
|
||||
|
||||
QJsonObject serverJson = adminConfig->toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::NativeConfigResult ExportController::generateNativeConfig(const QString &serverId, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const QString &clientName)
|
||||
{
|
||||
NativeConfigResult result;
|
||||
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Proto protocol = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const QPair<QString, QString> dns = adminConfig->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(serverId, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateOpenVpnConfig(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = DockerContainer::OpenVpn;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverId, 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(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::WireGuard);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverId, 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(const QString &serverId, 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;
|
||||
}
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverId, 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(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::Xray);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverId, 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("");
|
||||
} else if (vlessServer.security == "tls") {
|
||||
QJsonObject tlsSettings = streamSettings.value("tlsSettings").toObject();
|
||||
vlessServer.serverName = tlsSettings.value(amnezia::protocols::xray::serverName).toString();
|
||||
vlessServer.fingerprint = tlsSettings.value(amnezia::protocols::xray::fingerprint).toString();
|
||||
// alpn: serialize array back to comma-separated for VLESS URI
|
||||
QJsonArray alpnArr = tlsSettings.value("alpn").toArray();
|
||||
QStringList alpnList;
|
||||
for (const QJsonValue &v : alpnArr) {
|
||||
alpnList << v.toString();
|
||||
}
|
||||
// alpn goes into vless URI query param — handled by Serialize via serverName/alpn fields
|
||||
// VlessServerObject doesn't have alpn field, so we embed in serverName if needed
|
||||
}
|
||||
|
||||
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExportController::updateClientManagementModel(const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit updateClientsRequested(serverId, container);
|
||||
}
|
||||
|
||||
void ExportController::revokeConfig(int row, const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit revokeClientRequested(serverId, row, container);
|
||||
}
|
||||
|
||||
void ExportController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit renameClientRequested(serverId, 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)));
|
||||
}
|
||||
@@ -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(const QString &serverId);
|
||||
ExportResult generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName);
|
||||
ExportResult generateOpenVpnConfig(const QString &serverId, const QString &clientName);
|
||||
ExportResult generateWireGuardConfig(const QString &serverId, const QString &clientName);
|
||||
ExportResult generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName);
|
||||
ExportResult generateXrayConfig(const QString &serverId, const QString &clientName);
|
||||
|
||||
signals:
|
||||
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(const QString &serverId, DockerContainer container);
|
||||
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
|
||||
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
void updateClientManagementModel(const QString &serverId, int containerIndex);
|
||||
void revokeConfig(int row, const QString &serverId, int containerIndex);
|
||||
void renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex);
|
||||
|
||||
private:
|
||||
struct NativeConfigResult
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QJsonObject jsonNativeConfig;
|
||||
};
|
||||
|
||||
NativeConfigResult generateNativeConfig(const QString &serverId, 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
|
||||
@@ -0,0 +1,778 @@
|
||||
#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/serverConfigUtils.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"
|
||||
|
||||
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 (serverConfigUtils::isServerFromApi(result.config)) {
|
||||
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
|
||||
apiConfig[apiDefs::key::vpnKey] = data;
|
||||
result.config[apiDefs::key::apiConfig] = apiConfig;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(serverConfigUtils::configTypeFromJson(result.config))) {
|
||||
result.errorCode = ErrorCode::LegacyApiV1NotSupportedError;
|
||||
result.config = {};
|
||||
return result;
|
||||
}
|
||||
|
||||
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)) {
|
||||
m_serversRepository->addServer(QString(), config, serverConfigUtils::configTypeFromJson(config));
|
||||
emit importFinished();
|
||||
} else if (config.contains(configKey::configVersion)) {
|
||||
quint16 crc = qChecksum(QJsonDocument(config).toJson());
|
||||
bool hasServerWithCrc = false;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (static_cast<quint16>(apiV2->crc) == crc) {
|
||||
hasServerWithCrc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasServerWithCrc) {
|
||||
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
||||
} else {
|
||||
QJsonObject configWithCrc = config;
|
||||
configWithCrc.insert(configKey::crc, crc);
|
||||
m_serversRepository->addServer(QString(), configWithCrc, serverConfigUtils::configTypeFromJson(configWithCrc));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -0,0 +1,129 @@
|
||||
#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"
|
||||
#include "core/installers/mtProxyInstaller.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(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
ErrorCode removeContainer(const QString &serverId, DockerContainer container);
|
||||
|
||||
ErrorCode setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled);
|
||||
|
||||
/// statusOut: 0 = not deployed, 1 = running, 2 = stopped, 3 = error
|
||||
ErrorCode queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut);
|
||||
|
||||
ErrorCode queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out);
|
||||
|
||||
QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container);
|
||||
|
||||
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
|
||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
|
||||
|
||||
ErrorCode scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
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(const QString &serverId, 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(const QString &serverId, DockerContainer container);
|
||||
|
||||
ErrorCode validateAndPrepareConfig(const QString &serverId);
|
||||
|
||||
void validateConfig(const QString &serverId);
|
||||
|
||||
void addEmptyServer(const ServerCredentials &credentials);
|
||||
|
||||
signals:
|
||||
void configValidated(bool isValid);
|
||||
void validationErrorOccurred(ErrorCode errorCode);
|
||||
|
||||
void serverIsBusy(const bool isBusy);
|
||||
void cancelInstallationRequested();
|
||||
void clientRevocationRequested(const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container);
|
||||
void clientAppendRequested(const QString &serverId, 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,
|
||||
const QString &serverId, const QString &clientName);
|
||||
|
||||
void adminAppendRequested(const QString &serverId, 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
|
||||
@@ -0,0 +1,844 @@
|
||||
#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/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(const QString &serverId, const DockerContainer container)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
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(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
error = updateClients(serverId, container);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
int existingIndex = clientIndexById(clientId, m_clientsTable);
|
||||
if (existingIndex >= 0) {
|
||||
return renameClient(serverId, 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(const QString &serverId, const int row, const QString &clientName,
|
||||
const DockerContainer container, bool addTimeStamp)
|
||||
{
|
||||
if (row < 0 || row >= m_clientsTable.size()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
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,
|
||||
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 §ion : 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(const QString &serverId, const int index, const DockerContainer container)
|
||||
{
|
||||
if (index < 0 || index >= m_clientsTable.size()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
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, &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) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ContainerConfig containerCfg = adminConfig->containerConfig(container);
|
||||
QString containerClientId = containerCfg.protocolConfig.clientId();
|
||||
|
||||
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
}
|
||||
|
||||
emit clientRevoked(index);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode UsersController::revokeClient(const QString &serverId, const ContainerConfig &containerConfig, const DockerContainer container)
|
||||
{
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
errorCode = updateClients(serverId, 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, &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(serverId, container);
|
||||
emit clientRevoked(row);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#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(const QString &serverId, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
ErrorCode updateClients(const QString &serverId, const DockerContainer container);
|
||||
ErrorCode appendClient(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container);
|
||||
ErrorCode renameClient(const QString &serverId, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
|
||||
ErrorCode revokeClient(const QString &serverId, const int index, const DockerContainer container);
|
||||
ErrorCode revokeClient(const QString &serverId, 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,
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,418 @@
|
||||
#include "serversController.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
|
||||
#include "core/models/serverDescription.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)
|
||||
{
|
||||
ensureDefaultServerValid();
|
||||
}
|
||||
|
||||
void ServersController::ensureDefaultServerValid()
|
||||
{
|
||||
if (!getServersCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString defaultId = getDefaultServerId();
|
||||
if (!defaultId.isEmpty() && indexOfServerId(defaultId) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString firstId = getServerId(0);
|
||||
if (!firstId.isEmpty()) {
|
||||
setDefaultServer(firstId);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServersController::renameServer(const QString &serverId, const QString &name)
|
||||
{
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->name = name;
|
||||
cfg->displayName = name;
|
||||
cfg->nameOverriddenByUser = true;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersController::removeServer(const QString &serverId)
|
||||
{
|
||||
m_serversRepository->removeServer(serverId);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultServer(const QString &serverId)
|
||||
{
|
||||
m_serversRepository->setDefaultServer(serverId);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultContainer(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<ServerDescription> ServersController::buildServerDescriptions(bool isAmneziaDnsEnabled) const
|
||||
{
|
||||
QVector<ServerDescription> out;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
out.reserve(ids.size());
|
||||
|
||||
for (const QString &id : ids) {
|
||||
ServerDescription d;
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
const Kind kind = m_serversRepository->serverKind(id);
|
||||
switch (kind) {
|
||||
case Kind::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
d.serverId = id;
|
||||
out.append(d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> ServersController::getServerContainersMap(const QString &serverId) const
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
DockerContainer ServersController::getDefaultContainer(const QString &serverId) const
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return DockerContainer::None;
|
||||
}
|
||||
}
|
||||
|
||||
ContainerConfig ServersController::getContainerConfig(const QString &serverId, DockerContainer container) const
|
||||
{
|
||||
return getServerContainersMap(serverId).value(container);
|
||||
}
|
||||
|
||||
int ServersController::getDefaultServerIndex() const
|
||||
{
|
||||
return m_serversRepository->defaultServerIndex();
|
||||
}
|
||||
|
||||
QString ServersController::getDefaultServerId() const
|
||||
{
|
||||
return m_serversRepository->defaultServerId();
|
||||
}
|
||||
|
||||
int ServersController::getServersCount() const
|
||||
{
|
||||
return m_serversRepository->serversCount();
|
||||
}
|
||||
|
||||
QString ServersController::getServerId(int serverIndex) const
|
||||
{
|
||||
return m_serversRepository->serverIdAt(serverIndex);
|
||||
}
|
||||
|
||||
int ServersController::indexOfServerId(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->indexOfServerId(serverId);
|
||||
}
|
||||
|
||||
QString ServersController::notificationDisplayName(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin: {
|
||||
if (const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::SelfHostedUser: {
|
||||
if (const auto cfg = m_serversRepository->selfHostedUserConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::Native: {
|
||||
if (const auto cfg = m_serversRepository->nativeConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium: {
|
||||
if (const auto cfg = m_serversRepository->apiV2Config(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2: {
|
||||
if (const auto cfg = m_serversRepository->legacyApiConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const int idx = indexOfServerId(serverId);
|
||||
if (idx >= 0) {
|
||||
return QString::number(idx + 1);
|
||||
}
|
||||
return serverId;
|
||||
}
|
||||
|
||||
std::optional<ApiV2ServerConfig> ServersController::apiV2Config(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->apiV2Config(serverId);
|
||||
}
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> ServersController::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
}
|
||||
|
||||
ServerCredentials ServersController::getServerCredentials(const QString &serverId) const
|
||||
{
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (cfg.has_value()) {
|
||||
const ServerCredentials creds = cfg->credentials();
|
||||
if (creds.isValid()) {
|
||||
return creds;
|
||||
}
|
||||
}
|
||||
return ServerCredentials {};
|
||||
}
|
||||
|
||||
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol) const
|
||||
{
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (apiV2->apiConfig.userCountryCode == userCountryCode && apiV2->serviceType() == serviceType
|
||||
&& apiV2->serviceProtocol() == serviceProtocol) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServersController::hasInstalledContainers(const QString &serverId) const
|
||||
{
|
||||
const QMap<DockerContainer, ContainerConfig> containers = getServerContainersMap(serverId);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool ServersController::isLegacyApiV1Server(const QString &serverId) const
|
||||
{
|
||||
return !serverId.isEmpty()
|
||||
&& serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef SERVERSCONTROLLER_H
|
||||
#define SERVERSCONTROLLER_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
|
||||
#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/containerConfig.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
|
||||
class SshSession;
|
||||
class InstallController;
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class ServersController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ServersController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository = nullptr,
|
||||
QObject *parent = nullptr);
|
||||
~ServersController() = default;
|
||||
|
||||
// Server management
|
||||
bool renameServer(const QString &serverId, const QString &name);
|
||||
void removeServer(const QString &serverId);
|
||||
void setDefaultServer(const QString &serverId);
|
||||
|
||||
// Container management
|
||||
void setDefaultContainer(const QString &serverId, DockerContainer container);
|
||||
|
||||
// Getters
|
||||
QVector<ServerDescription> buildServerDescriptions(bool isAmneziaDnsEnabled) const;
|
||||
int getDefaultServerIndex() const;
|
||||
QString getDefaultServerId() const;
|
||||
int getServersCount() const;
|
||||
QString getServerId(int serverIndex) const;
|
||||
int indexOfServerId(const QString &serverId) const;
|
||||
QString notificationDisplayName(const QString &serverId) const;
|
||||
std::optional<ApiV2ServerConfig> apiV2Config(const QString &serverId) const;
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
ServerCredentials getServerCredentials(const QString &serverId) const;
|
||||
QMap<DockerContainer, ContainerConfig> getServerContainersMap(const QString &serverId) const;
|
||||
DockerContainer getDefaultContainer(const QString &serverId) const;
|
||||
ContainerConfig getContainerConfig(const QString &serverId, DockerContainer container) const;
|
||||
|
||||
// Validation
|
||||
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const;
|
||||
bool hasInstalledContainers(const QString &serverId) const;
|
||||
bool isLegacyApiV1Server(const QString &serverId) const;
|
||||
|
||||
private:
|
||||
void ensureDefaultServerValid();
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
};
|
||||
|
||||
#endif // SERVERSCONTROLLER_H
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
#include "settingsController.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QOperatingSystemVersion>
|
||||
|
||||
#include "version.h"
|
||||
#include "ui/utils/qAutoStart.h"
|
||||
#include "logger.h"
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/portavailabilityhelper.h"
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int kDefaultProxyPort = 10808;
|
||||
constexpr int kLocalProxyPortMin = 1024;
|
||||
constexpr int kLocalProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this,
|
||||
&SettingsController::localProxySettingsUpdated);
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxyStartFailed, this,
|
||||
&SettingsController::localProxyStartFailed);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
m_appSettingsRepository->clearSettings();
|
||||
|
||||
m_serversRepository->clearServers();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxySupported() const
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
}
|
||||
|
||||
int SettingsController::localProxyPort() const
|
||||
{
|
||||
return static_cast<int>(m_appSettingsRepository->localProxyPort());
|
||||
}
|
||||
|
||||
QString SettingsController::localProxyOwnerId() const
|
||||
{
|
||||
return m_appSettingsRepository->localProxyOwnerId();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
}
|
||||
|
||||
bool SettingsController::setLocalProxyPort(int port)
|
||||
{
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() == static_cast<quint16>(port)) {
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(port));
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortBusy(int port) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
return !PortAvailabilityHelper::isPortAvailable(port);
|
||||
#else
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::findFirstAvailableLocalProxyPort(int startPort) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const auto port = PortAvailabilityHelper::findFirstAvailablePort(startPort, kLocalProxyPortMax);
|
||||
return port ? *port : -1;
|
||||
#else
|
||||
Q_UNUSED(startPort);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::enableLocalProxy(const QString &ownerId, int port)
|
||||
{
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
Q_UNUSED(ownerId);
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#else
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax || ownerId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled() && m_appSettingsRepository->localProxyOwnerId() != ownerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int selectedPort = port;
|
||||
|
||||
const bool isUserDefinedPort = m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
if (isUserDefinedPort) {
|
||||
if (!PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
} else if (selectedPort != kDefaultProxyPort && !PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() != static_cast<quint16>(selectedPort)) {
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(selectedPort));
|
||||
}
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(isUserDefinedPort);
|
||||
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(ownerId);
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(true);
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::disableLocalProxy()
|
||||
{
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#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);
|
||||
|
||||
// Local proxy
|
||||
bool isLocalProxySupported() const;
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
int localProxyPort() const;
|
||||
QString localProxyOwnerId() const;
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
bool setLocalProxyPort(int port);
|
||||
bool isLocalProxyPortBusy(int port) const;
|
||||
int findFirstAvailableLocalProxyPort(int startPort) const;
|
||||
bool enableLocalProxy(const QString &ownerId, int port);
|
||||
void disableLocalProxy();
|
||||
|
||||
signals:
|
||||
void siteSplitTunnelingRouteModeChanged(RouteMode mode);
|
||||
void siteSplitTunnelingToggled(bool enabled);
|
||||
void appSplitTunnelingRouteModeChanged(AppsRouteMode mode);
|
||||
void appSplitTunnelingToggled(bool enabled);
|
||||
void appSplitTunnelingClearAppsList();
|
||||
|
||||
void localProxySettingsUpdated();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
QString getPlatform() const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
QString m_appVersion;
|
||||
QDateTime m_loggingDisableDate;
|
||||
bool m_isDevModeEnabled = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
#include "updateController.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QVersionNumber>
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
Logger logger("UpdateController");
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_windows_x64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos_x64.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateController::UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
||||
: QObject(parent), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QString UpdateController::getRawChangelogText() const
|
||||
{
|
||||
return m_changelogText;
|
||||
}
|
||||
|
||||
QString UpdateController::getReleaseDate() const
|
||||
{
|
||||
return m_releaseDate;
|
||||
}
|
||||
|
||||
QString UpdateController::getVersion() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
void UpdateController::checkForUpdates()
|
||||
{
|
||||
if (m_updateCheckRunning || !m_appSettingsRepository) {
|
||||
return;
|
||||
}
|
||||
m_updateCheckRunning = true;
|
||||
|
||||
fetchGatewayUrl();
|
||||
}
|
||||
|
||||
void UpdateController::finishUpdateCheck()
|
||||
{
|
||||
m_updateCheckRunning = false;
|
||||
}
|
||||
|
||||
void UpdateController::doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone)
|
||||
{
|
||||
QString fullUrl = m_baseUrl + endpoint;
|
||||
|
||||
QNetworkRequest req;
|
||||
req.setTransferTimeout(7000);
|
||||
req.setUrl(QUrl(fullUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(req);
|
||||
setupNetworkErrorHandling(reply, endpoint);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, endpoint, onDone]() {
|
||||
const bool ok = (reply->error() == QNetworkReply::NoError);
|
||||
QByteArray data;
|
||||
if (ok) {
|
||||
data = reply->readAll();
|
||||
} else {
|
||||
handleNetworkError(reply, endpoint);
|
||||
}
|
||||
reply->deleteLater();
|
||||
onDone(ok, data);
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchGatewayUrl()
|
||||
{
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::installationUuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
|
||||
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
|
||||
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
|
||||
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject gatewayData = QJsonDocument::fromJson(gatewayResponse).object();
|
||||
|
||||
QString baseUrl = gatewayData.value("url").toString();
|
||||
if (baseUrl.endsWith('/')) {
|
||||
baseUrl.chop(1);
|
||||
}
|
||||
m_baseUrl = baseUrl;
|
||||
|
||||
fetchVersionInfo();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchVersionInfo()
|
||||
{
|
||||
doGetAsync("/VERSION", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
m_version = QString::fromUtf8(data).trimmed();
|
||||
|
||||
if (!isNewVersionAvailable()) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
fetchChangelog();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchChangelog()
|
||||
{
|
||||
doGetAsync("/CHANGELOG", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
m_changelogText.clear();
|
||||
} else {
|
||||
m_changelogText = QString::fromUtf8(data);
|
||||
}
|
||||
fetchReleaseDate();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchReleaseDate()
|
||||
{
|
||||
doGetAsync("/RELEASE_DATE", [this](bool ok, QByteArray data) {
|
||||
if (ok) {
|
||||
m_releaseDate = QString::fromUtf8(data).trimmed();
|
||||
} else {
|
||||
m_releaseDate = QString();
|
||||
}
|
||||
|
||||
m_downloadUrl = composeDownloadUrl();
|
||||
emit updateFound();
|
||||
finishUpdateCheck();
|
||||
});
|
||||
}
|
||||
|
||||
bool UpdateController::isNewVersionAvailable() const
|
||||
{
|
||||
auto currentVersion = QVersionNumber::fromString(QString(APP_VERSION));
|
||||
auto newVersion = QVersionNumber::fromString(m_version);
|
||||
return newVersion > currentVersion;
|
||||
}
|
||||
|
||||
void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [reply, operation](QNetworkReply::NetworkError error) {
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
errorStrings << err.errorString();
|
||||
}
|
||||
logger.error() << QString("SSL errors while fetching %1: %2").arg(operation, errorStrings.join("; "));
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
return QString();
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(30000);
|
||||
request.setUrl(m_downloadUrl);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QFile file(kInstallerLocalPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to open installer file for writing:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.write(reply->readAll()) == -1) {
|
||||
logger.error() << "Failed to write installer data to file:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
file.close();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
{
|
||||
qint64 pid;
|
||||
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
QTemporaryDir extractDir;
|
||||
extractDir.setAutoRemove(false);
|
||||
if (!extractDir.isValid()) {
|
||||
logger.error() << "Failed to create temporary directory";
|
||||
return -1;
|
||||
}
|
||||
logger.info() << "Temporary directory created:" << extractDir.path();
|
||||
|
||||
// Create script file in the temporary directory
|
||||
QString scriptPath = extractDir.path() + "/mac_installer.sh";
|
||||
QFile scriptFile(scriptPath);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to create script file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get script content from registry
|
||||
QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::mac_installer);
|
||||
if (scriptContent.isEmpty()) {
|
||||
logger.error() << "macOS installer script content is empty";
|
||||
scriptFile.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
scriptFile.write(scriptContent.toUtf8());
|
||||
scriptFile.close();
|
||||
logger.info() << "Script file created:" << scriptPath;
|
||||
|
||||
// Make script executable
|
||||
QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser);
|
||||
|
||||
// Start detached process
|
||||
qint64 pid;
|
||||
bool success =
|
||||
QProcess::startDetached("/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int UpdateController::runLinuxInstaller(const QString &installerPath)
|
||||
{
|
||||
QFile::setPermissions(installerPath, QFile::permissions(installerPath) | QFile::ExeUser);
|
||||
|
||||
qint64 pid;
|
||||
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef UPDATECONTROLLER_H
|
||||
#define UPDATECONTROLLER_H
|
||||
|
||||
#include <functional>
|
||||
#include <QObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class UpdateController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent = nullptr);
|
||||
|
||||
QString getRawChangelogText() const;
|
||||
QString getReleaseDate() const;
|
||||
QString getVersion() const;
|
||||
|
||||
public slots:
|
||||
void checkForUpdates();
|
||||
void runInstaller();
|
||||
|
||||
signals:
|
||||
void updateFound();
|
||||
|
||||
private:
|
||||
void finishUpdateCheck();
|
||||
void fetchGatewayUrl();
|
||||
void fetchVersionInfo();
|
||||
void fetchChangelog();
|
||||
void fetchReleaseDate();
|
||||
void doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone);
|
||||
bool isNewVersionAvailable() const;
|
||||
void setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation);
|
||||
void handleNetworkError(QNetworkReply* reply, const QString& operation);
|
||||
QString composeDownloadUrl() const;
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
QString m_baseUrl;
|
||||
QString m_changelogText;
|
||||
QString m_version;
|
||||
QString m_releaseDate;
|
||||
QString m_downloadUrl;
|
||||
bool m_updateCheckRunning = false;
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int runWindowsInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
int runMacInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int runLinuxInstaller(const QString &installerPath);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UPDATECONTROLLER_H
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef CONTAINERDIAGNOSTICS_H
|
||||
#define CONTAINERDIAGNOSTICS_H
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
struct ContainerDiagnostics
|
||||
{
|
||||
bool available = false;
|
||||
bool portReachable = false;
|
||||
|
||||
virtual ~ContainerDiagnostics() = default;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // CONTAINERDIAGNOSTICS_H
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef MTPROXYDIAGNOSTICS_H
|
||||
#define MTPROXYDIAGNOSTICS_H
|
||||
|
||||
#include "containerDiagnostics.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace amnezia {
|
||||
struct MtProxyDiagnostics : ContainerDiagnostics {
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // MTPROXYDIAGNOSTICS_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef TELEMTDIAGNOSTICS_H
|
||||
#define TELEMTDIAGNOSTICS_H
|
||||
|
||||
#include "containerDiagnostics.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
struct TelemtDiagnostics : ContainerDiagnostics
|
||||
{
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // TELEMTDIAGNOSTICS_H
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#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/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.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::MtProxy: {
|
||||
MtProxyProtocolConfig mtConfig;
|
||||
mtConfig.port = portStr;
|
||||
config.protocolConfig = mtConfig;
|
||||
break;
|
||||
}
|
||||
case Proto::Telemt: {
|
||||
TelemtProtocolConfig telemtConfig;
|
||||
telemtConfig.port = portStr;
|
||||
config.protocolConfig = telemtConfig;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "mtProxyInstaller.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/models/containerConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxySecretPath("/data/secret");
|
||||
}
|
||||
|
||||
MtProxyInstaller::MtProxyInstaller(QObject *parent)
|
||||
: InstallerBase(parent) {
|
||||
}
|
||||
|
||||
ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession, ContainerConfig &config) {
|
||||
if (container != DockerContainer::MtProxy || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode jsonErr = ErrorCode::NoError;
|
||||
const QByteArray jsonRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyClientJsonPath), jsonErr);
|
||||
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
QJsonObject merged = mt->toJson();
|
||||
const QJsonObject snap = doc.object();
|
||||
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
|
||||
merged.insert(it.key(), it.value());
|
||||
}
|
||||
*mt = MtProxyProtocolConfig::fromJson(merged);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode secretErr = ErrorCode::NoError;
|
||||
const QByteArray secretRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
|
||||
const QString sec = QString::fromUtf8(secretRaw).trimmed();
|
||||
if (sec.length() == 32) {
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (hex32.match(sec).hasMatch()) {
|
||||
mt->secret = sec;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out)
|
||||
{
|
||||
out = {};
|
||||
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
const QString script =
|
||||
QStringLiteral(
|
||||
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
|
||||
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
|
||||
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
|
||||
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
|
||||
"echo \"PORT_OK=${PORT_OK}\"; "
|
||||
"echo \"TG_OK=${TG_OK}\"; "
|
||||
"echo \"CLIENTS=${CLIENTS:-0}\"; "
|
||||
"echo \"CONF_TIME=${CONF_TIME}\"; "
|
||||
"echo \"STATS=http://localhost:2398/stats\";")
|
||||
.arg(containerName)
|
||||
.arg(listenPort);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith(QLatin1String("PORT_OK="))) {
|
||||
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("TG_OK="))) {
|
||||
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
|
||||
out.clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
|
||||
out.lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith(QLatin1String("STATS="))) {
|
||||
out.statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, const ContainerConfig &config) {
|
||||
const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return;
|
||||
}
|
||||
const QByteArray payload = QJsonDocument(mt->toJson()).toJson(QJsonDocument::Compact);
|
||||
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
|
||||
QString(kMtProxyClientJsonUploadPath));
|
||||
if (err != ErrorCode::NoError) {
|
||||
qWarning() << "MtProxyInstaller::uploadClientSettingsSnapshot failed" << err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef MTPROXYINSTALLER_H
|
||||
#define MTPROXYINSTALLER_H
|
||||
|
||||
#include "installerBase.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
struct MtProxyContainerDiagnostics {
|
||||
bool portReachable = false;
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
class MtProxyInstaller : public InstallerBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MtProxyInstaller(QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode
|
||||
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
|
||||
SshSession *sshSession, amnezia::ContainerConfig &config) override;
|
||||
|
||||
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &config);
|
||||
|
||||
static amnezia::ErrorCode queryDiagnostics(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out);
|
||||
};
|
||||
|
||||
#endif // MTPROXYINSTALLER_H
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user