Compare commits

..

14 Commits

Author SHA1 Message Date
vladimir.kuznetsov
1f68c8ed80 refactoring: still total mess 2025-08-10 21:22:40 +08:00
vladimir.kuznetsov
153f96fc61 refactoring: build fixes 2025-08-09 20:45:06 +08:00
vladimir.kuznetsov
0dbab0e84b refactoring: separated api controllers 2025-08-08 21:58:52 +08:00
vladimir.kuznetsov
1b6442079a refactoring: keep moving 2025-08-08 14:16:01 +08:00
vladimir.kuznetsov
6b0c543fd2 refactoring: remove json usuage from cinfugurators 2025-08-04 16:10:31 +08:00
vladimir.kuznetsov
76413ec2df refactoring: cli management separation 2025-08-01 10:52:30 +08:00
vladimir.kuznetsov
9f0d4aaba1 refactoring: separated split tunneling and dns controllers and models 2025-07-31 23:17:46 +08:00
vladimir.kuznetsov
abb9ffe1b7 refactoring: separete settings controller 2025-07-31 13:00:01 +08:00
vladimir.kuznetsov
5012cd90e2 refactoring: attempting to move to MVVM (part 2) 2025-07-30 21:02:10 +08:00
vladimir.kuznetsov
39374075c5 refactoring: attempting to move to MVVM 2025-07-29 22:11:24 +08:00
vladimir.kuznetsov
c0fcb23b66 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2025-07-29 12:56:49 +08:00
vladimir.kuznetsov
d766a001e3 refactoring: all protocol models now work with c++ classes 2025-07-03 10:20:01 +08:00
vladimir.kuznetsov
65f60ab922 refactoring: replaced part of the code to work with new config classes 2025-06-26 09:57:29 +08:00
vladimir.kuznetsov
2d22a74b22 refactoring: added classes for working with server configs 2025-06-20 22:18:47 +08:00
460 changed files with 20558 additions and 23245 deletions

69
.cursorrules Normal file
View File

@@ -0,0 +1,69 @@
# Amnezia VPN Client - MVVM Refactoring Rules
## Architecture Requirements
### MVVM Pattern
- Follow Model-View-ViewModel architecture strictly
- Maintain clear separation of concerns between layers
- UI controllers should only handle user input and delegate to core controllers
- Core controllers contain all business logic and data manipulation
### Dependencies Direction
- UI layer depends on Core layer (unidirectional dependency)
- UI controllers must not depend on each other
- Core controllers must not depend on each other (exceptions: ServerController, VpnConfigurationsController, GatewayController can be created "on the fly")
### Settings Usage
- UI classes must NOT use Settings class directly
- Create wrapper methods in core controllers for Settings operations
### Server Controller Creation
- ServerController creation must happen in Core controllers, never in UI
- Remove direct instantiation: `new ServerController(m_settings)` from UI code
## Code Organization
### Folder Structure
- Core controllers: `client/core/controllers/`
- Core models: `client/core/models/`
- UI controllers: `client/ui/controllers/`
- UI models: `client/ui/models/`
- Common models and controllers in root of respective folders
### Controller Management
- All controllers must be instantiated in central `coreController`
- Dependencies between core controllers resolved in `coreController`
- UI controllers accept core controllers via constructor injection
- UI controllers should be renamed with "UI" suffix (e.g., `exportUIController`)
## Code Style
### Comments
- Do NOT include comments in code
- Code should be self-documenting
### Models Behavior
- Models should only display data
- Keep core models as pure data structures without business logic
## Refactoring Process
### UI Controller Refactoring
- Remove all business logic from UI controllers
- Replace direct method calls with delegation to core controllers
- Maintain only UI-specific logic (signals, model updates)
- Remove Settings dependencies
### Core Controller Pattern
- Create corresponding methods in core controllers for UI operations
- Core controllers handle all complex logic, network operations, file I/O
- Return structured results with error codes and data
- Manage ServerController lifecycle internally
## Git Workflow
- Use `git mv` for renaming controllers to maintain history
## Error Handling
- Core controllers return structured error results
- UI controllers only handle error presentation
- Maintain consistent error propagation patterns

View File

@@ -10,10 +10,10 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: android-runner
runs-on: ubuntu-22.04
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,15 +30,13 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'linux_gcc_64'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -46,31 +44,24 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
sudo apt-get install libxkbcommon-x11-0
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer.tar
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -93,7 +84,7 @@ jobs:
runs-on: windows-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -111,16 +102,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
shell: bash
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -128,62 +111,32 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2022_64'
arch: 'win64_msvc2019_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: 'x64'
- name: 'Setup .NET SDK'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 'Install WiX Toolset'
shell: powershell
run: |
dotnet tool install --global wix --version 4.0.6
wix extension add -g WixToolset.UI.wixext/4.0.6
wix extension add -g WixToolset.Util.wixext/4.0.6
wix extension list -g
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: 'Build project'
shell: cmd
run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
call deploy\\build_windows.bat
- name: 'Rename Windows installer'
shell: cmd
run: |
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7
- name: 'Upload MSI installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_MSI_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -196,10 +149,10 @@ jobs:
# ------------------------------------------------------
Build-iOS:
runs-on: macos-latest
runs-on: macos-13
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -214,7 +167,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '15.2'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -258,8 +211,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies'
run: pip install jsonschema jinja2
@@ -350,8 +303,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -378,7 +331,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.8.0
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -405,10 +358,10 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
xcode-version: '15.4.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
@@ -418,9 +371,8 @@ jobs:
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -428,32 +380,19 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Pack macOS installer'
run: |
cd deploy/build/pkg
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
cd ../../..
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
name: AmneziaVPN_MacOS_installer
path: deploy/build/pkg/AmneziaVPN.pkg
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -463,85 +402,14 @@ jobs:
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
dir: ${{ runner.temp }}
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-Android:
runs-on: android-runner
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.10.1
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -622,22 +490,15 @@ jobs:
with:
submodules: 'true'
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java'
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# cache: 'gradle'
cache: 'gradle'
- name: 'Setup Android NDK'
id: setup-ndk
@@ -662,44 +523,35 @@ jobs:
shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Rename Android APKs'
run: |
cd deploy/build
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
cd ../..
- name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload x86 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7

View File

@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else

4
.gitignore vendored
View File

@@ -9,7 +9,6 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -140,6 +139,3 @@ ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12

4
.gitmodules vendored
View File

@@ -14,7 +14,3 @@
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.13.0)
set(AMNEZIAVPN_VERSION 4.8.9.1)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2106)
set(APP_ANDROID_VERSION_CODE 2091)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -29,56 +29,17 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
endif()
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
# WiX patches
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
# WiX v4 Util extension for CloseApplication + namespace for util
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
include(CPack)
endif()

View File

@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)

Submodule client/3rd/qtgamepad deleted from f72b3e0c62

View File

@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -37,10 +38,6 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()
if(LINUX AND NOT ANDROID)
list(APPEND PACKAGES QuickTemplates2 QmlModels OpenGL)
endif()
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
set(LIBS ${LIBS}
@@ -57,27 +54,7 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
if(LINUX AND NOT ANDROID)
target_link_options(${PROJECT} PRIVATE "-Wl,--no-as-needed")
target_link_options(${PROJECT} PRIVATE "LINKER:--disable-new-dtags")
set_target_properties(${PROJECT} PROPERTIES
BUILD_RPATH "\$ORIGIN/../lib"
INSTALL_RPATH "\$ORIGIN/../lib"
INSTALL_RPATH_USE_LINK_PATH FALSE
)
set_property(TARGET ${PROJECT} PROPERTY BUILD_WITH_INSTALL_RPATH TRUE)
target_link_libraries(${PROJECT} PRIVATE
Qt6::QuickTemplates2
Qt6::QmlModels
Qt6::OpenGL
)
endif()
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
@@ -133,15 +110,6 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -171,7 +139,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -190,6 +158,7 @@ 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})
endif()
if(LINUX AND NOT ANDROID)
@@ -197,7 +166,8 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -208,25 +178,12 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
if(LINUX AND NOT ANDROID)
target_link_libraries(${PROJECT} PRIVATE
"-Wl,--push-state,--no-as-needed"
Qt6::QuickTemplates2
Qt6::QmlModels
Qt6::OpenGL
"-Wl,--pop-state"
)
endif()
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
# deploy artifacts required to run the application to the debug build folder
@@ -242,7 +199,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -257,16 +214,8 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
$<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).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
qt_finalize_target(${PROJECT})

View File

@@ -12,9 +12,6 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -24,18 +21,9 @@
#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;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
@@ -60,68 +48,30 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -129,16 +79,6 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url);
m_coreController->setQmlRoot();
@@ -154,7 +94,7 @@ void AmneziaApplication::init()
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
if (m_parser.isSet("a"))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
@@ -178,18 +118,6 @@ void AmneziaApplication::init()
}
});
#endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
}
void AmneziaApplication::registerTypes()
@@ -234,14 +162,15 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(m_optCleanup)) {
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
@@ -250,8 +179,9 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -268,32 +198,6 @@ void AmneziaApplication::startLocalServer() {
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;

View File

@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void startLocalServer();
#endif
@@ -45,11 +45,7 @@ public:
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
@@ -60,17 +56,10 @@ private:
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -45,8 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:exported="true">
<intent-filter>
@@ -215,4 +214,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder)
it.reconnect(0)

View File

@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun reconnectVpn(vpnBuilder: Builder)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) {

View File

@@ -6,9 +6,6 @@
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
</style>
<style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>

View File

@@ -26,8 +26,6 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.provider.OpenableColumns
import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -37,11 +35,6 @@ import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -177,9 +170,10 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
loadLibs()
// Configure window for edge-to-edge display
configureWindowForEdgeToEdge()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@@ -271,136 +265,6 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
if (deviceId < 0 && pressed) {
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true
}
}
}
// Real gamepad events (deviceId >= 0)
if (deviceId >= 0) {
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
if (isGamepad || isJoystick || isDpad) {
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
return true
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
super.onPause()
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
}, 100)
postDelayed({
sendTouch(2f, 2f)
}, 200)
postDelayed({
requestLayout()
invalidate()
}, 250)
}
} */
Log.d(TAG, "Resume Amnezia activity")
}
private fun configureWindowForEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
statusBarColor = android.graphics.Color.TRANSPARENT
navigationBarColor = android.graphics.Color.TRANSPARENT
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
// Workaround for Android 14 (API 34+) IME adjustResize bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setupImeInsetsListener()
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
}
}
private fun setupImeInsetsListener() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
}
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
@@ -802,43 +666,6 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
@Suppress("unused")
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
@Suppress("unused")
fun getStatusBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun getNavigationBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun startQrCodeReader() {
Log.v(TAG, "Start camera")

View File

@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING
connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
vpnProto?.protocol?.reconnectVpn(Builder())
}
}

View File

@@ -38,15 +38,15 @@ object AppListProvider {
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = (ai?.icon ?: 0) != 0
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai?.loadLabel(pm)?.toString()
this.name = name?.takeIf { it != packageName }
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {

View File

@@ -1,10 +1,7 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,25 +11,7 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}
@@ -52,7 +31,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
fileChooseResultLauncher.launch("*/*")
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })

View File

@@ -28,7 +28,4 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
}

View File

@@ -10,8 +10,6 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -137,8 +135,8 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}

View File

@@ -12,7 +12,6 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log
@@ -28,7 +27,6 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope
private var statusJob: Job? = null
@@ -63,7 +61,6 @@ open class Wireguard : Protocol() {
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
}
protected open fun parseConfig(config: JSONObject): WireguardConfig {
@@ -125,24 +122,23 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
configData.optStringOrNull("I1")?.let { setI1(it) }
configData.optStringOrNull("I2")?.let { setI2(it) }
configData.optStringOrNull("I3")?.let { setI3(it) }
configData.optStringOrNull("I4")?.let { setI4(it) }
configData.optStringOrNull("I5")?.let { setI5(it) }
configData.optStringOrNull("J1")?.let { setJ1(it) }
configData.optStringOrNull("J2")?.let { setJ2(it) }
configData.optStringOrNull("J3")?.let { setJ3(it) }
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
}
private fun start(
config: WireguardConfig,
vpnBuilder: Builder,
protect: (Int) -> Boolean,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
return
}
@@ -150,9 +146,6 @@ open class Wireguard : Protocol() {
buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd ->
if (stopExistingVpn && tunnelHandle != -1) {
turnOffVpn()
}
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
@@ -209,25 +202,20 @@ open class Wireguard : Protocol() {
return lastHandshake
}
private fun turnOffVpn() {
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
}

View File

@@ -22,15 +22,19 @@ open class WireguardConfig protected constructor(
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?,
var i1: String?,
var i2: String?,
var i3: String?,
var i4: String?,
var i5: String?,
var j1: String?,
var j2: String?,
var j3: String?,
var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -57,6 +61,10 @@ open class WireguardConfig protected constructor(
builder.i3,
builder.i4,
builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -86,6 +94,10 @@ open class WireguardConfig protected constructor(
i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") }
j1?.let { appendLine("j1=$it") }
j2?.let { appendLine("j2=$it") }
j3?.let { appendLine("j3=$it") }
itime?.let { appendLine("itime=$it") }
}
}
@@ -140,15 +152,19 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null
internal var s3: Int? = null
internal var s4: Int? = null
internal var h1: String? = null
internal var h2: String? = null
internal var h3: String? = null
internal var h4: String? = null
internal var h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = null
internal var i1: String? = null
internal var i2: String? = null
internal var i3: String? = null
internal var i4: String? = null
internal var i5: String? = null
internal var j1: String? = null
internal var j2: String? = null
internal var j3: String? = null
internal var itime: Int? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -169,15 +185,19 @@ open class WireguardConfig protected constructor(
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: String) = apply { this.h1 = h1 }
fun setH2(h2: String) = apply { this.h2 = h2 }
fun setH3(h3: String) = apply { this.h3 = h3 }
fun setH4(h4: String) = apply { this.h4 = h4 }
fun setH1(h1: Long) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
fun setI1(i1: String) = apply { this.i1 = i1 }
fun setI2(i2: String) = apply { this.i2 = i2 }
fun setI3(i3: String) = apply { this.i3 = i3 }
fun setI4(i4: String) = apply { this.i4 = i4 }
fun setI5(i5: String) = apply { this.i5 = i5 }
fun setJ1(j1: String) = apply { this.j1 = j1 }
fun setJ2(j2: String) = apply { this.j2 = j2 }
fun setJ3(j3: String) = apply { this.j3 = j3 }
fun setItime(itime: Int) = apply { this.itime = itime }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}

View File

@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd"
logLevel = "warn"
logLevel = "warning"
}
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err")

View File

@@ -27,18 +27,12 @@ if(WIN32)
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(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")
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")
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")
@@ -62,7 +56,7 @@ elseif(LINUX)
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})
@@ -83,26 +77,6 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
set(LIBS ${LIBS} qt6keychain)
include_directories(

View File

@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 28)
set(APP_ANDROID_MIN_SDK 26)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_TARGET_SDK_VERSION 34
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
@@ -20,11 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
if(TARGET Qt6::CorePrivate)
set(LIBS ${LIBS} Qt6::CorePrivate)
endif()
set(LIBS ${LIBS} -ljnigraphics)
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)

View File

@@ -34,7 +34,6 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
@@ -47,8 +46,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
)

View File

@@ -14,7 +14,7 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USER_NOTIFICATIONS}
${FW_USERNOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
@@ -35,8 +35,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -55,3 +53,4 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")

View File

@@ -1,170 +0,0 @@
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})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
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"
"$<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"
)

View File

@@ -4,7 +4,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/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/models/containers/containers_defs.h
${CLIENT_ROOT_DIR}/core/defs.h
${CLIENT_ROOT_DIR}/core/errorstrings.h
${CLIENT_ROOT_DIR}/core/scripts_registry.h
@@ -12,8 +12,18 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/api/apiDefs.h
${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
${CLIENT_ROOT_DIR}/core/controllers/serverController.h
${CLIENT_ROOT_DIR}/core/controllers/api/gatewayController.h
${CLIENT_ROOT_DIR}/core/controllers/api/apiConfigController.h
${CLIENT_ROOT_DIR}/core/controllers/api/apiSettingsController.h
${CLIENT_ROOT_DIR}/core/controllers/api/apiPremV1MigrationController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/serverController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/selfhostedConfigController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/clientManagementController.h
${CLIENT_ROOT_DIR}/core/controllers/configController.h
${CLIENT_ROOT_DIR}/core/controllers/dnsController.h
${CLIENT_ROOT_DIR}/core/controllers/splitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/settingsController.h
${CLIENT_ROOT_DIR}/core/controllers/connectionController.h
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
@@ -28,7 +38,7 @@ set(HEADERS ${HEADERS}
${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}/core/utils/fileUtils.h
)
# Mozilla headres
@@ -37,9 +47,10 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS AND NOT MACOS_NE)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -54,14 +65,24 @@ 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/models/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/controllers/coreController.cpp
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/gatewayController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/apiConfigController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/apiSettingsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/apiPremV1MigrationController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/serverController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/selfhostedConfigController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/clientManagementController.cpp
${CLIENT_ROOT_DIR}/core/controllers/configController.cpp
${CLIENT_ROOT_DIR}/core/controllers/dnsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/splitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/settingsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/connectionController.cpp
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
${CLIENT_ROOT_DIR}/ui/qautostart.cpp
@@ -79,7 +100,7 @@ set(SOURCES ${SOURCES}
${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}/core/utils/fileUtils.cpp
)
# Mozilla sources
@@ -87,28 +108,15 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS AND NOT MACOS_NE)
if(NOT IOS)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
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
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
@@ -129,21 +137,41 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
${CLIENT_ROOT_DIR}/ui/models/services/*.h
${CLIENT_ROOT_DIR}/ui/models/api/*.h
${CLIENT_ROOT_DIR}/ui/models/selfhosted/*.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/api/*.cpp
${CLIENT_ROOT_DIR}/ui/models/selfhosted/*.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/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/selfhosted/*.cpp
)
file(GLOB CORE_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/core/models/*.h
${CLIENT_ROOT_DIR}/core/models/containers/*.h
${CLIENT_ROOT_DIR}/core/models/protocols/*.h
${CLIENT_ROOT_DIR}/core/models/servers/*.h
)
file(GLOB CORE_MODELS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/core/models/*.cpp
${CLIENT_ROOT_DIR}/core/models/containers/*.cpp
${CLIENT_ROOT_DIR}/core/models/protocols/*.cpp
${CLIENT_ROOT_DIR}/core/models/servers/*.cpp
)
set(HEADERS ${HEADERS}
@@ -152,6 +180,7 @@ set(HEADERS ${HEADERS}
${CONFIGURATORS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
${CORE_MODELS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
@@ -159,6 +188,7 @@ set(SOURCES ${SOURCES}
${CONFIGURATORS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
${CORE_MODELS_CPP}
)
if(WIN32)
@@ -175,7 +205,7 @@ if(WIN32)
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -189,13 +219,11 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/privileged_process.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
@@ -205,14 +233,3 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/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
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
)
endif()

View File

@@ -1,24 +1,29 @@
#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)
QSharedPointer<ProtocolConfig> AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, 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();
auto result = WireguardConfigurator::createConfig(credentials, container, protocolConfig, errorCode);
if (!result) {
return nullptr;
}
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(result);
if (!awgConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
QString config = awgConfig->clientProtocolConfig.nativeConfig;
QMap<QString, QString> configMap;
auto configLines = awgConfig.split("\n");
auto configLines = config.split("\n");
for (auto &line : configLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
@@ -31,29 +36,17 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
}
}
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);
awgConfig->clientProtocolConfig.awgData.junkPacketCount = configMap.value(config_key::junkPacketCount);
awgConfig->clientProtocolConfig.awgData.junkPacketMinSize = configMap.value(config_key::junkPacketMinSize);
awgConfig->clientProtocolConfig.awgData.junkPacketMaxSize = configMap.value(config_key::junkPacketMaxSize);
awgConfig->clientProtocolConfig.awgData.initPacketJunkSize = configMap.value(config_key::initPacketJunkSize);
awgConfig->clientProtocolConfig.awgData.responsePacketJunkSize = configMap.value(config_key::responsePacketJunkSize);
awgConfig->clientProtocolConfig.awgData.initPacketMagicHeader = configMap.value(config_key::initPacketMagicHeader);
awgConfig->clientProtocolConfig.awgData.responsePacketMagicHeader = configMap.value(config_key::responsePacketMagicHeader);
awgConfig->clientProtocolConfig.awgData.underloadPacketMagicHeader = configMap.value(config_key::underloadPacketMagicHeader);
awgConfig->clientProtocolConfig.awgData.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);
}
awgConfig->clientProtocolConfig.wireGuardData.mtu = awgConfig->serverProtocolConfig.mtu;
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();
return awgConfig;
}

View File

@@ -11,8 +11,8 @@ class AwgConfigurator : public WireguardConfigurator
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);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
};
#endif // AWGCONFIGURATOR_H

View File

@@ -4,23 +4,47 @@
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/models/protocols/cloakProtocolConfig.h"
#include "protocols/protocols_defs.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)
ConfiguratorBase::Vars CloakConfigurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
auto cloakConfig = qSharedPointerCast<CloakProtocolConfig>(protocolConfig);
if (!cloakConfig) {
return vars;
}
vars.append({{"$CLOAK_SERVER_PORT", cloakConfig->serverProtocolConfig.port}});
vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig->serverProtocolConfig.site}});
return vars;
}
QSharedPointer<ProtocolConfig> CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
auto cloakConfig = qSharedPointerCast<CloakProtocolConfig>(protocolConfig);
if (!cloakConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
QString cloakPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
cloakPublicKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
QString cloakBypassUid =
@@ -28,24 +52,38 @@ QString CloakConfigurator::createConfig(const ServerCredentials &credentials, Do
cloakBypassUid.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
cloakConfig->clientProtocolConfig.transport = "direct";
cloakConfig->clientProtocolConfig.proxyMethod = "openvpn";
cloakConfig->clientProtocolConfig.encryptionMethod = "aes-gcm";
cloakConfig->clientProtocolConfig.uid = cloakBypassUid;
cloakConfig->clientProtocolConfig.publicKey = cloakPublicKey;
cloakConfig->clientProtocolConfig.serverName = cloakConfig->serverProtocolConfig.site;
cloakConfig->clientProtocolConfig.numConn = 1;
cloakConfig->clientProtocolConfig.browserSig = "chrome";
cloakConfig->clientProtocolConfig.streamTimeout = 300;
cloakConfig->clientProtocolConfig.remoteHost = credentials.hostName;
cloakConfig->clientProtocolConfig.remotePort = cloakConfig->serverProtocolConfig.port;
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");
config.insert("Transport", cloakConfig->clientProtocolConfig.transport);
config.insert("ProxyMethod", cloakConfig->clientProtocolConfig.proxyMethod);
config.insert("EncryptionMethod", cloakConfig->clientProtocolConfig.encryptionMethod);
config.insert("UID", cloakConfig->clientProtocolConfig.uid);
config.insert("PublicKey", cloakConfig->clientProtocolConfig.publicKey);
config.insert("ServerName", cloakConfig->clientProtocolConfig.serverName);
config.insert("NumConn", cloakConfig->clientProtocolConfig.numConn);
config.insert("BrowserSig", cloakConfig->clientProtocolConfig.browserSig);
config.insert("StreamTimeout", cloakConfig->clientProtocolConfig.streamTimeout);
config.insert("RemoteHost", cloakConfig->clientProtocolConfig.remoteHost);
config.insert("RemotePort", cloakConfig->clientProtocolConfig.remotePort);
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
QString textCfg = QJsonDocument(config).toJson();
return textCfg;
cloakConfig->clientProtocolConfig.isEmpty = false;
cloakConfig->clientProtocolConfig.nativeConfig = textCfg;
return cloakConfig;
}

View File

@@ -4,6 +4,7 @@
#include <QObject>
#include "configurator_base.h"
#include "core/models/protocols/cloakProtocolConfig.h"
using namespace amnezia;
@@ -13,8 +14,11 @@ class CloakConfigurator : public ConfiguratorBase
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);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
};
#endif // CLOAK_CONFIGURATOR_H

View File

@@ -1,26 +1,58 @@
#include "configurator_base.h"
#include "core/networkUtilities.h"
#include "core/models/protocols/protocolConfig.h"
#include <variant>
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)
void ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
processConfigWithDnsSettings(dns, protocolConfig);
}
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
void ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
processConfigWithDnsSettings(dns, protocolConfig);
}
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QSharedPointer<ProtocolConfig> &protocolConfig)
{
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([&dns](const auto &config) -> void {
config->clientProtocolConfig.nativeConfig.replace("$PRIMARY_DNS", dns.first);
config->clientProtocolConfig.nativeConfig.replace("$SECONDARY_DNS", dns.second);
}, variant);
}
ConfiguratorBase::Vars ConfiguratorBase::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
return generateCommonVars(credentials, container);
}
ConfiguratorBase::Vars ConfiguratorBase::generateCommonVars(const ServerCredentials &credentials, DockerContainer container) const
{
Vars vars;
vars.append({{"$REMOTE_HOST", credentials.hostName}});
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {
vars.append({{"$SERVER_IP_ADDRESS", serverIp}});
}
vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}});
vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}});
vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}});
vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}});
return vars;
}

View File

@@ -2,28 +2,39 @@
#define CONFIGURATORBASE_H
#include <QObject>
#include <QList>
#include <QPair>
#include <QSharedPointer>
#include "containers/containers_defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/defs.h"
#include "core/controllers/serverController.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/models/protocols/protocolConfig.h"
#include "settings.h"
class ConfiguratorBase : public QObject
{
Q_OBJECT
public:
using Vars = QList<QPair<QString, QString>>;
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 QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, 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);
virtual void processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig);
virtual void processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig);
virtual Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const;
protected:
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QSharedPointer<ProtocolConfig> &protocolConfig);
Vars generateCommonVars(const ServerCredentials &credentials, DockerContainer container) const;
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServerController> m_serverController;

View File

@@ -8,10 +8,11 @@
#include <QTemporaryFile>
#include <QUuid>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "protocols/protocols_defs.h"
#include "utilities.h"
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
@@ -19,6 +20,28 @@ Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const Q
{
}
ConfiguratorBase::Vars Ikev2Configurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
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"}});
return vars;
}
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
ErrorCode &errorCode)
{
@@ -54,21 +77,58 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData;
}
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
QSharedPointer<ProtocolConfig> Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
Q_UNUSED(containerConfig)
Q_UNUSED(protocolConfig)
// IKEv2 uses a generic ProtocolConfig - no specific subclass needed
if (!protocolConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
return genIkev2Config(connData);
auto ikev2Config = qSharedPointerCast<Ikev2ProtocolConfig>(protocolConfig);
if (!ikev2Config) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
ikev2Config->clientProtocolConfig.isEmpty = false;
ikev2Config->clientProtocolConfig.hostName = connData.host;
ikev2Config->clientProtocolConfig.userName = connData.clientId;
ikev2Config->clientProtocolConfig.cert = QString(connData.clientCert.toBase64());
ikev2Config->clientProtocolConfig.password = connData.password;
// Generate the appropriate native config based on platform
QString nativeConfigStr;
switch (Utils::systemType()) {
case SystemType::iOS:
[[fallthrough]];
case SystemType::macOS:
nativeConfigStr = genMobileConfig(connData);
break;
case SystemType::Android:
nativeConfigStr = genIkev2Config(connData);
break;
default:
nativeConfigStr = genStrongSwanConfig(connData);
break;
}
ikev2Config->clientProtocolConfig.nativeConfig = nativeConfigStr;
return ikev2Config;
}
QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
{
// Create temporary JSON for Android platform (will be eliminated when android protocols are updated)
QJsonObject config;
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;

View File

@@ -5,6 +5,8 @@
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/models/protocols/protocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/defs.h"
class Ikev2Configurator : public ConfiguratorBase
@@ -21,13 +23,16 @@ public:
QString host; // host ip
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode);
};

View File

@@ -1,8 +1,7 @@
#include "openvpn_configurator.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
@@ -14,9 +13,13 @@
#endif
#include "core/networkUtilities.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/protocols/protocolConfig.h"
#include <variant>
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/scripts_registry.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "protocols/protocols_defs.h"
#include "settings.h"
#include "utilities.h"
@@ -31,6 +34,39 @@ OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, con
{
}
ConfiguratorBase::Vars OpenVpnConfigurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
auto openVpnConfig = qSharedPointerCast<OpenVpnProtocolConfig>(protocolConfig);
if (!openVpnConfig) {
return vars;
}
vars.append({{"$OPENVPN_SUBNET_IP", openVpnConfig->serverProtocolConfig.subnetAddress}});
vars.append({{"$OPENVPN_SUBNET_CIDR", protocols::openvpn::defaultSubnetCidr}});
vars.append({{"$OPENVPN_SUBNET_MASK", protocols::openvpn::defaultSubnetMask}});
vars.append({{"$OPENVPN_PORT", openVpnConfig->serverProtocolConfig.port}});
vars.append({{"$OPENVPN_TRANSPORT_PROTO", openVpnConfig->serverProtocolConfig.transportProto}});
vars.append({{"$OPENVPN_NCP_DISABLE", openVpnConfig->serverProtocolConfig.ncpDisable ? protocols::openvpn::ncpDisableString : ""}});
vars.append({{"$OPENVPN_CIPHER", openVpnConfig->serverProtocolConfig.cipher}});
vars.append({{"$OPENVPN_HASH", openVpnConfig->serverProtocolConfig.hash}});
vars.append({{"$OPENVPN_TLS_AUTH", openVpnConfig->serverProtocolConfig.tlsAuth ? protocols::openvpn::tlsAuthString : ""}});
if (!openVpnConfig->serverProtocolConfig.tlsAuth) {
vars.append({{"$OPENVPN_TA_KEY", ""}});
}
vars.append({{"$OPENVPN_ADDITIONAL_CLIENT_CONFIG", openVpnConfig->serverProtocolConfig.additionalClientConfig}});
vars.append({{"$OPENVPN_ADDITIONAL_SERVER_CONFIG", openVpnConfig->serverProtocolConfig.additionalServerConfig}});
return vars;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode)
{
@@ -72,41 +108,29 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
QSharedPointer<ProtocolConfig> OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
auto openVpnConfig = qSharedPointerCast<OpenVpnProtocolConfig>(protocolConfig);
if (!openVpnConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
generateProtocolVars(credentials, container, protocolConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
auto sanitizeStaticKey = [](const QString &key) {
QStringList lines = key.split('\n');
QStringList filtered;
filtered.reserve(lines.size());
for (const QString &line : lines) {
const QString trimmed = line.trimmed();
if (trimmed.startsWith('#')) {
continue;
}
filtered.append(line);
}
QString result = filtered.join('\n');
if (!result.endsWith('\n')) {
result.append('\n');
}
return result;
};
config.replace("$OPENVPN_CA_CERT", connData.caCert);
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
config.replace("$OPENVPN_TA_KEY", connData.taKey);
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
@@ -116,91 +140,100 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
config.replace("block-outside-dns", "");
#endif
QJsonObject jConfig;
jConfig[config_key::config] = config;
openVpnConfig->clientProtocolConfig.isEmpty = false;
openVpnConfig->clientProtocolConfig.clientId = connData.clientId;
openVpnConfig->clientProtocolConfig.nativeConfig = config;
jConfig[config_key::clientId] = connData.clientId;
return QJsonDocument(jConfig).toJson();
return openVpnConfig;
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
void OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
processConfigWithDnsSettings(dns, protocolConfig);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([this, &dns, isApiConfig](const auto &config) -> void {
if constexpr (std::is_same_v<std::decay_t<decltype(config)>, QSharedPointer<OpenVpnProtocolConfig>>) {
QString &nativeConfig = config->clientProtocolConfig.nativeConfig;
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
nativeConfig.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);
nativeConfig.replace(dnsRegex, "");
}
// 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);
config.replace(dnsRegex, "");
}
if (!m_settings->isSitesSplitTunnelingEnabled()) {
nativeConfig.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
nativeConfig.append("block-ipv6\n");
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
if (!m_settings->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) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
nativeConfig.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif
config.append("block-ipv6\n");
}
}
nativeConfig.append("block-ipv6\n");
}
QStringList routeList = m_settings->vpnRoutes();
if (!routeList.isEmpty()) {
for (auto route : routeList) {
nativeConfig.append("\nroute " + route + " 255.255.255.255 vpn_gateway\n");
}
}
}
#ifndef MZ_WINDOWS
config.replace("block-outside-dns", "");
nativeConfig.replace("block-outside-dns", "");
#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());
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);
nativeConfig.append(dnsConf);
#endif
json[config_key::config] = config;
return QJsonDocument(json).toJson();
}
}, variant);
}
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
void OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
processConfigWithDnsSettings(dns, protocolConfig);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([&dns](const auto &config) -> void {
if constexpr (std::is_same_v<std::decay_t<decltype(config)>, QSharedPointer<OpenVpnProtocolConfig>>) {
QString &nativeConfig = config->clientProtocolConfig.nativeConfig;
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
QRegularExpression regex("redirect-gateway.*");
nativeConfig.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);
config.replace(dnsRegex, "");
}
// 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);
nativeConfig.replace(dnsRegex, "");
}
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
nativeConfig.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("block-ipv6\n");
// Prevent ipv6 leak
nativeConfig.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();
// remove block-outside-dns for all exported configs
nativeConfig.replace("block-outside-dns", "");
}
}, variant);
}
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
@@ -217,7 +250,7 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerC
.arg(clientId);
QStringList scriptList { script_import, script_sign };
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
QString script = m_serverController->replaceVars(scriptList.join("\n"), generateProtocolVars(credentials, container));
return m_serverController->runScript(credentials, script);
}

View File

@@ -6,6 +6,7 @@
#include "configurator_base.h"
#include "core/defs.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
class OpenVpnConfigurator : public ConfiguratorBase
{
@@ -24,13 +25,16 @@ public:
QString host; // host ip
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
void processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig) override;
void processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig) override;
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
static ConnectionData createCertRequest();

View File

@@ -4,8 +4,10 @@
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "protocols/protocols_defs.h"
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
@@ -13,28 +15,59 @@ ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> setti
{
}
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
ConfiguratorBase::Vars ShadowSocksConfigurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
auto shadowsocksConfig = qSharedPointerCast<ShadowsocksProtocolConfig>(protocolConfig);
if (!shadowsocksConfig) {
return vars;
}
vars.append({{"$SHADOWSOCKS_SERVER_PORT", shadowsocksConfig->serverProtocolConfig.port}});
vars.append({{"$SHADOWSOCKS_LOCAL_PORT", protocols::shadowsocks::defaultLocalProxyPort}});
vars.append({{"$SHADOWSOCKS_CIPHER", shadowsocksConfig->serverProtocolConfig.cipher}});
return vars;
}
QSharedPointer<ProtocolConfig> ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
auto shadowsocksConfig = qSharedPointerCast<ShadowsocksProtocolConfig>(protocolConfig);
if (!shadowsocksConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
QString ssKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
ssKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
shadowsocksConfig->clientProtocolConfig.server = credentials.hostName;
shadowsocksConfig->clientProtocolConfig.serverPort = shadowsocksConfig->serverProtocolConfig.port;
shadowsocksConfig->clientProtocolConfig.localPort = protocols::shadowsocks::defaultLocalProxyPort;
shadowsocksConfig->clientProtocolConfig.password = ssKey;
shadowsocksConfig->clientProtocolConfig.timeout = 60;
shadowsocksConfig->clientProtocolConfig.method = shadowsocksConfig->serverProtocolConfig.cipher;
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");
config.insert("server", shadowsocksConfig->clientProtocolConfig.server);
config.insert("server_port", shadowsocksConfig->clientProtocolConfig.serverPort);
config.insert("local_port", shadowsocksConfig->clientProtocolConfig.localPort);
config.insert("password", shadowsocksConfig->clientProtocolConfig.password);
config.insert("timeout", shadowsocksConfig->clientProtocolConfig.timeout);
config.insert("method", shadowsocksConfig->clientProtocolConfig.method);
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
QString textCfg = QJsonDocument(config).toJson();
// qDebug().noquote() << textCfg;
return textCfg;
shadowsocksConfig->clientProtocolConfig.isEmpty = false;
shadowsocksConfig->clientProtocolConfig.nativeConfig = textCfg;
return shadowsocksConfig;
}

View File

@@ -4,6 +4,7 @@
#include <QObject>
#include "configurator_base.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/defs.h"
class ShadowSocksConfigurator : public ConfiguratorBase
@@ -12,8 +13,11 @@ class ShadowSocksConfigurator : public ConfiguratorBase
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);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
};
#endif // SHADOWSOCKS_CONFIGURATOR_H

View File

@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
#ifndef Q_OS_IOS
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,10 +67,9 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
#ifndef Q_OS_IOS
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -102,7 +101,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
#elif defined(Q_OS_MACX)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif

View File

@@ -7,16 +7,21 @@
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <type_traits>
#include <variant>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "protocols/protocols_defs.h"
#include "settings.h"
#include "utilities.h"
@@ -37,6 +42,47 @@ WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
}
ConfiguratorBase::Vars WireguardConfigurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
if (m_isAwg) {
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig);
if (!awgConfig) {
return vars;
}
vars.append({{"$AWG_SUBNET_IP", awgConfig->serverProtocolConfig.subnetAddress}});
vars.append({{"$AWG_SERVER_PORT", awgConfig->serverProtocolConfig.port}});
const auto &awgData = awgConfig->serverProtocolConfig.awgData;
vars.append({{"$JUNK_PACKET_COUNT", awgData.junkPacketCount}});
vars.append({{"$JUNK_PACKET_MIN_SIZE", awgData.junkPacketMinSize}});
vars.append({{"$JUNK_PACKET_MAX_SIZE", awgData.junkPacketMaxSize}});
vars.append({{"$INIT_PACKET_JUNK_SIZE", awgData.initPacketJunkSize}});
vars.append({{"$RESPONSE_PACKET_JUNK_SIZE", awgData.responsePacketJunkSize}});
vars.append({{"$INIT_PACKET_MAGIC_HEADER", awgData.initPacketMagicHeader}});
vars.append({{"$RESPONSE_PACKET_MAGIC_HEADER", awgData.responsePacketMagicHeader}});
vars.append({{"$UNDERLOAD_PACKET_MAGIC_HEADER", awgData.underloadPacketMagicHeader}});
vars.append({{"$TRANSPORT_PACKET_MAGIC_HEADER", awgData.transportPacketMagicHeader}});
vars.append({{"$COOKIE_REPLY_PACKET_JUNK_SIZE", awgData.cookieReplyPacketJunkSize}});
vars.append({{"$TRANSPORT_PACKET_JUNK_SIZE", awgData.transportPacketJunkSize}});
} else {
auto wgConfig = qSharedPointerCast<WireGuardProtocolConfig>(protocolConfig);
if (!wgConfig) {
return vars;
}
vars.append({{"$WIREGUARD_SUBNET_IP", wgConfig->serverProtocolConfig.subnetAddress}});
vars.append({{"$WIREGUARD_SUBNET_CIDR", protocols::wireguard::defaultSubnetCidr}});
vars.append({{"$WIREGUARD_SUBNET_MASK", protocols::wireguard::defaultSubnetMask}});
vars.append({{"$WIREGUARD_SERVER_PORT", wgConfig->serverProtocolConfig.port}});
}
return vars;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
{
// TODO review
@@ -91,23 +137,27 @@ QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig,
const QSharedPointer<ProtocolConfig> &protocolConfig,
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);
// Extract port from appropriate protocol config
if (m_isAwg) {
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig);
connData.port = awgConfig ? awgConfig->serverProtocolConfig.port : m_defaultPort;
} else {
auto wgConfig = qSharedPointerCast<WireGuardProtocolConfig>(protocolConfig);
connData.port = wgConfig ? wgConfig->serverProtocolConfig.port : m_defaultPort;
}
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
errorCode = ErrorCode::InternalError;
return connData;
}
QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
@@ -124,10 +174,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
QHostAddress result;
QHostAddress lastIp;
if (ips.empty()) {
lastIp.setAddress(containerConfig.value(m_protocolName)
.toObject()
.value(config_key::subnet_address)
.toString(protocols::wireguard::defaultSubnetAddress));
// Get subnet from protocol config
QString subnetAddress;
if (m_isAwg) {
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig);
subnetAddress = awgConfig ? awgConfig->serverProtocolConfig.subnetAddress : protocols::wireguard::defaultSubnetAddress;
} else {
auto wgConfig = qSharedPointerCast<WireGuardProtocolConfig>(protocolConfig);
subnetAddress = wgConfig ? wgConfig->serverProtocolConfig.subnetAddress : protocols::wireguard::defaultSubnetAddress;
}
lastIp.setAddress(subnetAddress);
} else {
lastIp = ips.last();
}
@@ -165,36 +221,51 @@ 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_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
return connData;
}
bool isAwg = (container == DockerContainer::Awg2);
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
errorCode = m_serverController->runScript(
credentials,
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
m_serverController->replaceVars(script, generateProtocolVars(credentials, container)));
return connData;
}
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
QSharedPointer<ProtocolConfig> WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
QSharedPointer<ProtocolConfig> result;
if (m_isAwg) {
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig);
if (!awgConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
result = awgConfig;
} else {
auto wgConfig = qSharedPointerCast<WireGuardProtocolConfig>(protocolConfig);
if (!wgConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
result = wgConfig;
}
QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = m_serverController->replaceVars(
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
scriptData, generateProtocolVars(credentials, container, protocolConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
ConnectionData connData = prepareWireguardConfig(credentials, container, protocolConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
return nullptr;
}
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
@@ -202,40 +273,37 @@ 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;
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(result);
std::visit([&connData, &config](const auto &protocolConfig) -> void {
using ConfigType = std::decay_t<decltype(*protocolConfig)>;
if constexpr (std::is_same_v<ConfigType, AwgProtocolConfig> || std::is_same_v<ConfigType, WireGuardProtocolConfig>) {
protocolConfig->clientProtocolConfig.isEmpty = false;
protocolConfig->clientProtocolConfig.clientId = connData.clientPubKey;
protocolConfig->clientProtocolConfig.hostname = connData.host;
protocolConfig->clientProtocolConfig.port = connData.port.toInt();
protocolConfig->clientProtocolConfig.wireGuardData.clientPrivateKey = connData.clientPrivKey;
protocolConfig->clientProtocolConfig.wireGuardData.clientIp = connData.clientIP;
protocolConfig->clientProtocolConfig.wireGuardData.clientPublicKey = connData.clientPubKey;
protocolConfig->clientProtocolConfig.wireGuardData.pskKey = connData.pskKey;
protocolConfig->clientProtocolConfig.wireGuardData.serverPubKey = connData.serverPubKey;
protocolConfig->clientProtocolConfig.wireGuardData.mtu = protocolConfig->serverProtocolConfig.mtu;
protocolConfig->clientProtocolConfig.wireGuardData.persistentKeepAlive = "25";
protocolConfig->clientProtocolConfig.wireGuardData.allowedIps = QStringList{"0.0.0.0/0", "::/0"};
protocolConfig->clientProtocolConfig.nativeConfig = config;
}
}, variant);
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();
return result;
}
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
void WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
processConfigWithDnsSettings(dns, protocolConfig);
}
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
void WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QSharedPointer<ProtocolConfig> &protocolConfig)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
processConfigWithDnsSettings(dns, protocolConfig);
}

View File

@@ -6,6 +6,8 @@
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/defs.h"
#include "core/scripts_registry.h"
@@ -27,20 +29,23 @@ public:
QString port;
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
void processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig) override;
void processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QSharedPointer<ProtocolConfig> &protocolConfig) override;
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
static ConnectionData genClientKeys();
private:
QList<QHostAddress> getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode);
bool m_isAwg;
QString m_serverConfigPath;

View File

@@ -6,9 +6,11 @@
#include <QUuid>
#include "logger.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/scripts_registry.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "protocols/protocols_defs.h"
namespace {
Logger logger("XrayConfigurator");
@@ -19,8 +21,24 @@ XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSh
{
}
ConfiguratorBase::Vars XrayConfigurator::generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const
{
Vars vars = generateCommonVars(credentials, container);
auto xrayConfig = qSharedPointerCast<XrayProtocolConfig>(protocolConfig);
if (!xrayConfig) {
return vars;
}
vars.append({{"$XRAY_SITE_NAME", xrayConfig->serverProtocolConfig.site}});
vars.append({{"$XRAY_SERVER_PORT", xrayConfig->serverProtocolConfig.port}});
return vars;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
@@ -44,7 +62,6 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains("inbounds")) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
@@ -106,7 +123,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_serverController->runScript(
credentials,
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
m_serverController->replaceVars(restartScript, generateProtocolVars(credentials, container))
);
if (errorCode != ErrorCode::NoError) {
@@ -117,24 +134,30 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
return clientId;
}
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
QSharedPointer<ProtocolConfig> XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode)
{
auto xrayConfig = qSharedPointerCast<XrayProtocolConfig>(protocolConfig);
if (!xrayConfig) {
errorCode = ErrorCode::InternalError;
return nullptr;
}
// Get client ID from prepareServerConfig
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
QString xrayClientId = prepareServerConfig(credentials, container, protocolConfig, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
return "";
return nullptr;
}
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
generateProtocolVars(credentials, container, protocolConfig));
if (config.isEmpty()) {
logger.error() << "Failed to get config template";
errorCode = ErrorCode::InternalError;
return "";
return nullptr;
}
QString xrayPublicKey =
@@ -142,7 +165,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
errorCode = ErrorCode::InternalError;
return "";
return nullptr;
}
xrayPublicKey.replace("\n", "");
@@ -151,7 +174,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return "";
return nullptr;
}
xrayShortId.replace("\n", "");
@@ -162,12 +185,16 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
errorCode = ErrorCode::InternalError;
return "";
return nullptr;
}
config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
return config;
xrayConfig->clientProtocolConfig.isEmpty = false;
xrayConfig->clientProtocolConfig.clientId = xrayClientId;
xrayConfig->clientProtocolConfig.nativeConfig = config;
return xrayConfig;
}

View File

@@ -4,6 +4,7 @@
#include <QObject>
#include "configurator_base.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/defs.h"
class XrayConfigurator : public ConfiguratorBase
@@ -12,12 +13,15 @@ class XrayConfigurator : public ConfiguratorBase
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);
QSharedPointer<ProtocolConfig> createConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode) override;
Vars generateProtocolVars(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig) const override;
private:
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const QSharedPointer<ProtocolConfig> &protocolConfig, ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -14,11 +14,6 @@ namespace apiDefs
ExternalPremium
};
enum ConfigSource {
Telegram = 1,
AmneziaGateway
};
namespace key
{
constexpr QLatin1String configVersion("config_version");
@@ -47,14 +42,12 @@ namespace apiDefs
constexpr QLatin1String serverCountryName("server_country_name");
constexpr QLatin1String osVersion("os_version");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
constexpr QLatin1String issuedConfigs("issued_configs");
constexpr QLatin1String subscriptionDescription("subscription_description");
constexpr QLatin1String supportInfo("support_info");
constexpr QLatin1String email("email");
@@ -66,17 +59,6 @@ namespace apiDefs
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
constexpr QLatin1String userCountryCode("user_country_code");
constexpr QLatin1String serviceInfo("service_info");
constexpr QLatin1String isAdVisible("is_ad_visible");
constexpr QLatin1String adHeader("ad_header");
constexpr QLatin1String adDescription("ad_description");
constexpr QLatin1String adEndpoint("ad_endpoint");
}
const int requestTimeoutMsecs = 12 * 1000; // 12 secs

View File

@@ -1,7 +1,6 @@
#include "apiUtils.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
namespace
@@ -24,7 +23,7 @@ namespace
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime now = QDateTime::currentDateTime();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}
@@ -33,8 +32,8 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
{
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: return true;
case apiDefs::ConfigSource::AmneziaGateway: return true;
case amnezia::ServerConfigType::ApiV1: return true;
case amnezia::ServerConfigType::ApiV2: return true;
default: return false;
}
}
@@ -44,7 +43,7 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: {
case amnezia::ServerConfigType::ApiV1: {
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
@@ -56,7 +55,7 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
return apiDefs::ConfigType::AmneziaFreeV2;
}
};
case apiDefs::ConfigSource::AmneziaGateway: {
case amnezia::ServerConfigType::ApiV2: {
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
@@ -78,50 +77,39 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
};
}
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
amnezia::ServerConfigType apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
{
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
return static_cast<amnezia::ServerConfigType>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
}
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
} else if (replyError == QNetworkReply::NoError) {
} else if (reply->error() == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << replyError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << replyErrorString;
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << httpStatusCode;
int httpStatusFromBody = -1;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
}
if (httpStatusFromBody == httpStatusCodeConflict) {
if (httpStatusCode == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError;
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
} else if (httpStatusCode == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
}
return amnezia::ErrorCode::ApiConfigDownloadError;
}
@@ -174,51 +162,3 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
}
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return {};
}
QString vpnKeyText = "";
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
QString vpnKeyStr = "{";
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
vpnKeyStr += "}, ";
vpnKeyStr += "\"auth_data\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
vpnKeyStr += "}";
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
return vpnKeyText;
}

View File

@@ -16,14 +16,11 @@ namespace apiUtils
bool isPremiumServer(const QJsonObject &serverConfigObject);
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ServerConfigType getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
}
#endif // APIUTILS_H

View File

@@ -0,0 +1,684 @@
#include "apiConfigController.h"
#include <QClipboard>
#include <QEventLoop>
#include "amnezia_application.h"
#include "configurators/wireguard_configurator.h"
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/controllers/api/gatewayController.h"
#include "core/qrCodeUtils.h"
#include "core/utils/fileUtils.h"
#include "core/models/servers/serverConfig.h"
#include "core/models/servers/apiV2ServerConfig.h"
#include "version.h"
namespace
{
namespace configKey
{
constexpr char cloak[] = "cloak";
constexpr char awg[] = "awg";
constexpr char vless[] = "vless";
constexpr char apiEndpoint[] = "api_endpoint";
constexpr char accessToken[] = "api_key";
constexpr char certificate[] = "certificate";
constexpr char publicKey[] = "public_key";
constexpr char protocol[] = "protocol";
constexpr char uuid[] = "installation_uuid";
constexpr char osVersion[] = "os_version";
constexpr char appVersion[] = "app_version";
constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code";
constexpr char serviceType[] = "service_type";
constexpr char serviceInfo[] = "service_info";
constexpr char serviceProtocol[] = "service_protocol";
constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload";
constexpr char apiConfig[] = "api_config";
constexpr char authData[] = "auth_data";
constexpr char config[] = "config";
}
struct ProtocolData
{
OpenVpnConfigurator::ConnectionData certRequest;
QString wireGuardClientPrivKey;
QString wireGuardClientPubKey;
QString xrayUuid;
};
struct GatewayRequestData
{
QString osVersion;
QString appVersion;
QString installationUuid;
QString userCountryCode;
QString serverCountryCode;
QString serviceType;
QString serviceProtocol;
QJsonObject authData;
QJsonObject toJsonObject() const
{
QJsonObject obj;
if (!osVersion.isEmpty()) {
obj[configKey::osVersion] = osVersion;
}
if (!appVersion.isEmpty()) {
obj[configKey::appVersion] = appVersion;
}
if (!installationUuid.isEmpty()) {
obj[configKey::uuid] = installationUuid;
}
if (!userCountryCode.isEmpty()) {
obj[configKey::userCountryCode] = userCountryCode;
}
if (!serverCountryCode.isEmpty()) {
obj[configKey::serverCountryCode] = serverCountryCode;
}
if (!serviceType.isEmpty()) {
obj[configKey::serviceType] = serviceType;
}
if (!serviceProtocol.isEmpty()) {
obj[configKey::serviceProtocol] = serviceProtocol;
}
if (!authData.isEmpty()) {
obj[configKey::authData] = authData;
}
return obj;
}
};
ProtocolData generateProtocolData(const QString &protocol)
{
ProtocolData protocolData;
if (protocol == configKey::cloak) {
protocolData.certRequest = OpenVpnConfigurator::createCertRequest();
} else 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 appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload)
{
if (protocol == configKey::cloak) {
apiPayload[configKey::certificate] = protocolData.certRequest.request;
} else if (protocol == configKey::awg) {
apiPayload[configKey::publicKey] = protocolData.wireGuardClientPubKey;
} else if (protocol == configKey::vless) {
apiPayload[configKey::publicKey] = protocolData.xrayUuid;
}
}
ErrorCode fillServerConfig(const QString &protocol, const ProtocolData &apiPayloadData, const QByteArray &apiResponseBody,
QSharedPointer<ServerConfig> &serverConfigPtr)
{
QJsonObject serverConfig;
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::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::cloak) {
configStr.replace("<key>", "<key>\n");
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
} else if (protocol == configKey::awg) {
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
auto containers = newServerConfig.value(config_key::containers).toArray();
if (containers.isEmpty()) {
qDebug() << "missing containers field";
return ErrorCode::ApiConfigEmptyError;
}
auto container = containers.at(0).toObject();
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
auto serverProtocolConfig = container.value(containerName).toObject();
auto clientProtocolConfig =
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
//TODO looks like this block can be removed after v1 configs EOL
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize);
serverProtocolConfig[config_key::initPacketJunkSize] = clientProtocolConfig.value(config_key::initPacketJunkSize);
serverProtocolConfig[config_key::responsePacketJunkSize] = clientProtocolConfig.value(config_key::responsePacketJunkSize);
serverProtocolConfig[config_key::initPacketMagicHeader] = clientProtocolConfig.value(config_key::initPacketMagicHeader);
serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader);
serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader);
serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader);
serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = clientProtocolConfig.value(config_key::cookieReplyPacketJunkSize);
serverProtocolConfig[config_key::transportPacketJunkSize] = clientProtocolConfig.value(config_key::transportPacketJunkSize);
serverProtocolConfig[config_key::specialJunk1] = clientProtocolConfig.value(config_key::specialJunk1);
serverProtocolConfig[config_key::specialJunk2] = clientProtocolConfig.value(config_key::specialJunk2);
serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3);
serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4);
serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5);
serverProtocolConfig[config_key::controlledJunk1] = clientProtocolConfig.value(config_key::controlledJunk1);
serverProtocolConfig[config_key::controlledJunk2] = clientProtocolConfig.value(config_key::controlledJunk2);
serverProtocolConfig[config_key::controlledJunk3] = clientProtocolConfig.value(config_key::controlledJunk3);
serverProtocolConfig[config_key::specialHandshakeTimeout] = clientProtocolConfig.value(config_key::specialHandshakeTimeout);
//
container[containerName] = serverProtocolConfig;
containers.replace(0, container);
newServerConfig[config_key::containers] = containers;
configStr = QString(QJsonDocument(newServerConfig).toJson());
}
QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
if (newServerConfig.value(config_key::configVersion).toInt() == static_cast<int>(amnezia::ServerConfigType::ApiV2)) {
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
serverConfig[config_key::name] = newServerConfig.value(config_key::name);
}
auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
serverConfig[config_key::defaultContainer] = defaultContainer;
QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
auto apiConfig = QJsonObject::fromVariantMap(map);
if (newServerConfig.value(config_key::configVersion).toInt() == static_cast<int>(amnezia::ServerConfigType::ApiV2)) {
apiConfig.insert(apiDefs::key::supportedProtocols,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
}
serverConfig[configKey::apiConfig] = apiConfig;
serverConfigPtr = ServerConfig::createServerConfig(serverConfig);
return ErrorCode::NoError;
}
}
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ApiServicesModel> &apiServicesModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings)
{
}
ErrorCode ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
{
if (fileName.isEmpty()) {
return ErrorCode::PermissionsError;
}
auto serverConfigPtr = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
apiConfigObject.value(configKey::serviceType).toString(),
configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(),
serverConfigObject.value(configKey::authData).toObject() };
QString protocol = gatewayRequestData.serviceProtocol;
ProtocolData protocolData = generateProtocolData(protocol);
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
QString nativeConfig = jsonConfig.value(configKey::config).toString();
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
FileUtils::saveFile(fileName, nativeConfig);
return ErrorCode::NoError;
}
ErrorCode ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
{
auto serverConfigPtr = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
apiConfigObject.value(configKey::serviceType).toString(),
configKey::awg, // apiConfigObject.value(configKey::serviceProtocol).toString(),
serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
return errorCode;
}
return ErrorCode::NoError;
}
void ApiConfigsController::prepareVpnKeyExport()
{
auto serverConfigPtr = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
m_vpnKey = vpnKey;
vpnKey.replace("vpn://", "");
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(vpnKey.toUtf8());
}
void ApiConfigsController::copyVpnKeyToClipboard()
{
auto clipboard = amnApp->getClipboard();
clipboard->setText(m_vpnKey);
}
ErrorCode ApiConfigsController::fillAvailableServices()
{
QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains("services")) {
errorCode = ErrorCode::ApiServicesMissingError;
}
}
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
m_apiServicesModel->updateModel(data);
return ErrorCode::NoError;
}
bool ApiConfigsController::isServerFromApiAlreadyExists(const QString &userCountryCode,
const QString &serviceType,
const QString &serviceProtocol) const
{
auto servers = m_settings->serversArray();
for (const auto &server : servers) {
auto serverConfig = ServerConfig::createServerConfig(server.toObject());
if (serverConfig->type != amnezia::ServerConfigType::ApiV2) continue;
auto apiV2Config = qSharedPointerCast<ApiV2ServerConfig>(serverConfig);
if (!apiV2Config) continue;
if (apiV2Config->apiConfig.userCountryCode == userCountryCode &&
apiV2Config->apiConfig.serviceType == serviceType &&
apiV2Config->apiConfig.serviceProtocol == serviceProtocol) {
return true;
}
}
return false;
}
bool ApiConfigsController::isApiKeyExpired(int serverIndex) const
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return false;
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
if (serverConfig->type != amnezia::ServerConfigType::ApiV2) return false;
auto apiV2Config = qSharedPointerCast<ApiV2ServerConfig>(serverConfig);
if (!apiV2Config) return false;
QString expiresIso = apiV2Config->apiConfig.publicKey.expiresAt;
if (expiresIso.isEmpty()) {
expiresIso = apiV2Config->apiConfig.subscription.end_date;
}
if (expiresIso.isEmpty()) return false;
auto expiresAt = QDateTime::fromString(expiresIso, Qt::ISODate);
return QDateTime::currentDateTime() > expiresAt;
}
void ApiConfigsController::removeApiConfig(int serverIndex)
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return;
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
if (serverConfig->type != amnezia::ServerConfigType::ApiV2) return;
auto apiV2 = qSharedPointerCast<ApiV2ServerConfig>(serverConfig);
if (!apiV2) return;
apiV2->containerConfigs.clear();
apiV2->apiConfig.publicKey.expiresAt.clear();
apiV2->apiConfig.vpnKey.clear();
apiV2->defaultContainer = ContainerProps::containerToString(DockerContainer::None);
m_serversModel->editServer(apiV2, serverIndex);
}
ErrorCode ApiConfigsController::importServiceFromGateway()
{
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
m_apiServicesModel->getCountryCode(),
"",
m_apiServicesModel->getSelectedServiceType(),
m_apiServicesModel->getSelectedServiceProtocol(),
QJsonObject() };
if (isServerFromApiAlreadyExists(gatewayRequestData.userCountryCode, gatewayRequestData.serviceType,
gatewayRequestData.serviceProtocol)) {
return ErrorCode::ApiConfigAlreadyAdded;
}
ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol);
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
QSharedPointer<ServerConfig> serverConfigPtr;
if (errorCode == ErrorCode::NoError) {
errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, serverConfigPtr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (serverConfigPtr->type == amnezia::ServerConfigType::ApiV2) {
auto apiV2ServerConfig = qSharedPointerCast<ApiV2ServerConfig>(serverConfigPtr);
apiV2ServerConfig->apiConfig.userCountryCode = m_apiServicesModel->getCountryCode();
apiV2ServerConfig->apiConfig.serviceType = m_apiServicesModel->getSelectedServiceType();
apiV2ServerConfig->apiConfig.serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol();
}
m_serversModel->addServer(serverConfigPtr);
return ErrorCode::NoError;
} else {
return errorCode;
}
}
ErrorCode ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig)
{
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
auto serverConfigJson = serverConfigPtr->toJson();
auto apiConfig = serverConfigJson.value(configKey::apiConfig).toObject();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
apiConfig.value(configKey::userCountryCode).toString(),
newCountryCode,
apiConfig.value(configKey::serviceType).toString(),
apiConfig.value(configKey::serviceProtocol).toString(),
serverConfigJson.value(configKey::authData).toObject() };
ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol);
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
QSharedPointer<ServerConfig> newServerConfigPtr;
if (errorCode == ErrorCode::NoError) {
errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, newServerConfigPtr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (newServerConfigPtr->type == amnezia::ServerConfigType::ApiV2 && serverConfigPtr->type == amnezia::ServerConfigType::ApiV2) {
auto newApiV2 = qSharedPointerCast<ApiV2ServerConfig>(newServerConfigPtr);
auto oldApiV2 = qSharedPointerCast<ApiV2ServerConfig>(serverConfigPtr);
newApiV2->apiConfig.userCountryCode = oldApiV2->apiConfig.userCountryCode;
newApiV2->apiConfig.serviceType = oldApiV2->apiConfig.serviceType;
newApiV2->apiConfig.serviceProtocol = oldApiV2->apiConfig.serviceProtocol;
newApiV2->apiConfig.vpnKey = oldApiV2->apiConfig.vpnKey;
newApiV2->apiConfig.authData.apiKey = gatewayRequestData.authData.value("api_key").toString();
if (serverConfigPtr->nameOverriddenByUser) {
newApiV2->name = oldApiV2->name;
newApiV2->nameOverriddenByUser = true;
}
}
m_serversModel->editServer(newServerConfigPtr, serverIndex);
return ErrorCode::NoError;
} else {
return errorCode;
}
}
ErrorCode ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
auto installationUuid = m_settings->getInstallationUuid(true);
auto serverConfigJson2 = serverConfigPtr->toJson();
QString serviceProtocol = serverConfigJson2.value(configKey::protocol).toString();
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QJsonObject apiPayload;
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload[configKey::uuid] = installationUuid;
apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[configKey::appVersion] = QString(APP_VERSION);
apiPayload[configKey::accessToken] = serverConfigJson2.value(configKey::accessToken).toString();
apiPayload[configKey::apiEndpoint] = serverConfigJson2.value(configKey::apiEndpoint).toString();
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
QSharedPointer<ServerConfig> updatedConfigPtr;
errorCode = fillServerConfig(serviceProtocol, protocolData, responseBody, updatedConfigPtr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
m_serversModel->editServer(updatedConfigPtr, serverIndex);
return ErrorCode::NoError;
} else {
return errorCode;
}
}
ErrorCode ApiConfigsController::deactivateDevice()
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (!apiUtils::isPremiumServer(serverConfigObject)) {
return ErrorCode::NoError;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
apiConfigObject.value(configKey::serverCountryCode).toString(),
apiConfigObject.value(configKey::serviceType).toString(),
"",
serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
return errorCode;
}
serverConfigPtr->containerConfigs.clear();
m_serversModel->editServer(serverConfigPtr, serverIndex);
return ErrorCode::NoError;
}
ErrorCode ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode)
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (!apiUtils::isPremiumServer(serverConfigObject)) {
return ErrorCode::NoError;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
uuid,
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
apiConfigObject.value(configKey::serviceType).toString(),
"",
serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
return errorCode;
}
if (uuid == m_settings->getInstallationUuid(true)) {
serverConfigPtr->containerConfigs.clear();
m_serversModel->editServer(serverConfigPtr, serverIndex);
}
return ErrorCode::NoError;
}
bool ApiConfigsController::isConfigValid()
{
int serverIndex = m_serversModel->getDefaultServerIndex();
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
QJsonObject serverConfigObject = serverConfigPtr->toJson();
auto configSource = apiUtils::getConfigSource(serverConfigObject);
if (configSource == amnezia::ServerConfigType::ApiV1
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
removeApiConfig(serverIndex);
return updateServiceFromTelegram(serverIndex);
} else if (configSource == amnezia::ServerConfigType::ApiV2
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
return updateServiceFromGateway(serverIndex, "", "");
} else if (configSource && isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event";
if (configSource == amnezia::ServerConfigType::ApiV2) {
return updateServiceFromGateway(serverIndex, "", "");
} else {
removeApiConfig(serverIndex);
return updateServiceFromTelegram(serverIndex);
}
}
return true;
}
void ApiConfigsController::setCurrentProtocol(const QString &protocolName)
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigPtr = m_serversModel->getServerConfig(serverIndex);
auto serverConfigObject = serverConfigPtr->toJson();
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
apiConfigObject[configKey::serviceProtocol] = protocolName;
serverConfigObject.insert(configKey::apiConfig, apiConfigObject);
auto updatedPtr = ServerConfig::createServerConfig(serverConfigObject);
m_serversModel->editServer(updatedPtr, serverIndex);
}
bool ApiConfigsController::isVlessProtocol()
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (apiConfigObject[configKey::serviceProtocol].toString() == "vless") {
return true;
}
return false;
}
QList<QString> ApiConfigsController::getQrCodes()
{
return m_qrCodes;
}
int ApiConfigsController::getQrCodesCount()
{
return m_qrCodes.size();
}
QString ApiConfigsController::getVpnKey()
{
return m_vpnKey;
}
ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody);
}

View File

@@ -0,0 +1,57 @@
#ifndef APICONFIGSCONTROLLER_H
#define APICONFIGSCONTROLLER_H
#include <QObject>
#include "configurators/openvpn_configurator.h"
#include "ui/models/api/apiServicesModel.h"
#include "ui/models/servers_model.h"
class ApiConfigsController : public QObject
{
Q_OBJECT
public:
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiServicesModel> &apiServicesModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
QList<QString> getQrCodes();
int getQrCodesCount();
QString getVpnKey();
public slots:
ErrorCode exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
ErrorCode revokeNativeConfig(const QString &serverCountryCode);
void prepareVpnKeyExport();
void copyVpnKeyToClipboard();
ErrorCode fillAvailableServices();
ErrorCode importServiceFromGateway();
ErrorCode updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig = false);
ErrorCode updateServiceFromTelegram(const int serverIndex);
ErrorCode deactivateDevice();
ErrorCode deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
bool isConfigValid();
void setCurrentProtocol(const QString &protocolName);
bool isVlessProtocol();
private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody);
bool isServerFromApiAlreadyExists(const QString &userCountryCode,
const QString &serviceType,
const QString &serviceProtocol) const;
bool isApiKeyExpired(int serverIndex) const;
void removeApiConfig(int serverIndex);
QList<QString> m_qrCodes;
QString m_vpnKey;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
std::shared_ptr<Settings> m_settings;
};
#endif // APICONFIGSCONTROLLER_H

View File

@@ -0,0 +1,134 @@
#include "apiPremV1MigrationController.h"
#include <QEventLoop>
#include <QTimer>
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/controllers/api/gatewayController.h"
ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
bool ApiPremV1MigrationController::hasConfigsToMigration()
{
QJsonArray vpnKeys;
auto serversCount = m_serversModel->getServersCount();
for (size_t i = 0; i < serversCount; i++) {
auto serverConfigPtr = m_serversModel->getServerConfig(i);
auto serverConfigObject = serverConfigPtr->toJson();
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
continue;
}
QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject);
vpnKeys.append(vpnKey);
}
if (!vpnKeys.isEmpty()) {
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload["configs"] = vpnKeys;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody);
auto migrationsStatus = QJsonDocument::fromJson(responseBody).object();
for (const auto &migrationStatus : migrationsStatus) {
if (migrationStatus == "not_found") {
return true;
}
}
}
return false;
}
void ApiPremV1MigrationController::getSubscriptionList(const QString &email)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_email = email;
m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array();
if (m_subscriptionsModel.isEmpty()) {
emit noSubscriptionToMigrate();
return;
}
emit subscriptionsModelChanged();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
QJsonArray ApiPremV1MigrationController::getSubscriptionModel()
{
return m_subscriptionsModel;
}
void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex)
{
QEventLoop wait;
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
wait.exec();
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_subscriptionIndex = subscriptionIndex;
emit otpSuccessfullySent();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
void ApiPremV1MigrationController::migrate(const QString &migrationCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString();
apiPayload[apiDefs::key::migrationCode] = migrationCode;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
auto responseObject = QJsonDocument::fromJson(responseBody).object();
QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString();
emit importPremiumV2VpnKey(premiumV2VpnKey);
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
bool ApiPremV1MigrationController::isPremV1MigrationReminderActive()
{
return m_settings->isPremV1MigrationReminderActive();
}
void ApiPremV1MigrationController::disablePremV1MigrationReminder()
{
m_settings->disablePremV1MigrationReminder();
}

View File

@@ -0,0 +1,50 @@
#ifndef APIPREMV1MIGRATIONCONTROLLER_H
#define APIPREMV1MIGRATIONCONTROLLER_H
#include <QObject>
#include "ui/models/servers_model.h"
class ApiPremV1MigrationController : public QObject
{
Q_OBJECT
public:
ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged)
public slots:
bool hasConfigsToMigration();
void getSubscriptionList(const QString &email);
QJsonArray getSubscriptionModel();
void sendMigrationCode(const int subscriptionIndex);
void migrate(const QString &migrationCode);
bool isPremV1MigrationReminderActive();
void disablePremV1MigrationReminder();
signals:
void subscriptionsModelChanged();
void otpSuccessfullySent();
void importPremiumV2VpnKey(const QString &vpnKey);
void errorOccurred(ErrorCode errorCode);
void showMigrationDrawer();
void migrationFinished();
void noSubscriptionToMigrate();
private:
QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
QJsonArray m_subscriptionsModel;
int m_subscriptionIndex;
QString m_email;
};
#endif // APIPREMV1MIGRATIONCONTROLLER_H

View File

@@ -4,8 +4,7 @@
#include <QTimer>
#include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h"
#include "platforms/ios/ios_controller.h"
#include "core/controllers/api/gatewayController.h"
#include "version.h"
namespace
@@ -42,36 +41,34 @@ ApiSettingsController::~ApiSettingsController()
{
}
bool ApiSettingsController::getAccountInfo(bool reload)
ErrorCode ApiSettingsController::getAccountInfo(bool reload)
{
if (reload) {
QEventLoop wait;
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
wait.exec();
}
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto processedIndex = m_serversModel->getProcessedServerIndex();
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
auto serverConfigPtr = m_serversModel->getServerConfig(processedIndex);
auto serverConfig = serverConfigPtr->toJson();
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto authData = serverConfig.value(configKey::authData).toObject();
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return false;
return errorCode;
}
QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object();
@@ -82,7 +79,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
updateApiDevicesModel();
}
return true;
return ErrorCode::NoError;
}
void ApiSettingsController::updateApiCountryModel()

View File

@@ -18,13 +18,10 @@ public:
~ApiSettingsController();
public slots:
bool getAccountInfo(bool reload);
ErrorCode getAccountInfo(bool reload);
void updateApiCountryModel();
void updateApiDevicesModel();
signals:
void errorOccurred(ErrorCode errorCode);
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;

View File

@@ -0,0 +1,364 @@
#include "gatewayController.h"
#include <algorithm>
#include <random>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QUrl>
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amnezia_application.h"
#include "core/api/apiUtils.h"
#include "core/networkUtilities.h"
#include "utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/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.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
{
}
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
QNetworkReply *reply;
reply = amnApp->networkManager()->get(request);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
responseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
auto requestFunction = [&request, &responseBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->get(request);
};
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
const QList<QSslError> &nestedSslErrors) {
responseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
sslErrors = nestedSslErrors;
reply = nestedReply;
return true;
}
return false;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
return errorCode;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(endpoint.arg(m_gatewayEndpoint));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
EVP_PKEY *publicKey = nullptr;
try {
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when encrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
QByteArray encryptedResponseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
};
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll();
reply = nestedReply;
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
if (errorCode) {
return errorCode;
}
try {
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
}
}
QStringList GatewayController::getProxyUrls()
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList proxyStorageUrls;
if (m_isDevEnvironment) {
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll();
reply->deleteLater();
EVP_PKEY *privateKey = nullptr;
QByteArray responseBody;
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
continue;
}
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
} else {
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
}
}
return {};
}
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred";
qDebug() << reply->error();
return true;
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << reply->error();
return true;
}
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << reply->error();
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
qDebug() << reply->error();
return true;
} else if (checkEncryption) {
try {
QSimpleCrypto::QBlockCipher blockCipher;
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
} catch (...) {
qDebug() << "failed to decrypt the data";
return true;
}
}
return false;
}
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls();
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
for (const QString &proxyUrl : proxyUrls) {
qDebug() << "go to the next proxy endpoint";
reply->deleteLater(); // delete the previous reply
reply = requestFunction(endpoint.arg(proxyUrl));
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (replyProcessingFunction(reply, sslErrors)) {
break;
}
}
}

View File

@@ -0,0 +1,37 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QNetworkReply>
#include <QObject>
#include "core/defs.h"
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
class GatewayController : public QObject
{
Q_OBJECT
public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
private:
QStringList getProxyUrls();
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
};
#endif // GATEWAYCONTROLLER_H

View File

@@ -0,0 +1,172 @@
#include "configController.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/servers/apiV1ServerConfig.h"
#include "core/models/servers/apiV2ServerConfig.h"
#include "core/models/servers/selfHostedServerConfig.h"
#include "core/networkUtilities.h"
#include "protocols/protocols_defs.h"
#include "settings.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/models/protocols/cloakProtocolConfig.h"
ConfigController::ConfigController(std::shared_ptr<Settings> settings, QObject *parent)
: QObject(parent), m_settings(settings)
{
}
void ConfigController::addServer(const QSharedPointer<ServerConfig> &serverConfig)
{
m_settings->addServer(serverConfig->toJson());
emit serverAdded(m_settings->serversCount() - 1);
}
void ConfigController::editServer(const QSharedPointer<ServerConfig> &serverConfig, int serverIndex)
{
updateServerInSettings(serverConfig, serverIndex);
emit serverEdited(serverIndex);
}
void ConfigController::removeServer(int serverIndex)
{
m_settings->removeServer(serverIndex);
emit serverRemoved(serverIndex);
}
void ConfigController::setDefaultServer(int serverIndex)
{
m_settings->setDefaultServer(serverIndex);
emit defaultServerChanged(serverIndex);
}
void ConfigController::setDefaultContainer(int serverIndex, int containerIndex)
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return;
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
auto container = static_cast<DockerContainer>(containerIndex);
serverConfig->defaultContainer = ContainerProps::containerToString(container);
updateServerInSettings(serverConfig, serverIndex);
emit defaultContainerChanged(serverIndex, container);
}
bool ConfigController::isServerFromApiAlreadyExists(quint16 crc) const
{
auto servers = m_settings->serversArray();
for (const auto &server : servers) {
auto serverConfig = ServerConfig::createServerConfig(server.toObject());
if (static_cast<quint16>(serverConfig->crc) == crc) {
return true;
}
}
return false;
}
// Removed API-specific helpers; moved to ApiConfigsController
QStringList ConfigController::getAllInstalledServicesName(int serverIndex) const
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return {};
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
QStringList serviceNames;
for (auto it = serverConfig->containerConfigs.constBegin();
it != serverConfig->containerConfigs.constEnd(); ++it) {
const QString &containerName = it.key();
serviceNames.append(containerName);
}
return serviceNames;
}
void ConfigController::clearCachedProfile(int serverIndex, DockerContainer container)
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return;
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
QString containerName = ContainerProps::containerToString(container);
if (serverConfig->containerConfigs.contains(containerName)) {
auto &containerConfig = serverConfig->containerConfigs[containerName];
containerConfig.clearProfile();
updateServerInSettings(serverConfig, serverIndex);
}
}
void ConfigController::updateServerInSettings(const QSharedPointer<ServerConfig> &serverConfig, int serverIndex)
{
m_settings->editServer(serverIndex, serverConfig->toJson());
}
bool ConfigController::isDefaultServerDefaultContainerHasSplitTunneling() const
{
int defaultServerIndex = m_settings->defaultServerIndex();
auto servers = m_settings->serversArray();
if (defaultServerIndex >= servers.size()) return false;
auto serverConfig = ServerConfig::createServerConfig(servers.at(defaultServerIndex).toObject());
if (!serverConfig->containerConfigs.contains(serverConfig->defaultContainer)) {
return false;
}
const auto &containerConfig = serverConfig->containerConfigs[serverConfig->defaultContainer];
return checkSplitTunnelingInContainer(containerConfig, serverConfig->defaultContainer);
}
bool ConfigController::checkSplitTunnelingInContainer(const ContainerConfig &containerConfig, const QString &defaultContainer) const
{
const DockerContainer containerType = ContainerProps::containerFromString(defaultContainer);
auto isWireguardHasSplit = [](const QString &nativeConfig, const QStringList &allowedIps) -> bool {
if (nativeConfig.contains("AllowedIPs") && !nativeConfig.contains("AllowedIPs = 0.0.0.0/0, ::/0")) {
return true;
}
if (!allowedIps.isEmpty() && !allowedIps.contains("0.0.0.0/0")) {
return true;
}
return false;
};
if (containerType == DockerContainer::Awg || containerType == DockerContainer::WireGuard) {
const auto protocolConfig = containerConfig.protocolConfigs.value(defaultContainer);
if (!protocolConfig) return false;
auto variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
return std::visit(
[&](const auto &ptr) -> bool {
using T = std::decay_t<decltype(ptr)>;
if constexpr (std::is_same_v<T, QSharedPointer<AwgProtocolConfig>> || std::is_same_v<T, QSharedPointer<WireGuardProtocolConfig>>) {
return isWireguardHasSplit(ptr->clientProtocolConfig.nativeConfig,
ptr->clientProtocolConfig.wireGuardData.allowedIps);
}
return false;
},
variant);
}
if (containerType == DockerContainer::Cloak || containerType == DockerContainer::OpenVpn || containerType == DockerContainer::ShadowSocks) {
const auto &protocolConfig = containerConfig.protocolConfigs.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
if (!protocolConfig) return false;
auto variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
return std::visit(
[&](const auto &ptr) -> bool {
using T = std::decay_t<decltype(ptr)>;
if constexpr (std::is_same_v<T, QSharedPointer<OpenVpnProtocolConfig>> ||
std::is_same_v<T, QSharedPointer<ShadowsocksProtocolConfig>> ||
std::is_same_v<T, QSharedPointer<CloakProtocolConfig>>) {
const auto nativeConfig = ptr->clientProtocolConfig.nativeConfig;
return (!nativeConfig.isEmpty() && !nativeConfig.contains("redirect-gateway"));
}
return false;
},
variant);
}
return false;
}

View File

@@ -0,0 +1,59 @@
#ifndef CONFIGCONTROLLER_H
#define CONFIGCONTROLLER_H
#include <QObject>
#include <QSharedPointer>
#include "core/defs.h"
#include "core/models/servers/serverConfig.h"
#include "core/models/containers/containers_defs.h"
class Settings;
using namespace amnezia;
class ConfigController : public QObject
{
Q_OBJECT
public:
explicit ConfigController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
virtual ~ConfigController() = default;
// Basic server management
virtual void addServer(const QSharedPointer<ServerConfig> &serverConfig);
virtual void editServer(const QSharedPointer<ServerConfig> &serverConfig, int serverIndex);
virtual void removeServer(int serverIndex);
// Default settings management
void setDefaultServer(int serverIndex);
void setDefaultContainer(int serverIndex, int containerIndex);
// API server utilities
bool isServerFromApiAlreadyExists(quint16 crc) const;
// General utilities
QStringList getAllInstalledServicesName(int serverIndex) const;
void clearCachedProfile(int serverIndex, DockerContainer container);
// Split tunneling detection
bool isDefaultServerDefaultContainerHasSplitTunneling() const;
protected:
std::shared_ptr<Settings> m_settings;
// Protected helper methods for derived classes
void updateServerInSettings(const QSharedPointer<ServerConfig> &serverConfig, int serverIndex);
bool checkSplitTunnelingInContainer(const ContainerConfig &containerConfig, const QString &defaultContainer) const;
signals:
// Common server management signals
void serverAdded(int serverIndex);
void serverEdited(int serverIndex);
void serverRemoved(int serverIndex);
void defaultServerChanged(int serverIndex);
void defaultContainerChanged(int serverIndex, DockerContainer container);
};
#endif // CONFIGCONTROLLER_H

View File

@@ -0,0 +1,93 @@
#include "connectionController.h"
#include <QtConcurrent>
#include "core/controllers/vpnConfigurationController.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "settings.h"
#include "logger.h"
#include "utilities.h"
namespace
{
Logger logger("ConnectionController");
}
ConnectionController::ConnectionController(const QSharedPointer<VpnConnection> &vpnConnection,
std::shared_ptr<Settings> settings,
QObject *parent)
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings)
{
}
QFuture<QJsonObject> ConnectionController::prepareVpnConfiguration(const QSharedPointer<ServerConfig> &serverConfig,
DockerContainer container,
const QPair<QString, QString> &dns) const
{
return QtConcurrent::run([this, serverConfig, container, dns]() -> QJsonObject {
logger.info() << "Preparing VPN configuration for container" << ContainerProps::containerToString(container);
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
QString containerName = ContainerProps::containerToString(container);
const ContainerConfig &containerConfig = serverConfig->containerConfigs.value(containerName);
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns,
serverConfig,
containerConfig,
container);
emit configurationPrepared(vpnConfiguration);
return vpnConfiguration;
});
}
QFuture<ErrorCode> ConnectionController::openConnection(const int serverIndex,
const QSharedPointer<ServerConfig> &serverConfig,
const DockerContainer container,
const ServerCredentials &credentials,
const QPair<QString, QString> &dns)
{
return QtConcurrent::run([this, serverIndex, serverConfig, container, credentials, dns]() -> ErrorCode {
if (!isServerSupported(container)) {
emit connectionError(ErrorCode::NotSupportedOnThisPlatform);
return ErrorCode::NotSupportedOnThisPlatform;
}
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
const QString containerName = ContainerProps::containerToString(container);
const ContainerConfig &containerConfig = serverConfig->containerConfigs.value(containerName);
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container);
emit connectionProgress(QString("Connecting to %1...").arg(ContainerProps::containerToString(container)));
QMetaObject::invokeMethod(m_vpnConnection.get(), "connectToVpn", Qt::QueuedConnection,
Q_ARG(int, serverIndex),
Q_ARG(ServerCredentials, credentials),
Q_ARG(DockerContainer, container),
Q_ARG(QJsonObject, vpnConfiguration));
return ErrorCode::NoError;
});
}
QFuture<ErrorCode> ConnectionController::closeConnection()
{
return QtConcurrent::run([this]() -> ErrorCode {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
return ErrorCode::NoError;
});
}
bool ConnectionController::isServerSupported(DockerContainer container) const
{
return ContainerProps::isSupportedByCurrentPlatform(container);
}
ErrorCode ConnectionController::getLastConnectionError() const
{
return m_vpnConnection ? m_vpnConnection->lastError() : ErrorCode::NoError;
}

View File

@@ -0,0 +1,57 @@
#ifndef CONNECTIONCONTROLLER_H
#define CONNECTIONCONTROLLER_H
#include <QObject>
#include <QFuture>
#include <QSharedPointer>
#include "core/defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/servers/serverConfig.h"
#include "protocols/vpnprotocol.h"
#include "vpnconnection.h"
class Settings;
class ServerController;
class VpnConfigurationsController;
using namespace amnezia;
class ConnectionController : public QObject
{
Q_OBJECT
public:
explicit ConnectionController(const QSharedPointer<VpnConnection> &vpnConnection,
std::shared_ptr<Settings> settings,
QObject *parent = nullptr);
QFuture<QJsonObject> prepareVpnConfiguration(const QSharedPointer<ServerConfig> &serverConfig,
DockerContainer container,
const QPair<QString, QString> &dns) const;
QFuture<ErrorCode> openConnection(const int serverIndex,
const QSharedPointer<ServerConfig> &serverConfig,
const DockerContainer container,
const ServerCredentials &credentials,
const QPair<QString, QString> &dns);
QFuture<ErrorCode> closeConnection();
ErrorCode getLastConnectionError() const;
signals:
void configurationPrepared(const QJsonObject &vpnConfiguration);
void connectionEstablished();
void connectionTerminated();
void connectionError(ErrorCode errorCode);
void connectionProgress(const QString &message);
private:
bool isServerSupported(DockerContainer container) const;
QSharedPointer<VpnConnection> m_vpnConnection;
std::shared_ptr<Settings> m_settings;
};
#endif // CONNECTIONCONTROLLER_H

View File

@@ -3,6 +3,8 @@
#include <QDirIterator>
#include <QTranslator>
#include "core/models/clientInfo.h"
#if defined(Q_OS_ANDROID)
#include "core/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
@@ -17,8 +19,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
QQmlApplicationEngine *engine, QObject *parent)
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
{
initCoreControllers();
initModels();
initControllers();
initUIControllers();
initSignalHandlers();
initAndroidController();
@@ -26,8 +29,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initNotificationHandler();
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(m_settings->getAppLanguage());
updateTranslator(locale);
}
void CoreController::initModels()
@@ -41,51 +45,48 @@ void CoreController::initModels()
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_openVpnConfigModel = QSharedPointer<OpenVpnConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_shadowSocksConfigModel = QSharedPointer<ShadowSocksConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_cloakConfigModel = QSharedPointer<CloakConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_wireGuardConfigModel = QSharedPointer<WireGuardConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_awgConfigModel = QSharedPointer<AwgConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_xrayConfigModel = QSharedPointer<XrayConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_ikev2ConfigModel = QSharedPointer<Ikev2ConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_sftpConfigModel = QSharedPointer<SftpConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
m_socks5ConfigModel = QSharedPointer<Socks5ProxyConfigModel>::create(this);
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_protocolsModel.reset(new ProtocolsModel(m_openVpnConfigModel, m_shadowSocksConfigModel, m_cloakConfigModel, m_wireGuardConfigModel,
m_awgConfigModel, m_xrayConfigModel,
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
auto clientManagementController = QSharedPointer<ClientManagementController>::create(m_settings, this);
m_clientManagementModel.reset(new ClientManagementModel(clientManagementController, this));
m_clientManagementUIController.reset(new ClientManagementUIController(clientManagementController, this));
m_engine->rootContext()->setContextProperty("ClientManagementUIController", m_clientManagementUIController.get());
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
m_apiServicesModel.reset(new ApiServicesModel(this));
@@ -100,62 +101,128 @@ void CoreController::initModels()
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
m_newsModel.reset(new NewsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
m_sitesModel.reset(new SitesModel(m_splitTunnelingController, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_allowedDnsModel.reset(new AllowedDnsModel(m_dnsController, this));
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_splitTunnelingController, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_languageModel.reset(new LanguageModel(m_settingsController, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
}
void CoreController::initControllers()
void CoreController::initCoreControllers()
{
m_settingsController = QSharedPointer<SettingsController>::create(m_settings, this);
m_dnsController = QSharedPointer<DnsController>::create(m_settings, this);
m_splitTunnelingController = QSharedPointer<SplitTunnelingController>::create(m_settings, m_vpnConnection, this);
m_exportController = QSharedPointer<ExportController>::create(m_settings, this);
m_installController = QSharedPointer<InstallController>::create(m_settings, this);
}
void CoreController::initUIControllers()
{
auto coreConnectionController = QSharedPointer<ConnectionController>::create(m_vpnConnection, m_settings, this);
m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
new ConnectionUIController(m_serversModel, m_containersModel, m_clientManagementModel, coreConnectionController));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_pageController.reset(new PageController(m_serversModel, m_settingsController));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_focusController.reset(new FocusController(m_engine, this));
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
auto clientManagementController = m_clientManagementUIController->getClientManagementController();
m_exportUIController.reset(new ExportUIController(m_serversModel, m_containersModel, m_clientManagementModel, m_exportController, clientManagementController));
m_engine->rootContext()->setContextProperty("ExportController", m_exportUIController.get());
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
m_installUIController.reset(new InstallUIController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_installController, m_apiConfigsCoreController, clientManagementController));
m_engine->rootContext()->setContextProperty("InstallController", m_installUIController.get());
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
connect(m_installUIController.get(), &InstallUIController::currentContainerUpdated, m_connectionController.get(),
&ConnectionUIController::onCurrentContainerUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settingsController));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsUIController.reset(
new SettingsUIController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settingsController));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsUIController.get());
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_siteSplitUIController.reset(new SiteSplitUIController(m_splitTunnelingController, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_siteSplitUIController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsUIController.reset(new AllowedDnsUIController(m_dnsController, m_allowedDnsModel));
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsUIController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
m_appSplitUIController.reset(new AppSplitUIController(m_splitTunnelingController, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitUIController.get());
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_systemController.reset(new SystemController());
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_apiSettingsCoreController = QSharedPointer<ApiSettingsController>::create(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings);
m_apiConfigsCoreController = QSharedPointer<ApiConfigsController>::create(m_serversModel, m_apiServicesModel, m_settings);
m_apiPremV1MigrationCoreController = QSharedPointer<ApiPremV1MigrationController>::create(m_serversModel, m_settings, this);
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiSettingsUIController.reset(new ApiSettingsUIController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_apiSettingsCoreController));
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsUIController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
m_apiConfigUIController.reset(new ApiConfigUIController(m_serversModel, m_apiServicesModel, m_apiConfigsCoreController));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigUIController.get());
m_apiPremV1MigrationUIController.reset(new ApiPremV1MigrationUIController(m_serversModel, m_apiPremV1MigrationCoreController));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationUIController.get());
setupControllerSignalConnections();
}
void CoreController::setupControllerSignalConnections()
{
auto clientManagementController = m_clientManagementUIController->getClientManagementController();
connect(m_exportController.data(), &ExportController::clientAppendRequested,
clientManagementController.data(),
[clientManagementController](const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController) {
QList<ClientInfo> clientsList;
ErrorCode result = clientManagementController->appendClient(container, credentials, containerConfig,
clientName, serverController, clientsList);
emit clientManagementController->clientAppendCompleted(result);
});
connect(m_exportController.data(), &ExportController::nativeConfigClientAppendRequested,
clientManagementController.data(),
[clientManagementController](const QSharedPointer<ProtocolConfig> &protocolConfig, const QString &clientName,
const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController) {
QList<ClientInfo> clientsList;
auto nonConstProtocolConfig = QSharedPointer<ProtocolConfig>(protocolConfig);
ErrorCode result = clientManagementController->appendClient(nonConstProtocolConfig, clientName, container,
credentials, serverController, clientsList);
emit clientManagementController->nativeConfigClientAppendCompleted(result);
});
connect(clientManagementController.data(), &ClientManagementController::clientAppendCompleted,
m_exportController.data(), &ExportController::onClientAppendCompleted);
connect(clientManagementController.data(), &ClientManagementController::nativeConfigClientAppendCompleted,
m_exportController.data(), &ExportController::onNativeConfigClientAppendCompleted);
connect(m_installController.data(), &InstallController::clientAppendRequested,
clientManagementController.data(),
[clientManagementController](const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController) {
QList<ClientInfo> clientsList;
clientManagementController->appendClient(container, credentials, containerConfig,
clientName, serverController, clientsList);
});
}
void CoreController::initAndroidController()
@@ -207,7 +274,7 @@ void CoreController::initAppleController()
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
emit m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup();
emit m_settingsController->importBackupFromOutside(filePath);
emit m_settingsUIController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
@@ -228,12 +295,14 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler();
}
void CoreController::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -241,14 +310,11 @@ void CoreController::initNotificationHandler()
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));
static_cast<void (ConnectionUIController::*)()>(&ConnectionUIController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
&ConnectionUIController::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
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -263,7 +329,6 @@ void CoreController::updateTranslator(const QLocale &locale)
availableTranslations << it.next();
}
// This code allow to load translation for the language only, without country code
const QString lang = locale.name().split("_").first();
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
@@ -285,17 +350,16 @@ void CoreController::updateTranslator(const QLocale &locale)
m_engine->retranslate();
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()
{
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
connect(m_connectionController.get(), &ConnectionUIController::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(),
connect(m_apiConfigUIController.get(), &ApiConfigUIController::errorOccurred, m_pageController.get(),
qOverload<ErrorCode>(&PageController::showErrorMessage));
}
@@ -317,11 +381,6 @@ 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();
}
@@ -333,42 +392,39 @@ void CoreController::initAdminConfigRevokedHandler()
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);
connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionUIController::onTranslationsUpdated);
}
void CoreController::initAutoConnectHandler()
{
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
if (m_settingsUIController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
}
void CoreController::initAmneziaDnsToggledHandler()
{
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
connect(m_settingsUIController.get(), &SettingsUIController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
}
void CoreController::initPrepareConfigHandler()
{
connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
connect(m_connectionController.get(), &ConnectionUIController::prepareConfig, this, [this]() {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
if (!m_apiConfigsController->isConfigValid()) {
if (!m_apiConfigUIController->isConfigValid()) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
if (!m_installController->isConfigValid()) {
if (!m_installController->isConfigValid(m_serversModel->getProcessedServerCredentials())) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
@@ -377,9 +433,28 @@ void CoreController::initPrepareConfigHandler()
});
}
void CoreController::initImportPremiumV2VpnKeyHandler()
{
connect(m_apiPremV1MigrationUIController.get(), &ApiPremV1MigrationUIController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
m_importController->extractConfigFromData(vpnKey);
m_importController->importConfig();
emit m_apiPremV1MigrationUIController->migrationFinished();
});
}
void CoreController::initShowMigrationDrawerHandler()
{
QTimer::singleShot(1000, this, [this]() {
if (m_apiPremV1MigrationUIController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationUIController->hasConfigsToMigration()) {
m_apiPremV1MigrationUIController->showMigrationDrawer();
}
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
connect(m_settingsUIController.get(), &SettingsUIController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
&VpnConnection::onKillSwitchModeChanged);
}
@@ -387,22 +462,3 @@ QSharedPointer<PageController> CoreController::pageController() const
{
return m_pageController;
}
void CoreController::openConnectionByIndex(int serverIndex)
{
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
m_serversModel->setDefaultServerIndex(serverIndex);
}
m_connectionController->toggleConnection();
}
void CoreController::importConfigFromData(const QString &data)
{
if (!m_importController)
return;
if (m_importController->extractConfigFromData(data)) {
m_importController->importConfig();
}
}

View File

@@ -5,24 +5,31 @@
#include <QQmlContext>
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.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/api/apiConfigUIController.h"
#include "ui/controllers/api/apiSettingsUIController.h"
#include "ui/controllers/api/apiPremV1MigrationUIController.h"
#include "core/controllers/api/apiConfigController.h"
#include "core/controllers/api/apiSettingsController.h"
#include "core/controllers/api/apiPremV1MigrationController.h"
#include "core/controllers/selfhosted/clientManagementController.h"
#include "ui/controllers/appSplitUIController.h"
#include "ui/controllers/allowedDnsUIController.h"
#include "ui/controllers/connectionUIController.h"
#include "core/controllers/selfhosted/exportController.h"
#include "core/controllers/selfhosted/installController.h"
#include "ui/controllers/selfhosted/exportUIController.h"
#include "ui/controllers/focusController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/selfhosted/installUIController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/settingsUIController.h"
#include "ui/controllers/siteSplitUIController.h"
#include "ui/controllers/systemController.h"
#include "core/utils/fileUtils.h"
#include "core/controllers/settingsController.h"
#include "core/controllers/dnsController.h"
#include "core/controllers/connectionController.h"
#include "core/controllers/splitTunnelingController.h"
#include "ui/models/allowed_dns_model.h"
#include "ui/models/containers_model.h"
@@ -36,7 +43,8 @@
#include "ui/models/api/apiDevicesModel.h"
#include "ui/models/api/apiServicesModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#include "ui/models/clientManagementModel.h"
#include "ui/models/selfhosted/clientManagementModel.h"
#include "ui/controllers/selfhosted/clientManagementUIController.h"
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
@@ -47,9 +55,8 @@
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#endif
@@ -64,19 +71,17 @@ public:
QSharedPointer<PageController> pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initModels();
void initControllers();
void initCoreControllers();
void initUIControllers();
void initSignalHandlers();
void initAndroidController();
void initAppleController();
void initSignalHandlers();
void setupControllerSignalConnections();
void initNotificationHandler();
@@ -92,34 +97,46 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<ConnectionUIController> m_connectionController;
QScopedPointer<FocusController> m_focusController;
QSharedPointer<PageController> m_pageController; // TODO
QScopedPointer<InstallController> m_installController;
QSharedPointer<PageController> m_pageController;
QSharedPointer<ExportController> m_exportController;
QSharedPointer<InstallController> m_installController;
QScopedPointer<ExportUIController> m_exportUIController;
QScopedPointer<InstallUIController> m_installUIController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SettingsUIController> m_settingsUIController;
QScopedPointer<SiteSplitUIController> m_siteSplitUIController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QScopedPointer<AllowedDnsController> m_allowedDnsController;
QScopedPointer<AppSplitUIController> m_appSplitUIController;
QScopedPointer<AllowedDnsUIController> m_allowedDnsUIController;
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ApiSettingsController> m_apiSettingsCoreController;
QSharedPointer<ApiConfigsController> m_apiConfigsCoreController;
QSharedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationCoreController;
QScopedPointer<ApiSettingsUIController> m_apiSettingsUIController;
QScopedPointer<ApiConfigUIController> m_apiConfigUIController;
QScopedPointer<ApiPremV1MigrationUIController> m_apiPremV1MigrationUIController;
QSharedPointer<SettingsController> m_settingsController;
QSharedPointer<DnsController> m_dnsController;
QSharedPointer<SplitTunnelingController> m_splitTunnelingController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
@@ -127,27 +144,27 @@ private:
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;
QSharedPointer<ClientManagementUIController> m_clientManagementUIController;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
QSharedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QSharedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QSharedPointer<CloakConfigModel> m_cloakConfigModel;
QSharedPointer<XrayConfigModel> m_xrayConfigModel;
QSharedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QSharedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
QSharedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
QSharedPointer<SftpConfigModel> m_sftpConfigModel;
QSharedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
};
#endif // CORECONTROLLER_H

View File

@@ -0,0 +1,66 @@
#include "dnsController.h"
#include "core/networkUtilities.h"
DnsController::DnsController(std::shared_ptr<Settings> settings, QObject *parent)
: QObject(parent), m_settings(settings)
{
}
bool DnsController::addDns(const QString &ip)
{
if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) {
return false;
}
QStringList currentDnsServers = m_settings->allowedDnsServers();
if (currentDnsServers.contains(ip)) {
return false;
}
currentDnsServers.append(ip);
m_settings->setAllowedDnsServers(currentDnsServers);
emit dnsAdded(ip);
return true;
}
bool DnsController::addDnsList(const QStringList &dnsServers, bool replaceExisting)
{
QStringList currentDnsServers;
if (!replaceExisting) {
currentDnsServers = m_settings->allowedDnsServers();
}
for (const QString &ip : dnsServers) {
if (!currentDnsServers.contains(ip)) {
currentDnsServers.append(ip);
}
}
m_settings->setAllowedDnsServers(currentDnsServers);
emit dnsListAdded(dnsServers);
return true;
}
bool DnsController::removeDns(const QString &ip)
{
QStringList currentDnsServers = m_settings->allowedDnsServers();
if (!currentDnsServers.contains(ip)) {
return false;
}
currentDnsServers.removeAll(ip);
m_settings->setAllowedDnsServers(currentDnsServers);
emit dnsRemoved(ip);
return true;
}
QStringList DnsController::getAllowedDnsServers() const
{
return m_settings->allowedDnsServers();
}

View File

@@ -0,0 +1,32 @@
#ifndef DNSCONTROLLER_H
#define DNSCONTROLLER_H
#include <QObject>
#include <QStringList>
#include <QSharedPointer>
#include "settings.h"
class DnsController : public QObject
{
Q_OBJECT
public:
explicit DnsController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
// DNS management
bool addDns(const QString &ip);
bool addDnsList(const QStringList &dnsServers, bool replaceExisting = false);
bool removeDns(const QString &ip);
QStringList getAllowedDnsServers() const;
signals:
void dnsAdded(const QString &ip);
void dnsListAdded(const QStringList &dnsServers);
void dnsRemoved(const QString &ip);
private:
std::shared_ptr<Settings> m_settings;
};
#endif // DNSCONTROLLER_H

View File

@@ -1,665 +0,0 @@
#include "gatewayController.h"
#include <algorithm>
#include <functional>
#include <random>
#include <QCryptographicHash>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPromise>
#include <QUrl>
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amnezia_application.h"
#include "core/api/apiUtils.h"
#include "core/networkUtilities.h"
#include "utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/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.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
constexpr int httpStatusCodeNotFound = 404;
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
{
}
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
{
EncryptedRequestData encRequestData;
encRequestData.errorCode = ErrorCode::NoError;
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
});
}
}
#endif
QSimpleCrypto::QBlockCipher blockCipher;
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
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());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
EVP_PKEY *publicKey = nullptr;
try {
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
Utils::logException();
qCritical() << "error when encrypting the request body";
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
}
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
QNetworkReply::NetworkError replyError, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
DecryptionResult result;
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
return result;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
return encRequestData.errorCode;
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(QEventLoop::ExcludeUserInputEvents);
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
};
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString();
replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
}
auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
if (errorCode) {
return errorCode;
}
if (!decryptionResult.isDecryptionSuccessful) {
qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
}
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
}
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
{
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
promise->start();
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
promise->finish();
return promise->future();
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
decryptionResult.decryptedBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
if (!decryptionResult.isDecryptionSuccessful) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
return;
}
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
promise->finish();
};
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
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");
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
GatewayController::DecryptionResult result;
result.decryptedBody = decryptedBody;
result.isDecryptionSuccessful = isDecryptionSuccessful;
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
});
});
});
} else {
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
}
});
return promise->future();
}
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
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");
}
}
for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll();
reply->deleteLater();
EVP_PKEY *privateKey = nullptr;
QByteArray responseBody;
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
continue;
}
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
} else {
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << replyError;
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
}
}
return {};
}
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
bool isDecryptionSuccessful)
{
const QByteArray &responseBody = decryptedResponseBody;
int httpStatus = -1;
if (isDecryptionSuccessful) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatus = jsonObj.value("http_status").toInt(-1);
}
} else {
qDebug() << "failed to decrypt the data";
return true;
}
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred";
qDebug() << replyError;
return true;
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (httpStatus == httpStatusCodeNotFound) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << replyError;
return true;
}
} else if (httpStatus == httpStatusCodeNotImplemented) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << replyError;
return true;
}
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
return true;
}
return false;
}
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QByteArray responseBody;
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
QEventLoop wait;
QList<QSslError> sslErrors;
qDebug() << "go to the next proxy endpoint";
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(QEventLoop::ExcludeUserInputEvents);
auto result = replyProcessingFunction(reply, sslErrors);
reply->deleteLater();
return result;
};
if (m_proxyUrl.isEmpty()) {
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
for (const QString &proxyUrl : proxyUrls) {
request.setUrl(proxyUrl + "lmbd-health");
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) {
reply->deleteLater();
m_proxyUrl = proxyUrl;
if (!m_proxyUrl.isEmpty()) {
break;
}
} else {
reply->deleteLater();
}
}
}
if (!m_proxyUrl.isEmpty()) {
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
return;
}
}
for (const QString &proxyUrl : proxyUrls) {
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
m_proxyUrl = proxyUrl;
break;
}
}
}
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete)
{
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
onComplete({});
return;
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray encrypted = reply->readAll();
reply->deleteLater();
QByteArray responseBody;
try {
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encrypted);
QSimpleCrypto::QBlockCipher cipher;
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
responseBody = encrypted;
}
} catch (...) {
Utils::logException();
qCritical() << "error decrypting payload";
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
return;
}
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const QJsonValue &endpoint : endpointsArray)
endpoints.push_back(endpoint.toString());
QStringList shuffled = endpoints;
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(shuffled.begin(), shuffled.end(), generator);
onComplete(shuffled);
return;
}
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
std::function<void(const QString &)> onComplete)
{
if (currentProxyIndex >= proxyUrls.size()) {
onComplete("");
return;
}
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
// *(state->sslErrors) = e;
// });
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError) {
m_proxyUrl = proxyUrls[currentProxyIndex];
onComplete(m_proxyUrl);
return;
}
qDebug() << "go to the next proxy endpoint";
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
return;
}
QNetworkRequest request = encRequestData.request;
request.setUrl(endpoint.arg(proxyUrl));
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
httpStatusCode);
});
}

View File

@@ -1,70 +0,0 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QNetworkReply>
#include <QObject>
#include <QPair>
#include <QPromise>
#include <QSharedPointer>
#include "core/defs.h"
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
class GatewayController : public QObject
{
Q_OBJECT
public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private:
struct EncryptedRequestData
{
QNetworkRequest request;
QByteArray requestBody;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode;
};
struct DecryptionResult
{
QByteArray decryptedBody;
bool isDecryptionSuccessful;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete);
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
inline static QString m_proxyUrl;
};
#endif // GATEWAYCONTROLLER_H

View File

@@ -0,0 +1,825 @@
#include "clientManagementController.h"
#include <QJsonDocument>
#include <QJsonObject>
#include "core/controllers/selfhosted/serverController.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/models/protocols/cloakProtocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProtocolConfig.h"
#include "core/models/protocols/torWebsiteProtocolConfig.h"
#include "core/models/protocols/protocolConfig.h"
#include "core/models/clientInfo.h"
#include <variant>
#include "settings.h"
#include "logger.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
using namespace amnezia::config_key;
namespace
{
Logger logger("ClientManagementController");
namespace configKey
{
constexpr char clientId[] = "clientId";
constexpr char clientName[] = "clientName";
constexpr char container[] = "container";
constexpr char userData[] = "userData";
constexpr char creationDate[] = "creationDate";
constexpr char latestHandshake[] = "latestHandshake";
constexpr char dataReceived[] = "dataReceived";
constexpr char dataSent[] = "dataSent";
constexpr char allowedIps[] = "allowedIps";
}
}
ClientManagementController::ClientManagementController(std::shared_ptr<Settings> settings, QObject *parent)
: QObject(parent), m_settings(settings)
{
}
QString ClientManagementController::getClientsTableFilePath(const DockerContainer container)
{
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
return clientsTableFile;
}
void ClientManagementController::migration(const QByteArray &clientsTableString, QList<ClientInfo> &clientsList)
{
QJsonObject clientsTableObj = QJsonDocument::fromJson(clientsTableString).object();
for (auto &clientId : clientsTableObj.keys()) {
ClientInfo client;
client.clientId = clientId;
client.clientName = clientsTableObj.value(clientId).toObject().value(configKey::clientName).toString();
client.creationDate = QDateTime::currentDateTime();
clientsList.append(client);
}
}
ErrorCode ClientManagementController::updateClientsData(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList)
{
clientsList.clear();
ErrorCode error = ErrorCode::NoError;
QString clientsTableFile = getClientsTableFilePath(container);
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the clientsTable file from the server";
return error;
}
QJsonArray clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (clientsTable.isEmpty()) {
migration(clientsTableString, clientsList);
const QByteArray newClientsTableString = QJsonDocument(clientsToJsonArray(clientsList)).toJson();
error = serverController->uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
} else {
clientsList = clientsFromJsonArray(clientsTable);
}
int count = 0;
switch (container) {
case DockerContainer::OpenVpn:
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak:
error = getOpenVpnClients(container, credentials, serverController, count, clientsList);
break;
case DockerContainer::WireGuard:
case DockerContainer::Awg:
error = getWireGuardClients(container, credentials, serverController, count, clientsList);
break;
case DockerContainer::Xray:
error = getXrayClients(container, credentials, serverController, count, clientsList);
break;
default:
error = ErrorCode::NoError;
break;
}
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get clients for container" << ContainerProps::containerTypeToString(container);
return error;
}
emit clientsDataUpdated(clientsList);
return ErrorCode::NoError;
}
// UI-facing methods - create ServerController internally
ErrorCode ClientManagementController::updateClientsData(const DockerContainer container, const ServerCredentials &credentials)
{
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QList<ClientInfo> clientsList;
return updateClientsData(container, credentials, serverController, clientsList);
}
ErrorCode ClientManagementController::appendClient(const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList)
{
Proto protocol;
switch (container) {
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak:
protocol = Proto::OpenVpn;
break;
case DockerContainer::OpenVpn:
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Xray:
protocol = ContainerProps::defaultProtocol(container);
break;
default:
return ErrorCode::NoError;
}
QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = containerConfig.protocolConfigs.value(protocolName);
return appendClient(protocolConfig, clientName, container, credentials, serverController, clientsList);
}
ErrorCode ClientManagementController::appendClient(QSharedPointer<ProtocolConfig> &protocolConfig, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList)
{
QString clientId;
if (container == DockerContainer::Xray) {
if (!protocolConfig) {
return ErrorCode::InternalError;
}
QJsonObject protocolConfigJson = protocolConfig->toJson();
if (!protocolConfigJson.contains("outbounds")) {
return ErrorCode::InternalError;
}
QJsonArray outbounds = protocolConfigJson.value("outbounds").toArray();
if (outbounds.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject outbound = outbounds[0].toObject();
if (!outbound.contains("settings")) {
return ErrorCode::InternalError;
}
QJsonObject settings = outbound["settings"].toObject();
if (!settings.contains("vnext")) {
return ErrorCode::InternalError;
}
QJsonArray vnext = settings["vnext"].toArray();
if (vnext.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject vnextObj = vnext[0].toObject();
if (!vnextObj.contains("users")) {
return ErrorCode::InternalError;
}
QJsonArray users = vnextObj["users"].toArray();
if (users.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject user = users[0].toObject();
if (!user.contains("id")) {
return ErrorCode::InternalError;
}
clientId = user["id"].toString();
} else {
if (!protocolConfig) {
return ErrorCode::InternalError;
}
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([&clientId](const auto &config) -> void {
clientId = config->clientProtocolConfig.clientId;
}, variant);
}
return appendClient(clientId, clientName, container, credentials, serverController, clientsList);
}
ErrorCode ClientManagementController::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList)
{
ErrorCode error = ErrorCode::NoError;
QList<ClientInfo> currentClients;
error = updateClientsData(container, credentials, serverController, currentClients);
if (error != ErrorCode::NoError) {
return error;
}
for (int i = 0; i < currentClients.size(); ++i) {
if (currentClients[i].clientId == clientId) {
return renameClient(i, clientName, container, credentials, serverController, currentClients, true);
}
}
ClientInfo newClient(clientId, clientName);
newClient.container = container;
currentClients.append(newClient);
const QByteArray clientsTableString = QJsonDocument(clientsToJsonArray(currentClients)).toJson();
QString clientsTableFile = getClientsTableFilePath(container);
error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
clientsList = currentClients;
emit clientAdded(clientId, clientName);
return ErrorCode::NoError;
}
ErrorCode ClientManagementController::renameClient(const int row, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, bool addTimeStamp)
{
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QList<ClientInfo> clientsList;
ErrorCode errorCode = updateClientsData(container, credentials, serverController, clientsList);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return renameClient(row, clientName, container, credentials, serverController, clientsList, addTimeStamp);
}
ErrorCode ClientManagementController::renameClient(const int row, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList, bool addTimeStamp)
{
if (row < 0 || row >= clientsList.size()) {
return ErrorCode::InternalError;
}
clientsList[row].clientName = clientName;
if (addTimeStamp) {
clientsList[row].creationDate = QDateTime::currentDateTime();
}
const QByteArray clientsTableString = QJsonDocument(clientsToJsonArray(clientsList)).toJson();
QString clientsTableFile = getClientsTableFilePath(container);
ErrorCode error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
emit clientRenamed(row, clientName);
return ErrorCode::NoError;
}
ErrorCode ClientManagementController::getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList)
{
ErrorCode error = ErrorCode::NoError;
const QString wireGuardConfigFile = QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error);
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, clientsList)) {
ClientInfo client(wireguardKey, QString("Client %1").arg(count));
client.container = container;
clientsList.append(client);
count++;
}
}
return error;
}
ErrorCode ClientManagementController::getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList)
{
ErrorCode error = ErrorCode::NoError;
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = serverController->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("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) {
logger.error() << "Invalid xray server config structure";
return ErrorCode::InternalError;
}
const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject();
if (!inbound.contains("settings")) {
logger.error() << "Missing settings in xray inbound config";
return ErrorCode::InternalError;
}
const QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
logger.error() << "Missing clients in xray settings config";
return ErrorCode::InternalError;
}
const QJsonArray clients = settings["clients"].toArray();
for (const auto &clientValue : clients) {
const QJsonObject clientObj = clientValue.toObject();
if (!clientObj.contains("id")) {
logger.error() << "Missing id in xray client config";
continue;
}
QString clientId = clientObj["id"].toString();
QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error);
xrayDefaultUuid.replace("\n", "");
if (!isClientExists(clientId, clientsList) && clientId != xrayDefaultUuid) {
ClientInfo client(clientId, QString("Client %1").arg(count));
client.container = container;
clientsList.append(client);
count++;
}
}
return error;
}
ErrorCode ClientManagementController::getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList)
{
Q_UNUSED(container);
Q_UNUSED(credentials);
Q_UNUSED(serverController);
count = clientsList.size();
return ErrorCode::NoError;
}
ErrorCode ClientManagementController::wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data)
{
if (container != DockerContainer::WireGuard && container != DockerContainer::Awg) {
return ErrorCode::NoError;
}
ErrorCode error = ErrorCode::NoError;
QString stdOut;
std::function<ErrorCode(const QString &, libssh::Client &)> cbReadStdOut = [&](const QString &data, libssh::Client &){
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1'").arg("wg show all");
QString script = serverController->replaceVars(command, serverController->generateVarsForContainer(credentials, container));
error = serverController->runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to execute wg show command";
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 ClientManagementController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController)
{
QList<ClientInfo> clientsList;
ErrorCode errorCode = updateClientsData(container, credentials, serverController, clientsList);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return revokeOpenVpn(row, container, credentials, serverIndex, serverController, clientsList);
}
ErrorCode ClientManagementController::revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController)
{
QList<ClientInfo> clientsList;
ErrorCode errorCode = updateClientsData(container, credentials, serverController, clientsList);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return revokeWireGuard(row, container, credentials, serverController, clientsList);
}
ErrorCode ClientManagementController::revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController)
{
QList<ClientInfo> clientsList;
ErrorCode errorCode = updateClientsData(container, credentials, serverController, clientsList);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return revokeXray(row, container, credentials, serverController, clientsList);
}
ErrorCode ClientManagementController::revokeClient(const int row, const DockerContainer container,
const ServerCredentials &credentials, const int serverIndex)
{
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QList<ClientInfo> clientsList;
ErrorCode errorCode = updateClientsData(container, credentials, serverController, clientsList);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
return revokeClient(row, container, credentials, serverIndex, serverController, clientsList);
}
ErrorCode ClientManagementController::revokeClient(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList)
{
if (row < 0 || row >= clientsList.size()) {
return ErrorCode::InternalError;
}
QString clientId = clientsList[row].clientId;
ErrorCode errorCode = ErrorCode::NoError;
switch(container) {
case DockerContainer::OpenVpn:
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController, clientsList);
break;
}
case DockerContainer::WireGuard:
case DockerContainer::Awg: {
errorCode = revokeWireGuard(row, container, credentials, serverController, clientsList);
break;
}
case DockerContainer::Xray: {
errorCode = revokeXray(row, container, credentials, serverController, clientsList);
break;
}
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
}
if (errorCode == ErrorCode::NoError) {
emit clientRevoked(row);
}
return errorCode;
}
ErrorCode ClientManagementController::revokeClient(const ContainerConfig &containerConfig, const DockerContainer container,
const ServerCredentials &credentials, const int serverIndex,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList)
{
Proto protocol;
switch(container) {
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak: {
protocol = Proto::OpenVpn;
break;
}
case DockerContainer::OpenVpn:
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Xray: {
protocol = ContainerProps::defaultProtocol(container);
break;
}
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
}
auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig);
QString clientId;
if (container == DockerContainer::Xray) {
if (!protocolConfig) {
return ErrorCode::InternalError;
}
QJsonObject protocolConfigJson = protocolConfig->toJson();
if (!protocolConfigJson.contains("outbounds")) {
return ErrorCode::InternalError;
}
QJsonArray outbounds = protocolConfigJson.value("outbounds").toArray();
if (outbounds.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject outbound = outbounds[0].toObject();
if (!outbound.contains("settings")) {
return ErrorCode::InternalError;
}
QJsonObject settings = outbound["settings"].toObject();
if (!settings.contains("vnext")) {
return ErrorCode::InternalError;
}
QJsonArray vnext = settings["vnext"].toArray();
if (vnext.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject vnextObj = vnext[0].toObject();
if (!vnextObj.contains("users")) {
return ErrorCode::InternalError;
}
QJsonArray users = vnextObj["users"].toArray();
if (users.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject user = users[0].toObject();
if (!user.contains("id")) {
return ErrorCode::InternalError;
}
clientId = user["id"].toString();
} else {
if (!protocolConfig) {
return ErrorCode::InternalError;
}
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([&clientId](const auto &config) -> void {
clientId = config->clientProtocolConfig.clientId;
}, variant);
}
for (int i = 0; i < clientsList.size(); i++) {
if (clientsList[i].clientId == clientId) {
return revokeClient(i, container, credentials, serverIndex, serverController, clientsList);
}
}
return ErrorCode::InternalError;
}
ErrorCode ClientManagementController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList)
{
if (row < 0 || row >= clientsList.size()) {
return ErrorCode::InternalError;
}
QString clientId = clientsList[row].clientId;
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
};
const QString revokeClientScript = "sudo docker exec -i $CONTAINER_NAME bash -c './easyrsa --batch revoke %1'";
QString script = serverController->replaceVars(revokeClientScript.arg(clientId),
serverController->generateVarsForContainer(credentials, container));
error = serverController->runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
return error;
}
clientsList.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsToJsonArray(clientsList)).toJson();
QString clientsTableFile = getClientsTableFilePath(container);
error = serverController->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 ClientManagementController::revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList)
{
if (row < 0 || row >= clientsList.size()) {
return ErrorCode::InternalError;
}
QString clientId = clientsList[row].clientId;
ErrorCode error = ErrorCode::NoError;
const QString wireGuardConfigFile = QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error);
if (error != ErrorCode::NoError) {
return error;
}
QStringList configLines = wireguardConfigString.split("\n");
QStringList newConfigLines;
bool skipPeer = false;
for (const QString &line : configLines) {
if (line.startsWith("[Peer]")) {
skipPeer = false;
}
if (line.contains("PublicKey") && line.contains(clientId)) {
skipPeer = true;
continue;
}
if (!skipPeer) {
newConfigLines.append(line);
}
}
QString newConfig = newConfigLines.join("\n");
error = serverController->uploadTextFileToContainer(container, credentials, newConfig.toUtf8(), wireGuardConfigFile);
if (error != ErrorCode::NoError) {
return error;
}
clientsList.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsToJsonArray(clientsList)).toJson();
QString clientsTableFile = getClientsTableFilePath(container);
error = serverController->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 ClientManagementController::revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList)
{
if (row < 0 || row >= clientsList.size()) {
return ErrorCode::InternalError;
}
QString clientId = clientsList[row].clientId;
ErrorCode error = ErrorCode::NoError;
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error);
if (error != ErrorCode::NoError) {
return error;
}
QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
if (serverConfig.isNull()) {
return ErrorCode::InternalError;
}
QJsonObject configObj = serverConfig.object();
QJsonArray inbounds = configObj["inbounds"].toArray();
for (int i = 0; i < inbounds.size(); i++) {
QJsonObject inbound = inbounds[i].toObject();
QJsonObject settings = inbound["settings"].toObject();
QJsonArray clients = settings["clients"].toArray();
for (int j = 0; j < clients.size(); j++) {
QJsonObject clientObj = clients[j].toObject();
if (clientObj["id"].toString() == clientId) {
clients.removeAt(j);
settings["clients"] = clients;
inbound["settings"] = settings;
inbounds[i] = inbound;
break;
}
}
}
configObj["inbounds"] = inbounds;
QJsonDocument newServerConfig(configObj);
error = serverController->uploadTextFileToContainer(container, credentials, newServerConfig.toJson(), serverConfigPath);
if (error != ErrorCode::NoError) {
return error;
}
clientsList.removeAt(row);
const QByteArray clientsTableString = QJsonDocument(clientsToJsonArray(clientsList)).toJson();
QString clientsTableFile = getClientsTableFilePath(container);
error = serverController->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;
}
QList<ClientInfo> ClientManagementController::clientsFromJsonArray(const QJsonArray &jsonArray)
{
QList<ClientInfo> clientsList;
for (const auto &value : jsonArray) {
clientsList.append(ClientInfo(value.toObject()));
}
return clientsList;
}
QJsonArray ClientManagementController::clientsToJsonArray(const QList<ClientInfo> &clientsList)
{
QJsonArray jsonArray;
for (const auto &client : clientsList) {
jsonArray.append(client.toJson());
}
return jsonArray;
}
bool ClientManagementController::isClientExists(const QString &clientId, const QList<ClientInfo> &clientsList)
{
for (const auto &client : clientsList) {
if (client.clientId == clientId) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,133 @@
#ifndef CLIENTMANAGEMENTCONTROLLER_H
#define CLIENTMANAGEMENTCONTROLLER_H
#include <QObject>
#include <QSharedPointer>
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
#include <vector>
#include "core/defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "core/models/protocols/protocolConfig.h"
#include "core/models/clientInfo.h"
class ServerController;
class Settings;
using namespace amnezia;
struct WgShowData
{
QString clientId;
QString latestHandshake;
QString dataReceived;
QString dataSent;
QString allowedIps;
};
class ClientManagementController : public QObject
{
Q_OBJECT
public:
explicit ClientManagementController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
std::shared_ptr<Settings> getSettings() const { return m_settings; }
// UI-facing methods (no ServerController parameter - created internally)
ErrorCode updateClientsData(const DockerContainer container, const ServerCredentials &credentials);
ErrorCode renameClient(const int row, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, bool addTimeStamp = false);
ErrorCode revokeClient(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex);
// Core methods using ClientInfo model
ErrorCode updateClientsData(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList);
ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList);
ErrorCode appendClient(QSharedPointer<ProtocolConfig> &protocolConfig, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList);
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList);
ErrorCode renameClient(const int row, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList, bool addTimeStamp = false);
ErrorCode revokeClient(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList);
ErrorCode revokeClient(const ContainerConfig &containerConfig, const DockerContainer container,
const ServerCredentials &credentials, const int serverIndex,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList);
// WireGuard specific operations
ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data);
signals:
void adminConfigRevoked(const DockerContainer container);
void clientsDataUpdated(const QList<ClientInfo> &clientsList);
void clientAdded(const QString &clientId, const QString &clientName);
void clientRenamed(const int row, const QString &newName);
void clientRevoked(const int row);
void clientAppendCompleted(ErrorCode errorCode);
void nativeConfigClientAppendCompleted(ErrorCode errorCode);
private:
// Protocol-specific client management
ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList);
ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList);
ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
const QSharedPointer<ServerController> &serverController, int &count, QList<ClientInfo> &clientsList);
// Protocol-specific client revocation
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController,
QList<ClientInfo> &clientsList);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList);
ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, QList<ClientInfo> &clientsList);
// Internal wrappers to fetch clients list then delegate to detailed versions
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, const QSharedPointer<ServerController> &serverController);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController);
ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController);
// Helper methods
bool isClientExists(const QString &clientId, const QList<ClientInfo> &clientsList);
void migration(const QByteArray &clientsTableString, QList<ClientInfo> &clientsList);
QString getClientsTableFilePath(const DockerContainer container);
// JSON serialization for persistence only
static QList<ClientInfo> clientsFromJsonArray(const QJsonArray &jsonArray);
static QJsonArray clientsToJsonArray(const QList<ClientInfo> &clientsList);
std::shared_ptr<Settings> m_settings;
};
#endif // CLIENTMANAGEMENTCONTROLLER_H

View File

@@ -0,0 +1,353 @@
#include "exportController.h"
#include <QBuffer>
#include <QDataStream>
#include <QJsonDocument>
#include "core/controllers/vpnConfigurationController.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/controllers/selfhosted/clientManagementController.h"
#include "core/qrCodeUtils.h"
#include <QEventLoop>
#include <QTimer>
ExportController::ExportController(std::shared_ptr<Settings> settings,
QObject *parent)
: QObject(parent),
m_settings(settings),
m_lastClientAppendResult(ErrorCode::NoError),
m_lastNativeConfigAppendResult(ErrorCode::NoError),
m_waitingForClientAppend(false),
m_waitingForNativeConfigAppend(false)
{
}
ExportConfigResult ExportController::generateFullAccessConfig(const QSharedPointer<ServerConfig> &serverConfig)
{
ExportConfigResult result;
result.errorCode = ErrorCode::NoError;
// Create a copy of the ServerConfig and clean last_config from protocol configs
auto modifiedServerConfig = QSharedPointer<ServerConfig>::create(*serverConfig);
for (auto &containerConfig : modifiedServerConfig->containerConfigs) {
for (auto &protocolConfig : containerConfig.protocolConfigs) {
// Protocol configs will automatically exclude last_config when serialized to JSON for export
// No need to manually remove it here as the toJson() method handles this
}
}
QByteArray compressedConfig = QJsonDocument(modifiedServerConfig->toJson()).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
result.qrCodes = generateQrCodeSeries(compressedConfig);
return result;
}
ExportConfigResult ExportController::generateConnectionConfig(const QString &clientName,
const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QSharedPointer<ServerConfig> &serverConfig,
const QPair<QString, QString> &dnsSettings)
{
ExportConfigResult result;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
// Use the provided ContainerConfig directly
ContainerConfig modifiedContainerConfig = containerConfig;
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, modifiedContainerConfig);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
m_waitingForClientAppend = true;
emit clientAppendRequested(container, credentials, modifiedContainerConfig, clientName, serverController);
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(30000);
connect(this, &ExportController::onClientAppendCompleted, &loop, &QEventLoop::quit);
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.start();
loop.exec();
m_waitingForClientAppend = false;
result.errorCode = m_lastClientAppendResult;
if (result.errorCode != ErrorCode::NoError) {
return result;
}
// Create a modified ServerConfig for export with only the specific container
auto exportServerConfig = QSharedPointer<ServerConfig>::create(*serverConfig);
// Remove credentials (they are not needed in export)
exportServerConfig->containerConfigs.clear();
// Add only the specific container being exported
QString containerName = ContainerProps::containerToString(container);
exportServerConfig->containerConfigs.insert(containerName, modifiedContainerConfig);
exportServerConfig->defaultContainer = containerName;
exportServerConfig->dns1 = dnsSettings.first;
exportServerConfig->dns2 = dnsSettings.second;
QByteArray compressedConfig = QJsonDocument(exportServerConfig->toJson()).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
result.qrCodes = generateQrCodeSeries(compressedConfig);
return result;
}
ExportConfigResult ExportController::generateOpenVpnConfig(const QString &clientName,
const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
Proto protocol = Proto::OpenVpn;
if (container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) {
protocol = Proto::OpenVpn;
} else {
protocol = ContainerProps::defaultProtocol(container);
}
result.errorCode = generateNativeConfig(container, clientName, protocol, credentials,
containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes = generateQrCodeSeries(result.config.toUtf8());
return result;
}
ExportConfigResult ExportController::generateWireGuardConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
result.errorCode = generateNativeConfig(DockerContainer::WireGuard, clientName, Proto::WireGuard,
credentials, containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes << generateQrCode(result.config.toUtf8());
return result;
}
ExportConfigResult ExportController::generateAwgConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
result.errorCode = generateNativeConfig(DockerContainer::Awg, clientName, Proto::Awg,
credentials, containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.qrCodes << generateQrCode(result.config.toUtf8());
return result;
}
ExportConfigResult ExportController::generateShadowSocksConfig(const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
Proto protocol = Proto::ShadowSocks;
if (container == DockerContainer::Cloak) {
protocol = Proto::ShadowSocks;
} else {
protocol = ContainerProps::defaultProtocol(container);
}
result.errorCode = generateNativeConfig(container, "", protocol, credentials,
containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
result.nativeConfigString = QString("%1:%2@%3:%4")
.arg(nativeConfig.value("method").toString(),
nativeConfig.value("password").toString(),
nativeConfig.value("server").toString(),
nativeConfig.value("server_port").toString());
result.nativeConfigString = "ss://" + result.nativeConfigString.toUtf8().toBase64();
result.qrCodes << generateQrCode(result.nativeConfigString.toUtf8());
return result;
}
ExportConfigResult ExportController::generateCloakConfig(const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
result.errorCode = generateNativeConfig(DockerContainer::Cloak, "", Proto::Cloak,
credentials, containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
nativeConfig.remove(config_key::transport_proto);
nativeConfig.insert("ProxyMethod", "shadowsocks");
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
return result;
}
ExportConfigResult ExportController::generateXrayConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig)
{
ExportConfigResult result;
QJsonObject nativeConfig;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
result.errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray,
credentials, containerConfig, dnsSettings, isApiConfig, nativeConfig, serverController);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
for (const QString &line : std::as_const(lines)) {
result.config.append(line + "\n");
}
return result;
}
ErrorCode ExportController::generateNativeConfig(const DockerContainer container, const QString &clientName,
const Proto &protocol, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QPair<QString, QString> &dnsSettings,
bool isApiConfig, QJsonObject &jsonNativeConfig,
const QSharedPointer<ServerController> &serverController)
{
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
// Use the provided ContainerConfig directly
ContainerConfig modifiedContainerConfig = containerConfig;
QString protocolConfigString;
ErrorCode errorCode = vpnConfigurationController.createProtocolConfigString(isApiConfig, dnsSettings, credentials,
container, modifiedContainerConfig,
protocol, protocolConfigString);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) {
QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = modifiedContainerConfig.protocolConfigs.value(protocolName);
m_waitingForNativeConfigAppend = true;
emit nativeConfigClientAppendRequested(protocolConfig, clientName, container, credentials, serverController);
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(30000);
connect(this, &ExportController::onNativeConfigClientAppendCompleted, &loop, &QEventLoop::quit);
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.start();
loop.exec();
m_waitingForNativeConfigAppend = false;
errorCode = m_lastNativeConfigAppendResult;
}
return errorCode;
}
void ExportController::onClientAppendCompleted(ErrorCode errorCode)
{
if (m_waitingForClientAppend) {
m_lastClientAppendResult = errorCode;
}
}
void ExportController::onNativeConfigClientAppendCompleted(ErrorCode errorCode)
{
if (m_waitingForNativeConfigAppend) {
m_lastNativeConfigAppendResult = errorCode;
}
}
QList<QString> ExportController::generateQrCodeSeries(const QByteArray &data)
{
return qrCodeUtils::generateQrCodeImageSeries(data);
}
QString ExportController::generateQrCode(const QByteArray &data)
{
auto qr = qrCodeUtils::generateQrCode(data);
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
}

View File

@@ -0,0 +1,117 @@
#ifndef EXPORT_CORE_CONTROLLER_H
#define EXPORT_CORE_CONTROLLER_H
#include <QObject>
#include <QSharedPointer>
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
#include <QString>
#include "core/defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "core/models/protocols/protocolConfig.h"
#include "core/models/servers/serverConfig.h"
class ServerController;
class Settings;
using namespace amnezia;
struct ExportConfigResult
{
QString config;
QString nativeConfigString;
QList<QString> qrCodes;
ErrorCode errorCode;
};
class ExportController : public QObject
{
Q_OBJECT
public:
explicit ExportController(std::shared_ptr<Settings> settings,
QObject *parent = nullptr);
signals:
void clientAppendRequested(const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController);
void nativeConfigClientAppendRequested(const QSharedPointer<ProtocolConfig> &protocolConfig, const QString &clientName,
const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController);
void configGenerated(const ExportConfigResult &result);
void clientManagementError(ErrorCode errorCode);
public slots:
void onClientAppendCompleted(ErrorCode errorCode);
void onNativeConfigClientAppendCompleted(ErrorCode errorCode);
ExportConfigResult generateFullAccessConfig(const QSharedPointer<ServerConfig> &serverConfig);
ExportConfigResult generateConnectionConfig(const QString &clientName,
const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QSharedPointer<ServerConfig> &serverConfig,
const QPair<QString, QString> &dnsSettings);
ExportConfigResult generateOpenVpnConfig(const QString &clientName,
const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
ExportConfigResult generateWireGuardConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
ExportConfigResult generateAwgConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
ExportConfigResult generateShadowSocksConfig(const ServerCredentials &credentials,
const DockerContainer container,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
ExportConfigResult generateCloakConfig(const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
ExportConfigResult generateXrayConfig(const QString &clientName,
const ServerCredentials &credentials,
const ContainerConfig &containerConfig,
const QPair<QString, QString> &dnsSettings,
bool isApiConfig = false);
private:
ErrorCode generateNativeConfig(const DockerContainer container, const QString &clientName,
const Proto &protocol, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QPair<QString, QString> &dnsSettings,
bool isApiConfig, QJsonObject &jsonNativeConfig,
const QSharedPointer<ServerController> &serverController);
QList<QString> generateQrCodeSeries(const QByteArray &data);
QString generateQrCode(const QByteArray &data);
std::shared_ptr<Settings> m_settings;
ErrorCode m_lastClientAppendResult;
ErrorCode m_lastNativeConfigAppendResult;
bool m_waitingForClientAppend;
bool m_waitingForNativeConfigAppend;
};
#endif // EXPORT_CORE_CONTROLLER_H

View File

@@ -0,0 +1,397 @@
#include "installController.h"
#include <QDesktopServices>
#include <QDir>
#include <QEventLoop>
#include <QJsonDocument>
#include <QRandomGenerator>
#include <QStandardPaths>
#include <QtConcurrent>
#include "core/api/apiUtils.h"
#include "core/controllers/selfhosted/serverController.h"
#include "core/controllers/vpnConfigurationController.h"
#include "core/models/servers/selfHostedServerConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/networkUtilities.h"
#include "logger.h"
#include "utilities.h"
namespace
{
Logger logger("CoreInstallController");
}
InstallController::InstallController(std::shared_ptr<Settings> settings,
QObject *parent)
: QObject(parent),
m_settings(settings)
{
}
InstallResult InstallController::installContainer(DockerContainer container, int port, TransportProto transportProto,
const ServerCredentials &serverCredentials, bool shouldCreateServer,
const QString &privateKeyPassphrase)
{
InstallResult result;
result.errorCode = ErrorCode::NoError;
result.isServiceInstall = (ContainerProps::containerService(container) == ServiceType::Other);
result.isInstalledContainerFound = false;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
ContainerConfig containerConfig = generateContainerConfig(container, port, transportProto);
if (shouldCreateServer && isServerAlreadyExists(serverCredentials)) {
result.errorCode = ErrorCode::InternalError;
return result;
}
QMap<DockerContainer, ContainerConfig> installedContainers;
ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers);
if (errorCode != ErrorCode::NoError) {
result.errorCode = errorCode;
return result;
}
QSharedPointer<ServerConfig> serverConfig;
if (shouldCreateServer) {
errorCode = installServer(container, installedContainers, serverCredentials,
serverController, result.message, serverConfig);
} else {
if (installedContainers.contains(container)) {
result.errorCode = ErrorCode::InternalError;
return result;
}
errorCode = installContainer(container, installedContainers, serverCredentials,
serverController, result.message);
}
if (errorCode != ErrorCode::NoError) {
result.errorCode = errorCode;
return result;
}
result.errorCode = ErrorCode::NoError;
return result;
}
InstallResult InstallController::scanServerForInstalledContainers(const ServerCredentials &serverCredentials)
{
InstallResult result;
result.errorCode = ErrorCode::NoError;
result.isInstalledContainerFound = false;
result.isServiceInstall = false;
QMap<DockerContainer, ContainerConfig> installedContainers;
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
result.errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers);
if (result.errorCode == ErrorCode::NoError) {
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
auto container = iterator.key();
ContainerConfig containerConfig = iterator.value();
if (ContainerProps::isSupportedByCurrentPlatform(container)) {
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, container, containerConfig);
if (result.errorCode != ErrorCode::NoError) {
return result;
}
emit clientAppendRequested(container, serverCredentials, containerConfig,
QString("Admin [%1]").arg(QSysInfo::prettyProductName()),
serverController);
}
result.isInstalledContainerFound = true;
}
}
return result;
}
ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController,
QMap<DockerContainer, ContainerConfig> &installedContainers)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
ErrorCode errorCode = serverController->runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto containersInfo = stdOut.split("\n");
for (auto &containerInfo : containersInfo) {
if (containerInfo.isEmpty()) {
continue;
}
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z0-9]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
if (containerAndPortMatch.hasMatch()) {
QString containerName = containerAndPortMatch.captured(1);
QString port = containerAndPortMatch.captured(2);
QString transportProto = containerAndPortMatch.captured(3);
DockerContainer container = ContainerProps::containerFromString(containerName);
if (container != DockerContainer::None) {
ContainerConfig containerConfig;
containerConfig.containerName = ContainerProps::containerToString(container);
auto containerProto = ContainerProps::defaultProtocol(container);
auto containerProtoString = ProtocolProps::protoToString(containerProto);
// Create appropriate protocol config based on container type
QSharedPointer<ProtocolConfig> protocolConfig;
// Create a temporary QJsonObject to construct the protocol config
QJsonObject protoConfigObject;
protoConfigObject.insert(config_key::port, port);
protoConfigObject.insert(config_key::transport_proto, transportProto);
// Create the appropriate protocol config based on type
switch (containerProto) {
case Proto::OpenVpn:
protocolConfig = QSharedPointer<OpenVpnProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::WireGuard:
protocolConfig = QSharedPointer<WireGuardProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Awg:
protocolConfig = QSharedPointer<AwgProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Cloak:
protocolConfig = QSharedPointer<CloakProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::ShadowSocks:
protocolConfig = QSharedPointer<ShadowsocksProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Xray:
case Proto::SSXray:
protocolConfig = QSharedPointer<XrayProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Ikev2:
protocolConfig = QSharedPointer<Ikev2ProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Sftp:
protocolConfig = QSharedPointer<SftpProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
case Proto::Socks5Proxy:
protocolConfig = QSharedPointer<Socks5ProtocolConfig>::create(protoConfigObject, containerProtoString);
break;
default:
continue; // Skip unknown protocols
}
if (protocolConfig) {
containerConfig.protocolConfigs.insert(containerProtoString, protocolConfig);
installedContainers.insert(container, containerConfig);
}
}
}
}
return ErrorCode::NoError;
}
ErrorCode InstallController::installServer(const DockerContainer container,
const QMap<DockerContainer, ContainerConfig> &installedContainers,
const ServerCredentials &serverCredentials,
const QSharedPointer<ServerController> &serverController,
QString &finishMessage, QSharedPointer<ServerConfig> &serverConfig)
{
if (installedContainers.size() > 1) {
finishMessage += tr("\nAdded containers that were already installed on the server");
}
// Create a SelfHostedServerConfig and populate it properly
serverConfig = QSharedPointer<SelfHostedServerConfig>::create(QJsonObject());
auto selfHostedConfig = qSharedPointerCast<SelfHostedServerConfig>(serverConfig);
selfHostedConfig->hostName = serverCredentials.hostName;
selfHostedConfig->serverCredentials = serverCredentials;
selfHostedConfig->name = m_settings->nextAvailableServerName();
selfHostedConfig->defaultContainer = ContainerProps::containerToString(container);
selfHostedConfig->type = amnezia::ServerConfigType::SelfHosted;
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
auto containerConfig = iterator.value();
QString containerName = ContainerProps::containerToString(iterator.key());
if (ContainerProps::isSupportedByCurrentPlatform(iterator.key())) {
auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(),
containerConfig);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
}
selfHostedConfig->containerConfigs.insert(containerName, containerConfig);
}
int serverIndex = m_settings->nextAvailableServerIndex();
m_settings->setServerConfig(serverIndex, serverConfig->toJson());
m_settings->setDefaultServerIndex(serverIndex);
finishMessage = tr("Server '%1' was successfully added").arg(serverCredentials.hostName);
return ErrorCode::NoError;
}
ErrorCode InstallController::installContainer(const DockerContainer container,
const QMap<DockerContainer, ContainerConfig> &installedContainers,
const ServerCredentials &serverCredentials,
const QSharedPointer<ServerController> &serverController,
QString &finishMessage)
{
bool isInstalledContainerAddedToGui = false;
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
QList<QJsonObject> allContainerConfigs;
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
QJsonObject containerConfig = iterator.value();
if (ContainerProps::isSupportedByCurrentPlatform(container)) {
auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(), containerConfig);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
allContainerConfigs.append(containerConfig);
} else {
allContainerConfigs.append(containerConfig);
}
if (container != iterator.key()) {
isInstalledContainerAddedToGui = true;
}
}
if (isInstalledContainerAddedToGui) {
finishMessage += tr("\nAlready installed containers were found on the server. "
"All installed containers have been added to the application");
}
finishMessage = tr("Container '%1' was successfully added").arg(ContainerProps::containerHumanNames().value(container));
return ErrorCode::NoError;
}
bool InstallController::isServerAlreadyExists(const ServerCredentials &serverCredentials) const
{
for (int i = 0; i < m_settings->serversCount(); i++) {
QJsonObject serverConfig = m_settings->serverConfig(i);
ServerCredentials existingCredentials = ServerCredentials::fromServerConfig(serverConfig);
if (serverCredentials.hostName == existingCredentials.hostName &&
serverCredentials.port == existingCredentials.port) {
return true;
}
}
return false;
}
ContainerConfig InstallController::generateContainerConfig(const DockerContainer container, int port,
const TransportProto transportProto)
{
ContainerConfig containerConfig;
containerConfig.containerName = ContainerProps::containerToString(container);
auto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QSharedPointer<ProtocolConfig> protocolConfig;
if (protocol == mainProto) {
// Create the appropriate protocol config based on the protocol type
if (protocol == Proto::Awg && container == DockerContainer::Awg) {
// Use the VpnConfigurationsController to create proper AwgProtocolConfig
// For now, create a basic protocol config and set values
protocolConfig = QSharedPointer<AwgProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig);
// Set server protocol config
awgConfig->serverProtocolConfig.port = QString::number(port);
awgConfig->serverProtocolConfig.transportProto = ProtocolProps::transportProtoToString(transportProto, protocol);
// Generate AWG-specific parameters
awgConfig->serverProtocolConfig.junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
awgConfig->serverProtocolConfig.junkPacketMinSize = QString::number(10);
awgConfig->serverProtocolConfig.junkPacketMaxSize = QString::number(50);
int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150);
QSet<int> usedValues;
usedValues.insert(s1);
while (usedValues.contains(s2) || s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) {
s2 = QRandomGenerator::global()->bounded(15, 150);
}
usedValues.insert(s2);
awgConfig->serverProtocolConfig.initPacketJunkSize = QString::number(s1);
awgConfig->serverProtocolConfig.responsePacketJunkSize = QString::number(s2);
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();
awgConfig->serverProtocolConfig.initPacketMagicHeader = headersValueList.at(0);
awgConfig->serverProtocolConfig.responsePacketMagicHeader = headersValueList.at(1);
awgConfig->serverProtocolConfig.underloadPacketMagicHeader = headersValueList.at(2);
awgConfig->serverProtocolConfig.transportPacketMagicHeader = headersValueList.at(3);
} else {
// For other protocols, create basic protocol configs
// This will need to be expanded for each protocol type
protocolConfig = QSharedPointer<ProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
// For now, store basic port and transport info as JSON until all protocol configs are properly structured
QJsonObject tempConfig;
tempConfig.insert(config_key::port, QString::number(port));
tempConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
if (container == DockerContainer::Sftp) {
tempConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
tempConfig.insert(config_key::password, Utils::getRandomString(16));
} else if (container == DockerContainer::Socks5Proxy) {
tempConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName);
tempConfig.insert(config_key::password, Utils::getRandomString(16));
}
// Store this in the protocol config's internal JSON for now
// TODO: Replace with proper protocol config structure
protocolConfig = QSharedPointer<ProtocolConfig>::create(tempConfig, ProtocolProps::protoToString(protocol));
}
} else {
// Non-main protocols get basic configs
protocolConfig = QSharedPointer<ProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
}
containerConfig.protocolConfigs.insert(ProtocolProps::protoToString(protocol), protocolConfig);
}
return containerConfig;
}
ErrorCode InstallController::checkSshConnection(const ServerCredentials &serverCredentials, const QString &privateKeyPassphrase)
{
Q_UNUSED(privateKeyPassphrase);
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
ErrorCode errorCode = ErrorCode::NoError;
serverController->checkSshConnection(serverCredentials, errorCode);
return errorCode;
}

View File

@@ -0,0 +1,111 @@
#ifndef INSTALL_CORE_CONTROLLER_H
#define INSTALL_CORE_CONTROLLER_H
#include <QObject>
#include <QSharedPointer>
#include <QJsonArray>
#include <QJsonObject>
#include <QMap>
#include <QRegularExpression>
#include "core/defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "core/models/servers/serverConfig.h"
class ServerController;
class Settings;
using namespace amnezia;
struct InstallResult
{
ErrorCode errorCode;
QString message;
bool isServiceInstall;
bool isInstalledContainerFound;
};
class InstallController : public QObject
{
Q_OBJECT
public:
explicit InstallController(std::shared_ptr<Settings> settings,
QObject *parent = nullptr);
// Main installation operations
InstallResult installContainer(DockerContainer container, int port, TransportProto transportProto,
const ServerCredentials &serverCredentials, bool shouldCreateServer,
const QString &privateKeyPassphrase = QString());
InstallResult scanServerForInstalledContainers(const ServerCredentials &serverCredentials);
InstallResult updateContainer(const DockerContainer container, const ServerCredentials &serverCredentials,
const ContainerConfig &containerConfig);
// Server management operations
ErrorCode removeProcessedServer(const ServerCredentials &serverCredentials);
ErrorCode rebootProcessedServer(const ServerCredentials &serverCredentials);
ErrorCode removeAllContainers(const ServerCredentials &serverCredentials);
ErrorCode removeProcessedContainer(const DockerContainer container, const ServerCredentials &serverCredentials);
ErrorCode removeApiConfig(const QSharedPointer<ServerConfig> &serverConfig);
ErrorCode clearCachedProfile(const ServerCredentials &serverCredentials);
// Utility methods
ErrorCode mountSftpDrive(const ServerCredentials &serverCredentials, const QString &port,
const QString &password, const QString &username);
QString getNextAvailableServerName() const;
ErrorCode addEmptyServer(const ServerCredentials &serverCredentials);
bool isConfigValid(const ServerCredentials &serverCredentials) const;
ErrorCode checkSshConnection(const ServerCredentials &serverCredentials, const QString &privateKeyPassphrase);
signals:
void clientAppendRequested(const DockerContainer container, const ServerCredentials &credentials,
const ContainerConfig &containerConfig, const QString &clientName,
const QSharedPointer<ServerController> &serverController);
void containerInstalled(const InstallResult &result);
void serverScanned(const InstallResult &result);
void containerUpdated(const InstallResult &result);
void serverRebooted(const QString &message);
void serverRemoved(const QString &message);
void allContainersRemoved(const QString &message);
void containerRemoved(const QString &message);
void installationError(ErrorCode errorCode);
void wrongInstallationUser(const QString &message);
void serverIsBusy(bool isBusy);
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
void noInstalledContainers();
private:
// Installation helpers
ErrorCode installServer(const DockerContainer container,
const QMap<DockerContainer, ContainerConfig> &installedContainers,
const ServerCredentials &serverCredentials,
const QSharedPointer<ServerController> &serverController,
QString &finishMessage, QSharedPointer<ServerConfig> &serverConfig);
ErrorCode installContainer(const DockerContainer container,
const QMap<DockerContainer, ContainerConfig> &installedContainers,
const ServerCredentials &serverCredentials,
const QSharedPointer<ServerController> &serverController,
QString &finishMessage);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController,
QMap<DockerContainer, ContainerConfig> &installedContainers);
ContainerConfig generateContainerConfig(const DockerContainer container, int port,
const TransportProto transportProto);
bool isServerAlreadyExists(const ServerCredentials &serverCredentials) const;
std::shared_ptr<Settings> m_settings;
};
#endif // INSTALL_CORE_CONTROLLER_H

View File

@@ -0,0 +1,140 @@
#include "selfhostedConfigController.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/cloakProtocolConfig.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/networkUtilities.h"
#include "settings.h"
#include "logger.h"
namespace
{
Logger logger("SelfhostedConfigController");
}
SelfhostedConfigController::SelfhostedConfigController(std::shared_ptr<Settings> settings, QObject *parent)
: ConfigController(settings, parent)
{
m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
}
void SelfhostedConfigController::toggleAmneziaDns(bool enabled)
{
m_isAmneziaDnsEnabled = enabled;
m_settings->setUseAmneziaDns(enabled);
emit amneziaDnsToggled(enabled);
}
QPair<QString, QString> SelfhostedConfigController::getDnsPair(int serverIndex) const
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return {};
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
QPair<QString, QString> dns;
bool isDnsContainerInstalled = isAmneziaDnsContainerInstalled(serverIndex);
dns.first = serverConfig->dns1;
dns.second = serverConfig->dns2;
if (isDnsContainerInstalled && m_isAmneziaDnsEnabled) {
dns.first = serverConfig->hostName;
dns.second = "";
}
return dns;
}
bool SelfhostedConfigController::isAmneziaDnsContainerInstalled(int serverIndex) const
{
auto servers = m_settings->serversArray();
if (serverIndex >= servers.size()) return false;
auto serverConfig = ServerConfig::createServerConfig(servers.at(serverIndex).toObject());
for (const auto &container : serverConfig->containerConfigs) {
auto containerType = ContainerProps::containerFromString(container.containerName);
if (containerType == DockerContainer::Dns) {
return true;
}
}
return false;
}
QMap<QString, QSharedPointer<ProtocolConfig>> SelfhostedConfigController::getProtocolConfigs(const QVector<QSharedPointer<ProtocolConfig>> &protocols)
{
QMap<QString, QSharedPointer<ProtocolConfig>> protocolConfigs;
for (const auto &config : protocols) {
Proto protocol = ProtocolProps::protoFromString(config->protocolName);
if (isProtocolSupported(protocol)) {
protocolConfigs.insert(config->protocolName, config);
}
}
return protocolConfigs;
}
void SelfhostedConfigController::updateProtocolConfiguration(Proto protocol, const QSharedPointer<ProtocolConfig> &protocolConfig)
{
if (!isProtocolSupported(protocol)) {
logger.warning() << "Protocol not supported:" << ProtocolProps::protoToString(protocol);
return;
}
logger.info() << "Updating protocol configuration for:" << ProtocolProps::protoToString(protocol);
emit protocolConfigUpdated(protocol, protocolConfig);
}
QSharedPointer<ProtocolConfig> SelfhostedConfigController::createProtocolConfig(Proto protocol)
{
if (!isProtocolSupported(protocol)) {
logger.warning() << "Attempting to create unsupported protocol config:" << ProtocolProps::protoToString(protocol);
return nullptr;
}
switch (protocol) {
case Proto::Awg:
return QSharedPointer<AwgProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::Awg));
case Proto::Cloak:
return QSharedPointer<CloakProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::Cloak));
case Proto::OpenVpn:
return QSharedPointer<OpenVpnProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::OpenVpn));
case Proto::ShadowSocks:
return QSharedPointer<ShadowsocksProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::ShadowSocks));
case Proto::WireGuard:
return QSharedPointer<WireGuardProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::WireGuard));
case Proto::Xray:
return QSharedPointer<XrayProtocolConfig>::create(QJsonObject{}, ProtocolProps::protoToString(Proto::Xray));
default:
logger.warning() << "Unknown protocol in createProtocolConfig:" << ProtocolProps::protoToString(protocol);
return nullptr;
}
}
bool SelfhostedConfigController::isProtocolSupported(Proto protocol) const
{
switch (protocol) {
case Proto::Awg:
case Proto::Cloak:
case Proto::OpenVpn:
case Proto::ShadowSocks:
case Proto::WireGuard:
case Proto::Xray:
return true;
default:
return false;
}
}

View File

@@ -0,0 +1,45 @@
#ifndef SELFHOSTEDCONFIGCONTROLLER_H
#define SELFHOSTEDCONFIGCONTROLLER_H
#include "../configController.h"
#include "core/models/containers/containerConfig.h"
#include "core/models/protocols/protocolConfig.h"
#include <QMap>
using namespace amnezia;
class SelfhostedConfigController : public ConfigController
{
Q_OBJECT
public:
explicit SelfhostedConfigController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
// Self-hosted specific functionality
// Amnezia DNS management
void toggleAmneziaDns(bool enabled);
QPair<QString, QString> getDnsPair(int serverIndex) const;
bool isAmneziaDnsContainerInstalled(int serverIndex) const;
// Protocol management (from ProtocolConfigController)
QMap<QString, QSharedPointer<ProtocolConfig>> getProtocolConfigs(const QVector<QSharedPointer<ProtocolConfig>> &protocols);
void updateProtocolConfiguration(Proto protocol, const QSharedPointer<ProtocolConfig> &protocolConfig);
QSharedPointer<ProtocolConfig> createProtocolConfig(Proto protocol);
bool isProtocolSupported(Proto protocol) const;
private:
bool m_isAmneziaDnsEnabled;
// Helper methods
bool checkSplitTunnelingInContainer(const ContainerConfig &containerConfig,
const QString &defaultContainer) const;
signals:
// Self-hosted specific signals
void amneziaDnsToggled(bool enabled);
void protocolConfigUpdated(Proto protocol, const QSharedPointer<ProtocolConfig> &config);
};
#endif // SELFHOSTEDCONFIGCONTROLLER_H

View File

@@ -8,6 +8,15 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/models/protocols/cloakProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProtocolConfig.h"
#include <QPointer>
#include <QTemporaryFile>
#include <QThread>
@@ -22,14 +31,22 @@
#include <chrono>
#include <thread>
#include "containers/containers_defs.h"
#include "core/models/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"
#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"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProtocolConfig.h"
namespace
{
@@ -106,10 +123,10 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
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);
e = runScript(credentials, replaceVars(runner, generateVarsForContainer(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);
runScript(credentials, replaceVars(remover, generateVarsForContainer(credentials, container)), cbReadStdOut, cbReadStdErr);
return e;
}
@@ -132,14 +149,14 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
// mkdir
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container)));
e = runScript(credentials, replaceVars(mkdir, generateVarsForContainer(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)),
generateVarsForContainer(credentials, container)),
cbReadStd, cbReadStd);
if (e)
@@ -147,7 +164,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
genVarsForScript(credentials, container)),
generateVarsForContainer(credentials, container)),
cbReadStd, cbReadStd);
if (e)
@@ -155,7 +172,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
genVarsForScript(credentials, container)),
generateVarsForContainer(credentials, container)),
cbReadStd, cbReadStd);
if (e)
@@ -167,7 +184,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return ErrorCode::ServerContainerMissingError;
}
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), generateVarsForContainer(credentials, container)));
return e;
}
@@ -236,10 +253,10 @@ ErrorCode ServerController::removeAllContainers(const ServerCredentials &credent
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
{
return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container)));
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), generateVarsForContainer(credentials, container)));
}
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate)
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate)
{
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
ErrorCode e = ErrorCode::NoError;
@@ -299,8 +316,8 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
return startupContainerWorker(credentials, container, config);
}
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
QJsonObject &newConfig)
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
{
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
@@ -316,86 +333,20 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials
}
}
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
bool ServerController::isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
const auto &mainProto = ContainerProps::defaultProtocol(container);
const QString protocolName = ProtocolProps::protoToString(mainProto);
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;
const auto oldProtocolConfig = oldConfig.protocolConfigs.value(protocolName);
const auto newProtocolConfig = newConfig.protocolConfigs.value(protocolName);
if (!oldProtocolConfig || !newProtocolConfig) {
return true; // If either config is missing, reinstall is required
}
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;
// Use the existing isServerSettingsEqual method from ProtocolConfig
return !oldProtocolConfig->isServerSettingsEqual(newProtocolConfig);
}
ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container)
@@ -415,22 +366,10 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
};
ErrorCode error =
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), generateVarsForContainer(credentials, DockerContainer::None)),
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"))
@@ -439,17 +378,17 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
return error;
}
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config)
{
// create folder on host
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), generateVarsForContainer(credentials, container)));
}
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &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)));
ErrorCode errorCode = runScript(credentials, replaceVars(scriptString, generateVarsForContainer(credentials, container)));
if (errorCode)
return errorCode;
@@ -470,7 +409,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
ErrorCode error =
runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
replaceVars(amnezia::scriptData(SharedScriptType::build_container), generateVarsForContainer(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
if (stdOut.contains("doesn't work on cgroups v2"))
@@ -483,7 +422,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
return error;
}
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -493,7 +432,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)),
generateVarsForContainer(credentials, container, config)),
cbReadStdOut);
if (stdOut.contains("address already in use"))
@@ -506,7 +445,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
return e;
}
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -520,15 +459,16 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
ErrorCode e = runContainerScript(credentials, container,
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
genVarsForScript(credentials, container, config)),
generateVarsForContainer(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
// ensure header is included where needed; call into controller utility if accessible
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
return e;
}
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config)
{
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
@@ -536,7 +476,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
return ErrorCode::NoError;
}
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)),
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, generateVarsForContainer(credentials, container, config)),
"/opt/amnezia/start.sh");
if (e)
return e;
@@ -544,144 +484,105 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
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)));
generateVarsForContainer(credentials, container, config)));
}
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config)
ServerController::Vars ServerController::generateVarsForContainer(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &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", "" } });
// For VPN containers, use configurator pattern
if (ContainerProps::containerService(container) != ServiceType::Other) {
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QScopedPointer<ConfiguratorBase> configurator;
// Create the appropriate configurator for this protocol
switch (protocol) {
case Proto::OpenVpn:
configurator.reset(new OpenVpnConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
case Proto::ShadowSocks:
configurator.reset(new ShadowSocksConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
case Proto::Cloak:
configurator.reset(new CloakConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
case Proto::WireGuard:
configurator.reset(new WireguardConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){}), false));
break;
case Proto::Awg:
configurator.reset(new AwgConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
case Proto::Ikev2:
configurator.reset(new Ikev2Configurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
case Proto::Xray:
case Proto::SSXray:
configurator.reset(new XrayConfigurator(m_settings, QSharedPointer<ServerController>(this, [](ServerController*){})));
break;
default:
continue;
}
if (configurator) {
QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = config.protocolConfigs.value(protocolName);
return configurator->generateProtocolVars(credentials, container, protocolConfig);
}
}
}
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) } });
// Handle non-VPN services (SFTP, Socks5) with direct variable generation
Vars vars;
// Common variables that apply to all containers
vars.append({{"$REMOTE_HOST", credentials.hostName}});
vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}});
vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}});
vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}});
vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}});
// 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)
QString serverIp = (container != DockerContainer::Awg && 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";
vars.append({{"$SERVER_IP_ADDRESS", serverIp}});
}
// Handle container-specific variables for non-VPN services
if (container == DockerContainer::Sftp) {
QString protocolName = ProtocolProps::protoToString(Proto::Sftp);
auto sftpConfig = qSharedPointerCast<SftpProtocolConfig>(config.protocolConfigs.value(protocolName));
if (sftpConfig) {
QString port = sftpConfig->serverProtocolConfig.port;
if (port.isEmpty()) {
port = QString::number(ProtocolProps::defaultPort(Proto::Sftp));
}
vars.append({{"$SFTP_PORT", port}});
vars.append({{"$SFTP_USER", sftpConfig->serverProtocolConfig.userName}});
vars.append({{"$SFTP_PASSWORD", sftpConfig->serverProtocolConfig.password}});
}
} else if (container == DockerContainer::Socks5Proxy) {
QString protocolName = ProtocolProps::protoToString(Proto::Socks5Proxy);
auto socks5Config = qSharedPointerCast<Socks5ProtocolConfig>(config.protocolConfigs.value(protocolName));
if (socks5Config) {
QString port = socks5Config->serverProtocolConfig.port;
if (port.isEmpty()) {
port = protocols::socks5Proxy::defaultPort;
}
vars.append({{"$SOCKS5_PROXY_PORT", port}});
const QString &username = socks5Config->serverProtocolConfig.userName;
const QString &password = socks5Config->serverProtocolConfig.password;
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"}});
}
}
return vars;
@@ -711,7 +612,7 @@ void ServerController::cancelInstallation()
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), generateVarsForContainer(credentials, DockerContainer::None)));
}
QString ServerController::replaceVars(const QString &script, const Vars &vars)
@@ -723,7 +624,7 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
return s;
}
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config)
{
if (container == DockerContainer::Dns) {
return ErrorCode::NoError;
@@ -740,15 +641,37 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
};
const Proto protocol = ContainerProps::defaultProtocol(container);
const QString containerString = ProtocolProps::protoToString(protocol);
const QJsonObject containerConfig = config.value(containerString).toObject();
const QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = config.protocolConfigs.value(protocolName);
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);
QString port = QString::number(ProtocolProps::defaultPort(protocol)); // default
QString transportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); // default
if (protocolConfig) {
if (auto openVpnConfig = qSharedPointerCast<OpenVpnProtocolConfig>(protocolConfig)) {
port = openVpnConfig->serverProtocolConfig.port;
transportProto = openVpnConfig->serverProtocolConfig.transportProto;
} else if (auto wgConfig = qSharedPointerCast<WireGuardProtocolConfig>(protocolConfig)) {
port = wgConfig->serverProtocolConfig.port;
transportProto = wgConfig->serverProtocolConfig.transportProto;
} else if (auto awgConfig = qSharedPointerCast<AwgProtocolConfig>(protocolConfig)) {
port = awgConfig->serverProtocolConfig.port;
transportProto = awgConfig->serverProtocolConfig.transportProto;
} else if (auto xrayConfig = qSharedPointerCast<XrayProtocolConfig>(protocolConfig)) {
port = xrayConfig->serverProtocolConfig.port;
transportProto = xrayConfig->serverProtocolConfig.transportProto;
} else if (auto shadowsocksConfig = qSharedPointerCast<ShadowsocksProtocolConfig>(protocolConfig)) {
port = shadowsocksConfig->serverProtocolConfig.port;
} else if (auto cloakConfig = qSharedPointerCast<CloakProtocolConfig>(protocolConfig)) {
port = cloakConfig->serverProtocolConfig.port;
} else if (auto sftpConfig = qSharedPointerCast<SftpProtocolConfig>(protocolConfig)) {
port = sftpConfig->serverProtocolConfig.port;
} else if (auto socks5Config = qSharedPointerCast<Socks5ProtocolConfig>(protocolConfig)) {
port = socks5Config->serverProtocolConfig.port;
}
}
// 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);
@@ -764,12 +687,12 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
tcpProtoScript.append(" | grep LISTEN");
ErrorCode errorCode =
runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
runScript(credentials, replaceVars(tcpProtoScript, generateVarsForContainer(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
errorCode = runScript(credentials, replaceVars(udpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
errorCode = runScript(credentials, replaceVars(udpProtoScript, generateVarsForContainer(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
@@ -786,7 +709,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
script = script.append(" | grep LISTEN");
}
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
ErrorCode errorCode = runScript(credentials, replaceVars(script, generateVarsForContainer(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
@@ -810,7 +733,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
};
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
ErrorCode error = runScript(credentials, replaceVars(scriptData, generateVarsForContainer(credentials, DockerContainer::None)), cbReadStdOut, cbReadStdErr);
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
@@ -848,7 +771,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
return ErrorCode::ServerCancelInstallation;
}
stdOut.clear();
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)),
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), generateVarsForContainer(credentials, DockerContainer::None)),
cbReadStdOut, cbReadStdErr);
if (stdOut.contains("Packet manager not found"))

View File

@@ -4,7 +4,8 @@
#include <QJsonObject>
#include <QObject>
#include "containers/containers_defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "core/defs.h"
#include "core/sshclient.h"
@@ -25,12 +26,12 @@ public:
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 setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject());
const ContainerConfig &config = ContainerConfig());
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
const QString &path,
@@ -39,8 +40,8 @@ public:
ErrorCode &errorCode);
QString replaceVars(const QString &script, const Vars &vars);
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
const QJsonObject &config = QJsonObject());
Vars generateVarsForContainer(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &config = ContainerConfig());
ErrorCode runScript(const ServerCredentials &credentials, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
@@ -59,14 +60,14 @@ public:
private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config = ContainerConfig());
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);
const ContainerConfig &config = ContainerConfig());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config);
bool isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);

View File

@@ -0,0 +1,238 @@
#include "settingsController.h"
#include <QDateTime>
#include "settings.h"
#include "logger.h"
#include "ui/qautostart.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
namespace
{
Logger logger("SettingsController");
}
SettingsController::SettingsController(std::shared_ptr<Settings> settings, QObject *parent)
: QObject(parent), m_settings(settings)
{
}
void SettingsController::resetAllSettings()
{
logger.info() << "Resetting all settings to defaults";
m_settings->clearSettings();
emit settingsReset();
}
void SettingsController::configureDns(const QString &primaryDns, const QString &secondaryDns)
{
m_settings->setPrimaryDns(primaryDns);
m_settings->setSecondaryDns(secondaryDns);
emit dnsConfigChanged();
}
void SettingsController::toggleAmneziaDns(bool enable)
{
m_settings->setUseAmneziaDns(enable);
emit dnsConfigChanged();
}
void SettingsController::configureLogging(bool enabled)
{
m_settings->setSaveLogs(enabled);
}
void SettingsController::checkLoggingExpiration()
{
if (m_settings->isSaveLogs()) {
QDateTime loggingDisableDate = m_settings->getLogEnableDate().addDays(14);
if (loggingDisableDate <= QDateTime::currentDateTime()) {
configureLogging(false);
clearLogs();
emit loggingExpired();
}
}
}
void SettingsController::clearLogs()
{
logger.info() << "Clearing application logs";
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(false);
Logger::clearServiceLogs();
#endif
logger.info() << "Logs cleared successfully";
}
void SettingsController::configureKillSwitch(bool enable, bool strict)
{
m_settings->setKillSwitchEnabled(enable);
if (enable) {
m_settings->setStrictKillSwitchEnabled(strict);
} else {
m_settings->setStrictKillSwitchEnabled(false);
}
emit killSwitchConfigChanged();
}
void SettingsController::configureAutoStart(bool enable)
{
Autostart::setAutostart(enable);
emit autoStartConfigChanged();
}
void SettingsController::configureAutoConnect(bool enable)
{
m_settings->setAutoConnect(enable);
}
void SettingsController::configureStartMinimized(bool enable)
{
m_settings->setStartMinimized(enable);
}
void SettingsController::configureScreenshots(bool enable)
{
m_settings->setScreenshotsEnabled(enable);
}
QString SettingsController::getPrimaryDns() const
{
return m_settings->primaryDns();
}
QString SettingsController::getSecondaryDns() const
{
return m_settings->secondaryDns();
}
bool SettingsController::isAmneziaDnsEnabled() const
{
return m_settings->useAmneziaDns();
}
bool SettingsController::isLoggingEnabled() const
{
return m_settings->isSaveLogs();
}
bool SettingsController::isKillSwitchEnabled() const
{
return m_settings->isKillSwitchEnabled();
}
bool SettingsController::isStrictKillSwitchEnabled() const
{
return m_settings->isStrictKillSwitchEnabled();
}
bool SettingsController::isAutoStartEnabled() const
{
return Autostart::isAutostart();
}
bool SettingsController::isAutoConnectEnabled() const
{
return m_settings->isAutoConnect();
}
bool SettingsController::isStartMinimizedEnabled() const
{
return m_settings->isStartMinimized();
}
bool SettingsController::isScreenshotsEnabled() const
{
return m_settings->isScreenshotsEnabled();
}
QByteArray SettingsController::backupAppConfig() const
{
return m_settings->backupAppConfig();
}
bool SettingsController::restoreAppConfig(const QByteArray &data)
{
return m_settings->restoreAppConfig(data);
}
QString SettingsController::getInstallationUuid() const
{
return m_settings->getInstallationUuid(false);
}
QString SettingsController::nextAvailableServerName() const
{
return m_settings->nextAvailableServerName();
}
void SettingsController::resetGatewayEndpoint()
{
m_settings->resetGatewayEndpoint();
}
void SettingsController::setGatewayEndpoint(const QString &endpoint)
{
m_settings->setGatewayEndpoint(endpoint);
}
QString SettingsController::getGatewayEndpoint() const
{
if (m_settings->isDevGatewayEnv()) {
return "Dev endpoint";
}
return m_settings->getGatewayEndpoint();
}
bool SettingsController::isDevGatewayEnv() const
{
return m_settings->isDevGatewayEnv();
}
void SettingsController::toggleDevGatewayEnv(bool enabled)
{
m_settings->toggleDevGatewayEnv(enabled);
if (enabled) {
m_settings->setDevGatewayEndpoint();
} else {
m_settings->resetGatewayEndpoint();
}
}
void SettingsController::setDevGatewayEndpoint()
{
m_settings->setDevGatewayEndpoint();
}
bool SettingsController::isHomeAdLabelVisible() const
{
return m_settings->isHomeAdLabelVisible();
}
void SettingsController::disableHomeAdLabel()
{
m_settings->disableHomeAdLabel();
}
QDateTime SettingsController::getLogEnableDate() const
{
return m_settings->getLogEnableDate();
}
QLocale SettingsController::getAppLanguage() const
{
return m_settings->getAppLanguage();
}
void SettingsController::setAppLanguage(const QLocale &locale)
{
m_settings->setAppLanguage(locale);
}

View File

@@ -0,0 +1,88 @@
#ifndef SETTINGSCONTROLLER_H
#define SETTINGSCONTROLLER_H
#include <QObject>
#include <QFuture>
#include <QDateTime>
#include "core/defs.h"
class Settings;
using namespace amnezia;
class SettingsController : public QObject
{
Q_OBJECT
public:
explicit SettingsController(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
void resetAllSettings();
void configureDns(const QString &primaryDns, const QString &secondaryDns);
void toggleAmneziaDns(bool enable);
void configureLogging(bool enable);
void checkLoggingExpiration();
void clearLogs();
void configureKillSwitch(bool enable, bool strict = false);
void configureAutoStart(bool enable);
void configureAutoConnect(bool enable);
void configureStartMinimized(bool enable);
void configureScreenshots(bool enable);
QString getPrimaryDns() const;
QString getSecondaryDns() const;
bool isAmneziaDnsEnabled() const;
bool isLoggingEnabled() const;
bool isKillSwitchEnabled() const;
bool isStrictKillSwitchEnabled() const;
bool isAutoStartEnabled() const;
bool isAutoConnectEnabled() const;
bool isStartMinimizedEnabled() const;
bool isScreenshotsEnabled() const;
// Backup/restore functionality
QByteArray backupAppConfig() const;
bool restoreAppConfig(const QByteArray &data);
// Installation UUID
QString getInstallationUuid() const;
// Server naming
QString nextAvailableServerName() const;
// Gateway endpoint functionality
void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint);
QString getGatewayEndpoint() const;
bool isDevGatewayEnv() const;
void toggleDevGatewayEnv(bool enabled);
void setDevGatewayEndpoint();
// Home ad label
bool isHomeAdLabelVisible() const;
void disableHomeAdLabel();
// Log date
QDateTime getLogEnableDate() const;
QLocale getAppLanguage() const;
void setAppLanguage(const QLocale &locale);
signals:
void settingsReset();
void dnsConfigChanged();
void loggingConfigChanged();
void killSwitchConfigChanged();
void autoStartConfigChanged();
void loggingExpired();
private:
std::shared_ptr<Settings> m_settings;
};
#endif // SETTINGSCONTROLLER_H

View File

@@ -0,0 +1,228 @@
#include "splitTunnelingController.h"
#include "settings.h"
#include "core/networkUtilities.h"
#include <QFileInfo>
SplitTunnelingController::SplitTunnelingController(std::shared_ptr<Settings> settings,
QSharedPointer<VpnConnection> vpnConnection,
QObject *parent)
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection)
{
}
// Apps split tunneling implementation
bool SplitTunnelingController::addApp(const InstalledAppInfo &appInfo)
{
InstalledAppInfo processedAppInfo = appInfo;
// Migrated from AppSplitTunnelingController::addApp - app name extraction
if (processedAppInfo.appName.isEmpty() && !processedAppInfo.appPath.isEmpty()) {
QFileInfo fileInfo(processedAppInfo.appPath);
processedAppInfo.appName = fileInfo.fileName();
}
auto currentApps = m_settings->getVpnApps(getAppsRouteMode());
if (currentApps.contains(processedAppInfo)) {
return false;
}
currentApps.append(processedAppInfo);
m_settings->setVpnApps(getAppsRouteMode(), currentApps);
emit appAdded(processedAppInfo);
return true;
}
bool SplitTunnelingController::removeApp(const InstalledAppInfo &appInfo)
{
auto currentApps = m_settings->getVpnApps(getAppsRouteMode());
if (!currentApps.contains(appInfo)) {
return false;
}
currentApps.removeAll(appInfo);
m_settings->setVpnApps(getAppsRouteMode(), currentApps);
emit appRemoved(appInfo);
return true;
}
QVector<InstalledAppInfo> SplitTunnelingController::getApps(Settings::AppsRouteMode routeMode) const
{
return m_settings->getVpnApps(routeMode);
}
Settings::AppsRouteMode SplitTunnelingController::getAppsRouteMode() const
{
return m_settings->getAppsRouteMode();
}
void SplitTunnelingController::setAppsRouteMode(Settings::AppsRouteMode routeMode)
{
m_settings->setAppsRouteMode(routeMode);
emit appsRouteModelChanged();
}
bool SplitTunnelingController::isAppsSplitTunnelingEnabled() const
{
return m_settings->isAppsSplitTunnelingEnabled();
}
void SplitTunnelingController::setAppsSplitTunnelingEnabled(bool enabled)
{
m_settings->setAppsSplitTunnelingEnabled(enabled);
emit appsSplitTunnelingToggled();
}
// Sites split tunneling implementation
bool SplitTunnelingController::addSite(const QString &hostname, const QString &ip)
{
QString processedHostname = hostname;
// Migrated from SitesController::addSite - hostname processing
if (!NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
processedHostname.replace("https://", "");
processedHostname.replace("http://", "");
processedHostname.replace("ftp://", "");
processedHostname = processedHostname.split("/", Qt::SkipEmptyParts).first();
}
if (!m_settings->addVpnSite(getSitesRouteMode(), processedHostname, ip)) {
return false;
}
// Migrated from SitesController::addSite - VPN route management
if (m_vpnConnection) {
if (!ip.isEmpty()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << ip));
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(processedHostname)) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << processedHostname));
} else {
// Migrated from SitesController::addSite - DNS resolution
int lookupId = QHostInfo::lookupHost(processedHostname, this,
SLOT(handleHostnameResolved(QHostInfo)));
m_pendingResolutions[lookupId] = processedHostname;
}
}
emit siteAdded(processedHostname, ip);
return true;
}
bool SplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
{
if (replaceExisting) {
m_settings->removeAllVpnSites(getSitesRouteMode());
}
m_settings->addVpnSites(getSitesRouteMode(), sites);
// Migrated from SitesController - VPN route management for batch adds
if (m_vpnConnection) {
QStringList ips;
auto i = sites.constBegin();
while (i != sites.constEnd()) {
const QString &hostname = i.key();
const QString &ip = i.value();
if (ip.isEmpty()) {
ips.append(hostname);
} else {
ips.append(ip);
}
++i;
}
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, ips));
}
auto i = sites.constBegin();
while (i != sites.constEnd()) {
emit siteAdded(i.key(), i.value());
++i;
}
return true;
}
bool SplitTunnelingController::removeSite(const QString &hostname)
{
if (!m_settings->removeVpnSite(getSitesRouteMode(), hostname)) {
return false;
}
// Migrated from SitesController::removeSite - VPN route management
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname));
}
emit siteRemoved(hostname);
return true;
}
// Migrated from SitesController - DNS resolution handler
void SplitTunnelingController::handleHostnameResolved(const QHostInfo &hostInfo)
{
if (hostInfo.error() != QHostInfo::NoError) {
return;
}
QString hostname = m_pendingResolutions.take(hostInfo.lookupId());
if (hostname.isEmpty()) {
return;
}
for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << addr.toString()));
}
m_settings->addVpnSite(getSitesRouteMode(), hostname, addr.toString());
break;
}
}
}
QVector<QPair<QString, QString>> SplitTunnelingController::getSites(Settings::RouteMode routeMode) const
{
QVector<QPair<QString, QString>> sites;
const QVariantMap &sitesMap = m_settings->vpnSites(routeMode);
auto i = sitesMap.constBegin();
while (i != sitesMap.constEnd()) {
sites.append(qMakePair(i.key(), i.value().toString()));
++i;
}
return sites;
}
Settings::RouteMode SplitTunnelingController::getSitesRouteMode() const
{
return m_settings->routeMode();
}
void SplitTunnelingController::setSitesRouteMode(Settings::RouteMode routeMode)
{
m_settings->setRouteMode(routeMode);
emit sitesRouteModelChanged();
}
bool SplitTunnelingController::isSitesSplitTunnelingEnabled() const
{
return m_settings->isSitesSplitTunnelingEnabled();
}
void SplitTunnelingController::setSitesSplitTunnelingEnabled(bool enabled)
{
m_settings->setSitesSplitTunnelingEnabled(enabled);
emit sitesSplitTunnelingToggled();
}

View File

@@ -0,0 +1,71 @@
#ifndef SPLITTUNNELINGCONTROLLER_H
#define SPLITTUNNELINGCONTROLLER_H
#include <QObject>
#include <QVector>
#include <QMap>
#include <QStringList>
#include <QSharedPointer>
#include <QHostInfo>
#include "settings.h"
#include "core/defs.h"
#include "vpnconnection.h"
using namespace amnezia;
class SplitTunnelingController : public QObject
{
Q_OBJECT
public:
explicit SplitTunnelingController(std::shared_ptr<Settings> settings,
QSharedPointer<VpnConnection> vpnConnection = nullptr,
QObject *parent = nullptr);
// Apps split tunneling
bool addApp(const InstalledAppInfo &appInfo);
bool removeApp(const InstalledAppInfo &appInfo);
QVector<InstalledAppInfo> getApps(Settings::AppsRouteMode routeMode) const;
Settings::AppsRouteMode getAppsRouteMode() const;
void setAppsRouteMode(Settings::AppsRouteMode routeMode);
bool isAppsSplitTunnelingEnabled() const;
void setAppsSplitTunnelingEnabled(bool enabled);
// Sites split tunneling
bool addSite(const QString &hostname, const QString &ip = QString());
bool addSites(const QMap<QString, QString> &sites, bool replaceExisting = false);
bool removeSite(const QString &hostname);
QVector<QPair<QString, QString>> getSites(Settings::RouteMode routeMode) const;
Settings::RouteMode getSitesRouteMode() const;
void setSitesRouteMode(Settings::RouteMode routeMode);
bool isSitesSplitTunnelingEnabled() const;
void setSitesSplitTunnelingEnabled(bool enabled);
signals:
// Apps signals
void appsRouteModelChanged();
void appsSplitTunnelingToggled();
void appAdded(const InstalledAppInfo &appInfo);
void appRemoved(const InstalledAppInfo &appInfo);
// Sites signals
void sitesRouteModelChanged();
void sitesSplitTunnelingToggled();
void siteAdded(const QString &hostname, const QString &ip);
void siteRemoved(const QString &hostname);
private slots:
void handleHostnameResolved(const QHostInfo &hostInfo);
private:
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QMap<int, QString> m_pendingResolutions;
};
#endif // SPLITTUNNELINGCONTROLLER_H

View File

@@ -7,6 +7,18 @@
#include "configurators/shadowsocks_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "configurators/xray_configurator.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/cloakProtocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/openvpnProtocolConfig.h"
#include "core/models/protocols/shadowsocksProtocolConfig.h"
#include "core/models/protocols/torWebsiteProtocolConfig.h"
#include "core/models/protocols/wireguardProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProtocolConfig.h"
#include "core/models/protocols/protocolConfig.h"
#include <variant>
VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr<Settings> &settings,
QSharedPointer<ServerController> serverController, QObject *parent)
@@ -29,8 +41,33 @@ QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator
}
}
QSharedPointer<ProtocolConfig> VpnConfigurationsController::createProtocolConfig(const Proto protocol)
{
switch (protocol) {
case Proto::OpenVpn:
return QSharedPointer<OpenVpnProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::ShadowSocks:
return QSharedPointer<ShadowsocksProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::Cloak:
return QSharedPointer<CloakProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::WireGuard:
return QSharedPointer<WireGuardProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::Awg:
return QSharedPointer<AwgProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::Xray:
case Proto::SSXray:
return QSharedPointer<XrayProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
case Proto::Ikev2:
return QSharedPointer<Ikev2ProtocolConfig>::create(ProtocolProps::protoToString(protocol));
case Proto::TorWebSite:
return QSharedPointer<TorWebsiteProtocolConfig>::create(QJsonObject(), ProtocolProps::protoToString(protocol));
default:
return nullptr;
}
}
ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials,
const DockerContainer container, QJsonObject &containerConfig)
const DockerContainer container, ContainerConfig &containerConfig)
{
ErrorCode errorCode = ErrorCode::NoError;
@@ -39,25 +76,34 @@ ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const Se
}
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = containerConfig.protocolConfigs.value(protocolName);
if (!protocolConfig) {
protocolConfig = createProtocolConfig(protocol);
if (!protocolConfig) {
errorCode = ErrorCode::InternalError;
return errorCode;
}
containerConfig.protocolConfigs.insert(protocolName, protocolConfig);
}
auto configurator = createConfigurator(protocol);
QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
auto result = configurator->createConfig(credentials, container, protocolConfig, errorCode);
if (errorCode != ErrorCode::NoError || !result) {
return errorCode;
}
protocolConfig.insert(config_key::last_config, protocolConfigString);
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
containerConfig.protocolConfigs.insert(protocolName, result);
}
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)
const ServerCredentials &credentials, const DockerContainer container,
const ContainerConfig &containerConfig, const Proto protocol,
QString &protocolConfigString)
{
ErrorCode errorCode = ErrorCode::NoError;
@@ -65,19 +111,30 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA
return errorCode;
}
auto configurator = createConfigurator(protocol);
protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
QString protocolName = ProtocolProps::protoToString(protocol);
auto protocolConfig = containerConfig.protocolConfigs.value(protocolName);
if (!protocolConfig) {
errorCode = ErrorCode::InternalError;
return errorCode;
}
protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString);
auto configurator = createConfigurator(protocol);
auto result = configurator->createConfig(credentials, container, protocolConfig, errorCode);
if (errorCode != ErrorCode::NoError || !result) {
return errorCode;
}
configurator->processConfigWithExportSettings(dns, isApiConfig, result);
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(result);
std::visit([&protocolConfigString](const auto &config) -> void {
protocolConfigString = config->clientProtocolConfig.nativeConfig;
}, variant);
return errorCode;
}
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container)
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QSharedPointer<ServerConfig> &serverConfig,
const ContainerConfig &containerConfig, const DockerContainer container)
{
QJsonObject vpnConfiguration {};
@@ -85,26 +142,47 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
return vpnConfiguration;
}
bool isApiConfig = serverConfig.value(config_key::configVersion).toInt();
bool isApiConfig = static_cast<int>(serverConfig->type);
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();
QString protocolName = ProtocolProps::protoToString(proto);
auto protocolConfig = containerConfig.protocolConfigs.value(protocolName);
QString protocolConfigString;
if (protocolConfig) {
auto configurator = createConfigurator(proto);
configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfig);
ProtocolConfigVariant variant = ProtocolConfig::getProtocolConfigVariant(protocolConfig);
std::visit([&protocolConfigString](const auto &config) -> void {
protocolConfigString = config->clientProtocolConfig.nativeConfig;
}, variant);
} else {
protocolConfigString = "";
}
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
QJsonObject vpnConfigData;
if (proto == Proto::Xray || proto == Proto::SSXray) {
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
} else {
vpnConfigData[config_key::config] = protocolConfigString;
if (protocolConfig) {
QJsonObject protocolJson = protocolConfig->toJson();
for (auto it = protocolJson.begin(); it != protocolJson.end(); ++it) {
if (it.key() != config_key::config && it.key() != config_key::last_config) {
vpnConfigData[it.key()] = it.value();
}
}
}
}
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] =
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
}
}
@@ -117,30 +195,32 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
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::hostName] = serverConfig->hostName;
vpnConfiguration[config_key::description] = serverConfig->toJson().value(config_key::description).toString();
vpnConfiguration[config_key::configVersion] = static_cast<int>(serverConfig->type);
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)
void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, ContainerConfig &containerConfig,
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
if (container == DockerContainer::TorWebSite) {
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
QString protocolName = ProtocolProps::protoToString(mainProto);
auto protocolConfig = containerConfig.protocolConfigs.value(protocolName);
qDebug() << "amnezia-tor onions" << stdOut;
QString onion = stdOut;
onion.replace("\n", "");
protocol.insert(config_key::site, onion);
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
if (auto torConfig = qSharedPointerCast<TorWebsiteProtocolConfig>(protocolConfig)) {
torConfig->serverProtocolConfig.site = onion;
}
}
}

View File

@@ -4,8 +4,11 @@
#include <QObject>
#include "configurators/configurator_base.h"
#include "containers/containers_defs.h"
#include "core/models/containers/containers_defs.h"
#include "core/models/containers/containerConfig.h"
#include "core/defs.h"
#include "core/models/protocols/protocolConfig.h"
#include "core/models/servers/serverConfig.h"
#include "settings.h"
class VpnConfigurationsController : public QObject
@@ -17,16 +20,19 @@ public:
public slots:
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
QJsonObject &containerConfig);
ContainerConfig &containerConfig);
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
const DockerContainer container, const ContainerConfig &containerConfig, const Proto protocol,
QString &protocolConfigString);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QSharedPointer<ServerConfig> &serverConfig,
const ContainerConfig &containerConfig, const DockerContainer container);
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
static void updateContainerConfigAfterInstallation(const DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut);
signals:
public:
QSharedPointer<ProtocolConfig> createProtocolConfig(const Proto protocol);
private:
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);

View File

@@ -19,6 +19,13 @@ namespace amnezia
}
};
enum ServerConfigType
{
SelfHosted,
ApiV1,
ApiV2
};
struct InstalledAppInfo {
QString appName;
QString packageName;
@@ -61,7 +68,6 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -121,8 +127,6 @@ namespace amnezia
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors
OpenError = 1200,

View File

@@ -29,7 +29,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -78,8 +77,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View File

@@ -1,71 +1,108 @@
#include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
}
IpcClient& IpcClient::Instance()
IpcClient::~IpcClient()
{
thread_local IpcClient ipcClient;
return ipcClient;
if (m_localSocket)
m_localSocket->close();
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
}
IpcClient *IpcClient::Instance()
{
return m_instance;
}
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{
QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
if (rep.isNull()) {
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
if (!Instance())
return nullptr;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
return Instance()->m_ipcClient;
}
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
if (!Instance())
return nullptr;
return Instance()->m_Tun2SocksClient;
}
bool IpcClient::init(IpcClient *instance)
{
m_instance = instance;
Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
cliNode->waitForSource(5000);
Instance()->m_ipcClient.reset(cliNode);
if (!Instance()->m_ipcClient) {
qWarning() << "IpcClient is not ready!";
}
Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
}
});
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
[instance]() { instance->m_isSocketConnected = false; });
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected();
if (!Instance()->m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
}
return rep;
qDebug() << "IpcClient::init succeed";
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000);
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
@@ -73,7 +110,6 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
// TODO: rework the unsafe cast below
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
@@ -88,12 +124,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
[pd]() { pd->replicaNode->deleteLater(); });
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
return nullptr;
}
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;

View File

@@ -4,6 +4,7 @@
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
@@ -13,48 +14,27 @@ class IpcClient : public QObject
{
Q_OBJECT
public:
explicit IpcClient(QObject *parent = nullptr);
explicit IpcClient(QObject *parent = nullptr);
static IpcClient& Instance();
static IpcClient *Instance();
static bool init(IpcClient *instance);
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const;
template <typename Func>
static auto withInterface(Func func)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
qWarning() << "IpcClient::withInterface(): Service is not running";
if constexpr (std::is_void_v<ReturnType>)
return;
else
return ReturnType{};
}
return func(iface);
}
template <typename OnSuccess, typename OnFailure>
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
return onFailure();
}
return onSuccess(iface);
}
signals:
private:
QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
~IpcClient() override;
QRemoteObjectNode m_ClientNode;
QRemoteObjectNode m_Tun2SocksNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor {
ProcessDescriptor () {
@@ -66,6 +46,11 @@ private:
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
};
#endif // IPCCLIENT_H

View File

@@ -0,0 +1,76 @@
#include "clientInfo.h"
#include <QJsonObject>
namespace
{
namespace configKey
{
constexpr char clientId[] = "clientId";
constexpr char clientName[] = "clientName";
constexpr char container[] = "container";
constexpr char userData[] = "userData";
constexpr char creationDate[] = "creationDate";
constexpr char latestHandshake[] = "latestHandshake";
constexpr char dataReceived[] = "dataReceived";
constexpr char dataSent[] = "dataSent";
constexpr char allowedIps[] = "allowedIps";
}
}
ClientInfo::ClientInfo()
: container(DockerContainer::None)
{
}
ClientInfo::ClientInfo(const QString &clientId, const QString &clientName)
: clientId(clientId), clientName(clientName), creationDate(QDateTime::currentDateTime()), container(DockerContainer::None)
{
}
ClientInfo::ClientInfo(const QJsonObject &jsonObject)
{
clientId = jsonObject.value(configKey::clientId).toString();
container = ContainerProps::containerFromString(jsonObject.value(configKey::container).toString());
QJsonObject userData = jsonObject.value(configKey::userData).toObject();
clientName = userData.value(configKey::clientName).toString();
creationDate = QDateTime::fromString(userData.value(configKey::creationDate).toString());
latestHandshake = jsonObject.value(configKey::latestHandshake).toString();
dataReceived = jsonObject.value(configKey::dataReceived).toString();
dataSent = jsonObject.value(configKey::dataSent).toString();
allowedIps = jsonObject.value(configKey::allowedIps).toString();
}
QJsonObject ClientInfo::toJson() const
{
QJsonObject jsonObject;
jsonObject[configKey::clientId] = clientId;
jsonObject[configKey::container] = ContainerProps::containerToString(container);
QJsonObject userData;
userData[configKey::clientName] = clientName;
userData[configKey::creationDate] = creationDate.toString();
jsonObject[configKey::userData] = userData;
if (!latestHandshake.isEmpty()) {
jsonObject[configKey::latestHandshake] = latestHandshake;
}
if (!dataReceived.isEmpty()) {
jsonObject[configKey::dataReceived] = dataReceived;
}
if (!dataSent.isEmpty()) {
jsonObject[configKey::dataSent] = dataSent;
}
if (!allowedIps.isEmpty()) {
jsonObject[configKey::allowedIps] = allowedIps;
}
return jsonObject;
}
ClientInfo ClientInfo::fromJson(const QJsonObject &jsonObject)
{
return ClientInfo(jsonObject);
}

View File

@@ -0,0 +1,34 @@
#ifndef CLIENTINFO_H
#define CLIENTINFO_H
#include <QString>
#include <QDateTime>
#include <QJsonObject>
#include "core/models/containers/containers_defs.h"
using namespace amnezia;
class ClientInfo
{
public:
ClientInfo();
ClientInfo(const QString &clientId, const QString &clientName);
ClientInfo(const QJsonObject &jsonObject);
QJsonObject toJson() const;
static ClientInfo fromJson(const QJsonObject &jsonObject);
QString clientId;
QString clientName;
QDateTime creationDate;
DockerContainer container;
QString latestHandshake;
QString dataReceived;
QString dataSent;
QString allowedIps;
};
#endif // CLIENTINFO_H

View File

@@ -0,0 +1,5 @@
#include "containerConfig.h"
ContainerConfig::ContainerConfig()
{
}

View File

@@ -0,0 +1,21 @@
#ifndef CONTAINERCONFIG_H
#define CONTAINERCONFIG_H
#include <QMap>
#include <QSharedPointer>
#include <QString>
#include "core/models/protocols/protocolConfig.h"
#include "containers_defs.h"
class ContainerConfig
{
public:
ContainerConfig();
QString containerName;
amnezia::DockerContainer containerType;
QMap<QString, QSharedPointer<ProtocolConfig>> protocolConfigs;
};
#endif // CONTAINERCONFIG_H

View File

@@ -28,10 +28,7 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
return "amnezia-awg2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -44,10 +41,7 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Ipsec)
return "ikev2";
if (c == DockerContainer::Awg)
return "awg";
if (c == DockerContainer::Awg2)
return "awg";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -77,8 +71,6 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
case DockerContainer::Awg: return { Proto::Awg };
case DockerContainer::Awg2: return { Proto::Awg };
default: return { defaultProtocol(container) };
}
}
@@ -102,7 +94,6 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"},
@@ -129,9 +120,6 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Awg,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Awg2,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Xray,
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
"It is highly resistant to detection and offers high speed.") },
@@ -194,7 +182,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") },
{ DockerContainer::Awg2,
{ DockerContainer::Awg,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
@@ -254,7 +242,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2;
@@ -268,49 +255,21 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
}
}
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
Proto p = defaultProtocol(c);
return ProtocolProps::protoToString(p);
}
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{
#ifdef Q_OS_WINDOWS
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default:
return false;
}
#elif defined(MACOS_NE)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
default: return false;
}
#elif defined(Q_OS_MAC)
switch (c) {
@@ -324,7 +283,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true;
@@ -354,7 +312,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
default: return false;
}
}
@@ -362,7 +320,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("Automatic");
case DockerContainer::Awg: return tr("Automatic");
default: return "";
}
}
@@ -370,7 +328,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return "";
}
@@ -379,7 +337,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
default: return 0;
}
}
@@ -395,12 +353,6 @@ bool ContainerProps::isShareable(DockerContainer container)
}
}
bool ContainerProps::isAwgContainer(DockerContainer container)
{
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
@@ -418,7 +370,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;

View File

@@ -17,7 +17,6 @@ namespace amnezia
enum DockerContainer {
None = 0,
Awg,
Awg2,
WireGuard,
OpenVpn,
Cloak,
@@ -46,7 +45,6 @@ namespace amnezia
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();
@@ -73,9 +71,6 @@ namespace amnezia
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);

View File

@@ -0,0 +1,256 @@
#include "awgProtocolConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
using namespace amnezia;
AwgProtocolConfig::AwgProtocolConfig(const QString &protocolName) : ProtocolConfig(protocolName)
{
}
AwgProtocolConfig::AwgProtocolConfig(const QJsonObject &protocolConfigObject, const QString &protocolName) : ProtocolConfig(protocolName)
{
serverProtocolConfig.port = protocolConfigObject.value(config_key::port).toString(protocols::awg::defaultPort);
serverProtocolConfig.transportProto = protocolConfigObject.value(config_key::transport_proto).toString("udp");
serverProtocolConfig.subnetAddress = protocolConfigObject.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
serverProtocolConfig.mtu = protocolConfigObject.value(config_key::mtu).toString(protocols::awg::defaultMtu);
serverProtocolConfig.awgData.junkPacketCount = protocolConfigObject.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
serverProtocolConfig.awgData.junkPacketMinSize = protocolConfigObject.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
serverProtocolConfig.awgData.junkPacketMaxSize = protocolConfigObject.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
serverProtocolConfig.awgData.initPacketJunkSize = protocolConfigObject.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
serverProtocolConfig.awgData.responsePacketJunkSize = protocolConfigObject.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
serverProtocolConfig.awgData.cookieReplyPacketJunkSize = protocolConfigObject.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
serverProtocolConfig.awgData.transportPacketJunkSize = protocolConfigObject.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
serverProtocolConfig.awgData.initPacketMagicHeader = protocolConfigObject.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
serverProtocolConfig.awgData.responsePacketMagicHeader = protocolConfigObject.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader);
serverProtocolConfig.awgData.underloadPacketMagicHeader = protocolConfigObject.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
serverProtocolConfig.awgData.transportPacketMagicHeader = protocolConfigObject.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
auto clientProtocolString = protocolConfigObject.value(config_key::last_config).toString();
if (!clientProtocolString.isEmpty()) {
clientProtocolConfig.isEmpty = false;
QJsonObject clientProtocolConfigObject = QJsonDocument::fromJson(clientProtocolString.toUtf8()).object();
clientProtocolConfig.awgData.junkPacketCount = clientProtocolConfigObject.value(config_key::junkPacketCount).toString();
clientProtocolConfig.awgData.junkPacketMinSize = clientProtocolConfigObject.value(config_key::junkPacketMinSize).toString();
clientProtocolConfig.awgData.junkPacketMaxSize = clientProtocolConfigObject.value(config_key::junkPacketMaxSize).toString();
clientProtocolConfig.awgData.initPacketJunkSize = clientProtocolConfigObject.value(config_key::initPacketJunkSize).toString();
clientProtocolConfig.awgData.responsePacketJunkSize = clientProtocolConfigObject.value(config_key::responsePacketJunkSize).toString();
clientProtocolConfig.awgData.cookieReplyPacketJunkSize = clientProtocolConfigObject.value(config_key::cookieReplyPacketJunkSize).toString();
clientProtocolConfig.awgData.transportPacketJunkSize = clientProtocolConfigObject.value(config_key::transportPacketJunkSize).toString();
clientProtocolConfig.awgData.initPacketMagicHeader = clientProtocolConfigObject.value(config_key::initPacketMagicHeader).toString();
clientProtocolConfig.awgData.responsePacketMagicHeader =
clientProtocolConfigObject.value(config_key::responsePacketMagicHeader).toString();
clientProtocolConfig.awgData.underloadPacketMagicHeader =
clientProtocolConfigObject.value(config_key::underloadPacketMagicHeader).toString();
clientProtocolConfig.awgData.transportPacketMagicHeader =
clientProtocolConfigObject.value(config_key::transportPacketMagicHeader).toString();
clientProtocolConfig.clientId = clientProtocolConfigObject.value(config_key::clientId).toString();
clientProtocolConfig.wireGuardData.clientIp = clientProtocolConfigObject.value(config_key::client_ip).toString();
clientProtocolConfig.wireGuardData.clientPrivateKey = clientProtocolConfigObject.value(config_key::client_priv_key).toString();
clientProtocolConfig.wireGuardData.clientPublicKey = clientProtocolConfigObject.value(config_key::client_pub_key).toString();
clientProtocolConfig.wireGuardData.persistentKeepAlive =
clientProtocolConfigObject.value(config_key::persistent_keep_alive).toString();
clientProtocolConfig.wireGuardData.pskKey = clientProtocolConfigObject.value(config_key::psk_key).toString();
clientProtocolConfig.wireGuardData.serverPubKey = clientProtocolConfigObject.value(config_key::server_pub_key).toString();
clientProtocolConfig.wireGuardData.mtu = clientProtocolConfigObject.value(config_key::mtu).toString();
clientProtocolConfig.hostname = clientProtocolConfigObject.value(config_key::hostName).toString();
clientProtocolConfig.port = clientProtocolConfigObject.value(config_key::port).toInt(0);
clientProtocolConfig.nativeConfig = clientProtocolConfigObject.value(config_key::config).toString();
if (clientProtocolConfigObject.contains(config_key::allowed_ips)
&& clientProtocolConfigObject.value(config_key::allowed_ips).isArray()) {
auto allowedIpsArray = clientProtocolConfigObject.value(config_key::allowed_ips).toArray();
for (const auto &ip : allowedIpsArray) {
clientProtocolConfig.wireGuardData.allowedIps.append(ip.toString());
}
}
}
}
AwgProtocolConfig::AwgProtocolConfig(const AwgProtocolConfig &other) : ProtocolConfig(other.protocolName)
{
serverProtocolConfig = other.serverProtocolConfig;
clientProtocolConfig = other.clientProtocolConfig;
}
QJsonObject AwgProtocolConfig::toJson() const
{
QJsonObject json;
if (!serverProtocolConfig.port.isEmpty()) {
json[config_key::port] = serverProtocolConfig.port;
}
if (!serverProtocolConfig.transportProto.isEmpty()) {
json[config_key::transport_proto] = serverProtocolConfig.transportProto;
}
if (!serverProtocolConfig.subnetAddress.isEmpty()) {
json[config_key::subnet_address] = serverProtocolConfig.subnetAddress;
}
if (!serverProtocolConfig.mtu.isEmpty()) {
json[config_key::mtu] = serverProtocolConfig.mtu;
}
if (!serverProtocolConfig.awgData.junkPacketCount.isEmpty()) {
json[config_key::junkPacketCount] = serverProtocolConfig.awgData.junkPacketCount;
}
if (!serverProtocolConfig.awgData.junkPacketMinSize.isEmpty()) {
json[config_key::junkPacketMinSize] = serverProtocolConfig.awgData.junkPacketMinSize;
}
if (!serverProtocolConfig.awgData.junkPacketMaxSize.isEmpty()) {
json[config_key::junkPacketMaxSize] = serverProtocolConfig.awgData.junkPacketMaxSize;
}
if (!serverProtocolConfig.awgData.initPacketJunkSize.isEmpty()) {
json[config_key::initPacketJunkSize] = serverProtocolConfig.awgData.initPacketJunkSize;
}
if (!serverProtocolConfig.awgData.responsePacketJunkSize.isEmpty()) {
json[config_key::responsePacketJunkSize] = serverProtocolConfig.awgData.responsePacketJunkSize;
}
if (!serverProtocolConfig.awgData.cookieReplyPacketJunkSize.isEmpty()) {
json[config_key::cookieReplyPacketJunkSize] = serverProtocolConfig.awgData.cookieReplyPacketJunkSize;
}
if (!serverProtocolConfig.awgData.transportPacketJunkSize.isEmpty()) {
json[config_key::transportPacketJunkSize] = serverProtocolConfig.awgData.transportPacketJunkSize;
}
if (!serverProtocolConfig.awgData.initPacketMagicHeader.isEmpty()) {
json[config_key::initPacketMagicHeader] = serverProtocolConfig.awgData.initPacketMagicHeader;
}
if (!serverProtocolConfig.awgData.responsePacketMagicHeader.isEmpty()) {
json[config_key::responsePacketMagicHeader] = serverProtocolConfig.awgData.responsePacketMagicHeader;
}
if (!serverProtocolConfig.awgData.underloadPacketMagicHeader.isEmpty()) {
json[config_key::underloadPacketMagicHeader] = serverProtocolConfig.awgData.underloadPacketMagicHeader;
}
if (!serverProtocolConfig.awgData.transportPacketMagicHeader.isEmpty()) {
json[config_key::transportPacketMagicHeader] = serverProtocolConfig.awgData.transportPacketMagicHeader;
}
if (!clientProtocolConfig.isEmpty) {
QJsonObject clientConfigJson;
if (!clientProtocolConfig.clientId.isEmpty()) {
clientConfigJson[config_key::clientId] = clientProtocolConfig.clientId;
}
if (!clientProtocolConfig.awgData.junkPacketCount.isEmpty()) {
clientConfigJson[config_key::junkPacketCount] = clientProtocolConfig.awgData.junkPacketCount;
}
if (!clientProtocolConfig.awgData.junkPacketMinSize.isEmpty()) {
clientConfigJson[config_key::junkPacketMinSize] = clientProtocolConfig.awgData.junkPacketMinSize;
}
if (!clientProtocolConfig.awgData.junkPacketMaxSize.isEmpty()) {
clientConfigJson[config_key::junkPacketMaxSize] = clientProtocolConfig.awgData.junkPacketMaxSize;
}
if (!clientProtocolConfig.awgData.initPacketJunkSize.isEmpty()) {
clientConfigJson[config_key::initPacketJunkSize] = clientProtocolConfig.awgData.initPacketJunkSize;
}
if (!clientProtocolConfig.awgData.responsePacketJunkSize.isEmpty()) {
clientConfigJson[config_key::responsePacketJunkSize] = clientProtocolConfig.awgData.responsePacketJunkSize;
}
if (!clientProtocolConfig.awgData.initPacketMagicHeader.isEmpty()) {
clientConfigJson[config_key::initPacketMagicHeader] = clientProtocolConfig.awgData.initPacketMagicHeader;
}
if (!clientProtocolConfig.awgData.responsePacketMagicHeader.isEmpty()) {
clientConfigJson[config_key::responsePacketMagicHeader] = clientProtocolConfig.awgData.responsePacketMagicHeader;
}
if (!clientProtocolConfig.awgData.underloadPacketMagicHeader.isEmpty()) {
clientConfigJson[config_key::underloadPacketMagicHeader] = clientProtocolConfig.awgData.underloadPacketMagicHeader;
}
if (!clientProtocolConfig.awgData.transportPacketMagicHeader.isEmpty()) {
clientConfigJson[config_key::transportPacketMagicHeader] = clientProtocolConfig.awgData.transportPacketMagicHeader;
}
if (!clientProtocolConfig.wireGuardData.clientIp.isEmpty()) {
clientConfigJson[config_key::client_ip] = clientProtocolConfig.wireGuardData.clientIp;
}
if (!clientProtocolConfig.wireGuardData.clientPrivateKey.isEmpty()) {
clientConfigJson[config_key::client_priv_key] = clientProtocolConfig.wireGuardData.clientPrivateKey;
}
if (!clientProtocolConfig.wireGuardData.clientPublicKey.isEmpty()) {
clientConfigJson[config_key::client_pub_key] = clientProtocolConfig.wireGuardData.clientPublicKey;
}
if (!clientProtocolConfig.wireGuardData.persistentKeepAlive.isEmpty()) {
clientConfigJson[config_key::persistent_keep_alive] = clientProtocolConfig.wireGuardData.persistentKeepAlive;
}
if (!clientProtocolConfig.wireGuardData.pskKey.isEmpty()) {
clientConfigJson[config_key::psk_key] = clientProtocolConfig.wireGuardData.pskKey;
}
if (!clientProtocolConfig.wireGuardData.serverPubKey.isEmpty()) {
clientConfigJson[config_key::server_pub_key] = clientProtocolConfig.wireGuardData.serverPubKey;
}
if (!clientProtocolConfig.wireGuardData.mtu.isEmpty()) {
clientConfigJson[config_key::mtu] = clientProtocolConfig.wireGuardData.mtu;
}
if (!clientProtocolConfig.wireGuardData.allowedIps.isEmpty()) {
QJsonArray allowedIpsArray;
for (const auto &ip : clientProtocolConfig.wireGuardData.allowedIps) {
if (!ip.isEmpty()) {
allowedIpsArray.append(ip);
}
}
if (!allowedIpsArray.isEmpty()) {
clientConfigJson[config_key::allowed_ips] = allowedIpsArray;
}
}
if (!clientProtocolConfig.hostname.isEmpty()) {
clientConfigJson[config_key::hostName] = clientProtocolConfig.hostname;
}
if (clientProtocolConfig.port) {
clientConfigJson[config_key::port] = clientProtocolConfig.port;
}
if (!clientProtocolConfig.nativeConfig.isEmpty()) {
clientConfigJson[config_key::config] = clientProtocolConfig.nativeConfig;
}
if (!clientConfigJson.isEmpty()) {
json[config_key::last_config] = QString(QJsonDocument(clientConfigJson).toJson(QJsonDocument::Compact));
}
}
return json;
}
bool AwgProtocolConfig::hasEqualServerSettings(const AwgProtocolConfig &other) const
{
if (serverProtocolConfig.subnetAddress != other.serverProtocolConfig.subnetAddress
|| serverProtocolConfig.port != other.serverProtocolConfig.port
|| serverProtocolConfig.awgData.junkPacketCount != other.serverProtocolConfig.awgData.junkPacketCount
|| serverProtocolConfig.awgData.junkPacketMinSize != other.serverProtocolConfig.awgData.junkPacketMinSize
|| serverProtocolConfig.awgData.junkPacketMaxSize != other.serverProtocolConfig.awgData.junkPacketMaxSize
|| serverProtocolConfig.awgData.initPacketJunkSize != other.serverProtocolConfig.awgData.initPacketJunkSize
|| serverProtocolConfig.awgData.responsePacketJunkSize != other.serverProtocolConfig.awgData.responsePacketJunkSize
|| serverProtocolConfig.awgData.initPacketMagicHeader != other.serverProtocolConfig.awgData.initPacketMagicHeader
|| serverProtocolConfig.awgData.responsePacketMagicHeader != other.serverProtocolConfig.awgData.responsePacketMagicHeader
|| serverProtocolConfig.awgData.underloadPacketMagicHeader != other.serverProtocolConfig.awgData.underloadPacketMagicHeader
|| serverProtocolConfig.awgData.transportPacketMagicHeader != other.serverProtocolConfig.awgData.transportPacketMagicHeader) {
return false;
}
return true;
}
bool AwgProtocolConfig::hasEqualClientSettings(const AwgProtocolConfig &other) const
{
if (clientProtocolConfig.wireGuardData.mtu != other.clientProtocolConfig.wireGuardData.mtu
|| clientProtocolConfig.awgData.junkPacketCount != other.clientProtocolConfig.awgData.junkPacketCount
|| clientProtocolConfig.awgData.junkPacketMinSize != other.clientProtocolConfig.awgData.junkPacketMinSize
|| clientProtocolConfig.awgData.junkPacketMaxSize != other.clientProtocolConfig.awgData.junkPacketMaxSize) {
return false;
}
return true;
}
void AwgProtocolConfig::clearClientSettings()
{
clientProtocolConfig = awg::ClientProtocolConfig();
}

View File

@@ -0,0 +1,76 @@
#ifndef AWGPROTOCOLCONFIG_H
#define AWGPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QStringList>
#include "protocolConfig.h"
#include "wireguardProtocolConfig.h"
namespace awg
{
struct AwgData
{
QString junkPacketCount;
QString junkPacketMinSize;
QString junkPacketMaxSize;
QString initPacketJunkSize;
QString responsePacketJunkSize;
QString cookieReplyPacketJunkSize;
QString transportPacketJunkSize;
QString initPacketMagicHeader;
QString responsePacketMagicHeader;
QString underloadPacketMagicHeader;
QString transportPacketMagicHeader;
};
struct ServerProtocolConfig
{
QString port;
QString transportProto;
QString subnetAddress;
AwgData awgData;
};
struct ClientProtocolConfig
{
bool isEmpty = true;
QString clientId;
wireguard::WireGuardData wireGuardData;
AwgData awgData;
QString hostname;
int port;
QString nativeConfig;
};
const int messageInitiationSize = 148;
const int messageResponseSize = 92;
}
class AwgProtocolConfig : public ProtocolConfig
{
public:
AwgProtocolConfig(const QString &protocolName);
AwgProtocolConfig(const QJsonObject &protocolConfigObject, const QString &protocolName);
AwgProtocolConfig(const AwgProtocolConfig &other);
QJsonObject toJson() const override;
bool hasEqualServerSettings(const AwgProtocolConfig &other) const;
bool hasEqualClientSettings(const AwgProtocolConfig &other) const;
void clearClientSettings();
awg::ServerProtocolConfig serverProtocolConfig;
awg::ClientProtocolConfig clientProtocolConfig;
};
#endif // AWGPROTOCOLCONFIG_H

View File

@@ -0,0 +1,58 @@
#include "cloakProtocolConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
using namespace amnezia;
CloakProtocolConfig::CloakProtocolConfig(const QJsonObject &protocolConfigObject, const QString &protocolName) : ProtocolConfig(protocolName)
{
serverProtocolConfig.port = protocolConfigObject.value(config_key::port).toString(protocols::cloak::defaultPort);
serverProtocolConfig.cipher = protocolConfigObject.value(config_key::cipher).toString(protocols::cloak::defaultCipher);
serverProtocolConfig.site = protocolConfigObject.value(config_key::site).toString(protocols::cloak::defaultRedirSite);
auto clientProtocolString = protocolConfigObject.value(config_key::last_config).toString();
if (!clientProtocolString.isEmpty()) {
clientProtocolConfig.isEmpty = false;
QJsonObject clientProtocolConfigObject = QJsonDocument::fromJson(clientProtocolString.toUtf8()).object();
}
}
QJsonObject CloakProtocolConfig::toJson() const
{
QJsonObject json;
if (!serverProtocolConfig.port.isEmpty()) {
json[config_key::port] = serverProtocolConfig.port;
}
if (!serverProtocolConfig.cipher.isEmpty()) {
json[config_key::cipher] = serverProtocolConfig.cipher;
}
if (!serverProtocolConfig.site.isEmpty()) {
json[config_key::site] = serverProtocolConfig.site;
}
if (!clientProtocolConfig.isEmpty) {
QJsonObject clientConfigJson;
json[config_key::last_config] = QString(QJsonDocument(clientConfigJson).toJson());
}
return json;
}
bool CloakProtocolConfig::hasEqualServerSettings(const CloakProtocolConfig &other) const
{
if (serverProtocolConfig.port != other.serverProtocolConfig.port ||
serverProtocolConfig.cipher != other.serverProtocolConfig.cipher ||
serverProtocolConfig.site != other.serverProtocolConfig.site) {
return false;
}
return true;
}
void CloakProtocolConfig::clearClientSettings()
{
clientProtocolConfig = cloak::ClientProtocolConfig();
}

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