mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-08-22 11:09:30 +03:00
Compare commits
1 Commits
feat/split
...
fix/androi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f07bc2132e |
193
.github/workflows/deploy.yml
vendored
193
.github/workflows/deploy.yml
vendored
@@ -18,17 +18,14 @@ jobs:
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
base: ${{ github.event.before }}
|
||||
filters: |
|
||||
recipes:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
@@ -43,7 +40,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
shell: bash
|
||||
@@ -53,17 +50,15 @@ jobs:
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Linux
|
||||
if: ${{ always() }}
|
||||
@@ -103,7 +98,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Install system packages'
|
||||
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
@@ -123,7 +118,7 @@ jobs:
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_linux_x64.run
|
||||
path: deploy/build/AmneziaVPN-*-Linux.run
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
@@ -154,17 +149,15 @@ jobs:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -236,7 +229,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
@@ -249,31 +242,27 @@ jobs:
|
||||
- name: 'Upload WIX installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.msi
|
||||
path: deploy/build/AmneziaVPN-*-win64.msi
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload IFW installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.exe
|
||||
path: deploy/build/AmneziaVPN-*-win64.exe
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-iOS:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [26.0, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
xcode-version: [26.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -290,17 +279,15 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -357,7 +344,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.0'
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -389,7 +376,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install deps'
|
||||
run: pip install "conan==2.28.0" jsonschema jinja2
|
||||
run: pip install "conan==2.26.2" jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
@@ -407,21 +394,14 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 16.2
|
||||
os: macos-15
|
||||
- xcode-version: 16.4
|
||||
os: macos-15
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
xcode-version: [16.2, 16.4]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -438,23 +418,21 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-15
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS
|
||||
if: ${{ always() }}
|
||||
@@ -501,7 +479,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.3.0'
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -524,15 +502,13 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
|
||||
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
CODESIGN_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
CODESIGN_INSTALLER_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
@@ -542,28 +518,20 @@ jobs:
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_macos_x64.pkg
|
||||
path: deploy/build/AmneziaVPN-*-Darwin.pkg
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 16.2
|
||||
os: macos-15
|
||||
- xcode-version: 16.4
|
||||
os: macos-15
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
xcode-version: [16.2, 16.4]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -580,23 +548,21 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-26
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS-NE
|
||||
if: ${{ always() }}
|
||||
@@ -620,12 +586,6 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
|
||||
@@ -663,13 +623,19 @@ jobs:
|
||||
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 python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
@@ -705,7 +671,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
@@ -730,11 +696,9 @@ jobs:
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -745,37 +709,11 @@ jobs:
|
||||
needs: Bake-Prebuilts-Android
|
||||
if: ${{ always() }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- label: 'android9-10'
|
||||
qt_version: '6.10.1'
|
||||
min_sdk: 28
|
||||
max_sdk: 29
|
||||
version_code_offset: 0
|
||||
build_aab: true
|
||||
- label: 'android11+'
|
||||
qt_version: '6.10.3'
|
||||
min_sdk: 30
|
||||
max_sdk: ''
|
||||
version_code_offset: 1
|
||||
build_aab: true
|
||||
- label: 'android9-10-qt610-3'
|
||||
qt_version: '6.10.3'
|
||||
min_sdk: 28
|
||||
max_sdk: 29
|
||||
version_code_offset: 0
|
||||
build_aab: false
|
||||
|
||||
env:
|
||||
ANDROID_PLATFORM: android-${{ matrix.min_sdk }}
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: ${{ matrix.qt_version }}
|
||||
QT_VERSION: 6.10.1
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
APP_ANDROID_MIN_SDK: ${{ matrix.min_sdk }}
|
||||
APP_ANDROID_MAX_SDK: ${{ matrix.max_sdk }}
|
||||
APP_ANDROID_VERSION_CODE_OFFSET: ${{ matrix.version_code_offset }}
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
@@ -868,7 +806,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
@@ -883,82 +821,62 @@ jobs:
|
||||
QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
LABEL: ${{ matrix.label }}
|
||||
BUILD_AAB: ${{ matrix.build_aab }}
|
||||
shell: bash
|
||||
run: |
|
||||
OUT=deploy/artifacts
|
||||
rm -rf $OUT && mkdir -p $OUT
|
||||
|
||||
if [[ "$BUILD_AAB" == "true" ]]; then
|
||||
deploy/build.sh -t android --sign --aab
|
||||
else
|
||||
deploy/build.sh -t android --sign
|
||||
fi
|
||||
deploy/build.sh -t android --sign --aab
|
||||
|
||||
VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC deploy/build/CMakeCache.txt | cut -d= -f2)
|
||||
|
||||
mv deploy/build/client/android-build/AmneziaVPN.apk \
|
||||
$OUT/AmneziaVPN_${VERSION}_${LABEL}_universal.apk
|
||||
|
||||
if [[ "$BUILD_AAB" == "true" ]]; then
|
||||
mv deploy/build/client/android-build/build/outputs/bundle/release/android-build-release.aab \
|
||||
$OUT/AmneziaVPN_${VERSION}_${LABEL}.aab
|
||||
fi
|
||||
(cd deploy/build/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_universal.apk)
|
||||
(cd deploy/build/client/android-build/build/outputs/bundle/release && mv android-build-release.aab AmneziaVPN_${VERSION}.aab)
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
|
||||
deploy/build.sh -t android --sign --abi ${abi} --build ./deploy/build/${abi}
|
||||
mv deploy/build/${abi}/client/android-build/AmneziaVPN.apk \
|
||||
$OUT/AmneziaVPN_${VERSION}_${LABEL}_${abi}.apk
|
||||
(cd deploy/build/${abi}/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_${abi}.apk)
|
||||
done
|
||||
|
||||
ls -l $OUT
|
||||
|
||||
- name: 'Upload universal APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*_universal.apk
|
||||
path: deploy/build/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload AAB'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/client/android-build/build/outputs/bundle/release/*.aab
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*_arm64-v8a.apk
|
||||
path: deploy/build/arm64-v8a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*_armeabi-v7a.apk
|
||||
path: deploy/build/armeabi-v7a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*_x86.apk
|
||||
path: deploy/build/x86/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86_64 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*_x86_64.apk
|
||||
path: deploy/build/x86_64/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload AAB'
|
||||
if: ${{ matrix.build_aab }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/artifacts/*.aab
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Extra:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -976,4 +894,3 @@ jobs:
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,8 +10,6 @@ deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
.venv/
|
||||
.cursor*
|
||||
|
||||
|
||||
# Qt-es
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -7,6 +7,9 @@
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
[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
|
||||
|
||||
@@ -4,9 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
if(NOT AMNEZIAVPN_VERSION)
|
||||
set(AMNEZIAVPN_VERSION 5.0.1.1 CACHE STRING "Client app version")
|
||||
endif()
|
||||
set(AMNEZIAVPN_VERSION 4.8.15.4)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -20,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -30,12 +28,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})
|
||||
|
||||
# bump by 2 on every release, because we're releasing two versions on the play store
|
||||
set(APP_ANDROID_VERSION_CODE 2148)
|
||||
if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET)
|
||||
math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}")
|
||||
endif()
|
||||
set(APP_ANDROID_VERSION_CODE 2120)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -61,14 +54,14 @@ if(APPLE AND NOT IOS)
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
file(COPY_FILE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
|
||||
ONLY_IF_DIFFERENT
|
||||
)
|
||||
endif()
|
||||
|
||||
14
README.md
14
README.md
@@ -38,14 +38,14 @@
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Documentation | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/docs?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [Get Premium for 6 or 12 months](https://storage.googleapis.com/amnezia/pay?utm_source=github&utm_campaign=ampay-read)
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||
|
||||
## Tech
|
||||
|
||||
|
||||
16
README_RU.md
16
README_RU.md
@@ -28,21 +28,21 @@
|
||||
|
||||
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) и XRay.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Документация | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/docs?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [Оформите Premium на 6 или 12 месяцев](https://storage.googleapis.com/amnezia/pay?utm_source=github&utm_campaign=ampay-read)
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
|
||||
|
||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
1
client/3rd/QSimpleCrypto
vendored
Submodule
Submodule client/3rd/QSimpleCrypto added at c99b33f0e0
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
Submodule client/3rd/amneziawg-apple updated: fe2e2d7ebb...cf63135331
@@ -1,8 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/clientBrandingDefaults.cmake)
|
||||
|
||||
set(PROJECT ${CLIENT_TARGET_NAME})
|
||||
set(PROJECT AmneziaVPN)
|
||||
project(${PROJECT})
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
@@ -41,7 +39,6 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Core Qt6::Gui
|
||||
@@ -59,9 +56,6 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||
target_include_directories(${PROJECT} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
target_compile_definitions(${PROJECT} PRIVATE
|
||||
CLIENT_KEYCHAIN_NAME_STR=\"${CLIENT_KEYCHAIN_NAME}\"
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
@@ -72,34 +66,24 @@ qt6_add_resources(QRC ${QRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/client_scripts/clientScripts.qrc
|
||||
${CLIENT_QML_QRC_FILE}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
|
||||
${CLIENT_EXTRA_QRC}
|
||||
)
|
||||
|
||||
# -- i18n begin
|
||||
if(CLIENT_TS_FILES)
|
||||
set(AMNEZIAVPN_TS_FILES ${CLIENT_TS_FILES})
|
||||
else()
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ru_RU.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_zh_CN.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_fa_IR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ar_EG.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_my_MM.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_uk_UA.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ur_PK.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_hi_IN.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_es_ES.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ko_KR.ts
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_QML_FILES ${CMAKE_CURRENT_LIST_DIR}/ui/qml/*.qml)
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
||||
)
|
||||
|
||||
qt6_add_translations(${PROJECT}
|
||||
TS_FILES ${AMNEZIAVPN_TS_FILES}
|
||||
SOURCES ${AMNEZIAVPN_QML_FILES}
|
||||
RESOURCE_PREFIX "/translations"
|
||||
)
|
||||
# -- i18n end
|
||||
@@ -116,18 +100,12 @@ endif()
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||
|
||||
set(_client_include_dirs
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/crypto
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
if(CLIENT_BRANDING_INCLUDE_DIR)
|
||||
list(APPEND _client_include_dirs ${CLIENT_BRANDING_INCLUDE_DIR})
|
||||
endif()
|
||||
list(APPEND _client_include_dirs ${CMAKE_CURRENT_LIST_DIR})
|
||||
include_directories(${_client_include_dirs})
|
||||
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
@@ -143,26 +121,6 @@ include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/core/utils)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/utils/appUiConfig.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/core/utils/appUiConfig.h
|
||||
@ONLY
|
||||
)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/utils/swiftBridgeConfig.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/core/utils/swiftBridgeConfig.h
|
||||
@ONLY
|
||||
)
|
||||
# Swift can't consume a `-DGROUP_ID="value"` compile define (Swift -D flags are
|
||||
# presence-only), unlike the Objective-C++ sources that read GROUP_ID directly.
|
||||
# Generate a Swift constant instead so Log.swift can use the branded app group.
|
||||
set(CLIENT_GROUP_IDENTIFIER_SWIFT_FILE ${CMAKE_CURRENT_BINARY_DIR}/platforms/ios/GroupIdentifier.swift)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/GroupIdentifier.swift.in
|
||||
${CLIENT_GROUP_IDENTIFIER_SWIFT_FILE}
|
||||
@ONLY
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||
@@ -215,7 +173,6 @@ endif()
|
||||
if(LINUX AND NOT ANDROID)
|
||||
set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
set(LIBS ${LIBS} -Wl,--no-as-needed $<TARGET_LINKER_FILE:Qt6::RemoteObjects> -Wl,--as-needed)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
@@ -236,6 +193,10 @@ elseif(APPLE)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
@@ -255,32 +216,11 @@ endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET client_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^(lib)?[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
@@ -300,10 +240,6 @@ install(SCRIPT ${QT_DEPLOY_SCRIPT}
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
|
||||
)
|
||||
endif()
|
||||
if (LINUX AND NOT ANDROID)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
|
||||
|
||||
@@ -22,14 +22,9 @@
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
#include "ui/utils/mtProxyPublicHostInput.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/appUiConfig.h"
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ioscontextmenu.h"
|
||||
#endif
|
||||
|
||||
|
||||
bool AmneziaApplication::m_forceQuit = false;
|
||||
@@ -103,7 +98,7 @@ void AmneziaApplication::init()
|
||||
{
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
|
||||
const QUrl url(QStringLiteral(APP_QML_ENTRYPOINT));
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[this, url](QObject *obj, const QUrl &objUrl) {
|
||||
@@ -124,13 +119,7 @@ void AmneziaApplication::init()
|
||||
win->setPersistentSceneGraph(true);
|
||||
win->setPersistentGraphics(true);
|
||||
#endif
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
win->show();
|
||||
#else
|
||||
if (!m_coreController || !m_coreController->pageController()->shouldStartMinimized()) {
|
||||
win->show();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
@@ -143,20 +132,13 @@ void AmneziaApplication::init()
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
m_engine->rootContext()->setContextProperty("IosContextMenu", new IosContextMenu(this));
|
||||
#endif
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||
|
||||
m_marketplaceUpdateController.reset(new MarketplaceUpdateController());
|
||||
m_marketplaceUpdateController->start();
|
||||
|
||||
m_engine->addImportPath(QStringLiteral(APP_QML_IMPORT_PATH));
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
|
||||
if (m_parser.isSet(m_optImport)) {
|
||||
const QString data = m_parser.value(m_optImport);
|
||||
@@ -233,9 +215,6 @@ void AmneziaApplication::registerTypes()
|
||||
|
||||
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||
|
||||
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
|
||||
qmlRegisterType<PublicHostInputValidator>("TelemtConfig", 1, 0, "PublicHostInputValidator");
|
||||
|
||||
amnezia::declareQmlProtocolEnum();
|
||||
Vpn::declareQmlVpnConnectionStateEnum();
|
||||
PageLoader::declareQmlPageEnum();
|
||||
@@ -245,7 +224,7 @@ void AmneziaApplication::loadFonts()
|
||||
{
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
QFontDatabase::addApplicationFont(QStringLiteral(APP_UI_FONT_RESOURCE));
|
||||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||
}
|
||||
|
||||
bool AmneziaApplication::parseCommands()
|
||||
@@ -272,7 +251,7 @@ bool AmneziaApplication::parseCommands()
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName(APP_INSTANCE_NAME);
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer *server = new QLocalServer(this);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "secureQSettings.h"
|
||||
#include "ui/controllers/marketplaceUpdateController.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/models/containerProps.h"
|
||||
#include "ui/models/protocolProps.h"
|
||||
@@ -57,7 +56,6 @@ private:
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QScopedPointer<CoreController> m_coreController;
|
||||
QScopedPointer<MarketplaceUpdateController> m_marketplaceUpdateController;
|
||||
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:angle="135"
|
||||
android:startColor="#2A2A2E"
|
||||
android:centerColor="#17171A"
|
||||
android:endColor="#0E0E11" />
|
||||
</shape>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/ic_amnezia_round"
|
||||
android:insetLeft="19.5%"
|
||||
android:insetTop="19.5%"
|
||||
android:insetRight="19.5%"
|
||||
android:insetBottom="19.5%" />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="NoActionBar">
|
||||
<item name="android:windowBackground">@color/black</item>
|
||||
<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>
|
||||
|
||||
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/icon</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0E0E11</color>
|
||||
</resources>
|
||||
@@ -3,13 +3,6 @@ package org.amnezia.vpn
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.Gravity
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.app.NotificationManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -49,7 +42,6 @@ import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -107,8 +99,6 @@ class AmneziaActivity : QtActivity() {
|
||||
private var pendingOpenFileUri: String? = null
|
||||
private var openFileDeliveryScheduled = false
|
||||
|
||||
private var updateCoverDialog: Dialog? = null
|
||||
|
||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||
object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
@@ -496,116 +486,6 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
fun showUpdateCover() {
|
||||
runOnUiThread {
|
||||
if (isFinishing || isDestroyed || updateCoverDialog != null) return@runOnUiThread
|
||||
val dialog = Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
|
||||
dialog.setCancelable(false)
|
||||
val root = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundColor(0xFF0E0E11.toInt())
|
||||
}
|
||||
dialog.setContentView(root)
|
||||
dialog.show()
|
||||
updateCoverDialog = dialog
|
||||
}
|
||||
}
|
||||
|
||||
fun hideUpdateCover() {
|
||||
runOnUiThread {
|
||||
updateCoverDialog?.dismiss()
|
||||
updateCoverDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
fun showUpdatePrompt(title: String, message: String, updateTitle: String, skipTitle: String, storeUrl: String) {
|
||||
runOnUiThread {
|
||||
if (isFinishing || isDestroyed) return@runOnUiThread
|
||||
|
||||
val dialog = updateCoverDialog ?: Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen).also {
|
||||
it.setCancelable(false)
|
||||
it.show()
|
||||
updateCoverDialog = it
|
||||
}
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
fun dp(value: Int) = (value * density).toInt()
|
||||
|
||||
val root = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundColor(0xFF0E0E11.toInt())
|
||||
setPadding(dp(32), dp(32), dp(32), dp(32))
|
||||
}
|
||||
|
||||
val titleView = TextView(this).apply {
|
||||
text = title
|
||||
textSize = 22f
|
||||
setTextColor(0xFFFFFFFF.toInt())
|
||||
gravity = Gravity.CENTER
|
||||
typeface = Typeface.create(typeface, Typeface.BOLD)
|
||||
}
|
||||
|
||||
val messageView = TextView(this).apply {
|
||||
text = message
|
||||
textSize = 16f
|
||||
setTextColor(0xFFC7C8CB.toInt())
|
||||
gravity = Gravity.CENTER
|
||||
setPadding(0, dp(16), 0, dp(28))
|
||||
}
|
||||
|
||||
val updateButton = Button(this).apply {
|
||||
text = updateTitle
|
||||
isAllCaps = false
|
||||
textSize = 17f
|
||||
setTextColor(0xFF0E0E11.toInt())
|
||||
stateListAnimator = null
|
||||
background = GradientDrawable().apply {
|
||||
cornerRadius = dp(12).toFloat()
|
||||
setColor(0xFFFBB26A.toInt())
|
||||
}
|
||||
setOnClickListener {
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(storeUrl)))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "open store failed: ${e.message}")
|
||||
}
|
||||
hideUpdateCover()
|
||||
}
|
||||
}
|
||||
|
||||
val skipButton = Button(this).apply {
|
||||
text = skipTitle
|
||||
isAllCaps = false
|
||||
textSize = 17f
|
||||
setTextColor(0xFFD7D8DB.toInt())
|
||||
stateListAnimator = null
|
||||
background = GradientDrawable().apply {
|
||||
cornerRadius = dp(12).toFloat()
|
||||
setColor(0x00000000)
|
||||
setStroke(dp(1), 0xFF2C2D30.toInt())
|
||||
}
|
||||
setOnClickListener { hideUpdateCover() }
|
||||
}
|
||||
|
||||
val updateParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
|
||||
).apply { topMargin = dp(8) }
|
||||
|
||||
val skipParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
|
||||
).apply { topMargin = dp(12) }
|
||||
|
||||
root.addView(titleView)
|
||||
root.addView(messageView)
|
||||
root.addView(updateButton, updateParams)
|
||||
root.addView(skipButton, skipParams)
|
||||
|
||||
dialog.setContentView(root)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
|
||||
"resultCode: $resultCode, data: $data")
|
||||
@@ -883,13 +763,7 @@ class AmneziaActivity : QtActivity() {
|
||||
fun openFile(filter: String?) {
|
||||
Log.v(TAG, "Open file with filter: $filter")
|
||||
mainScope.launch {
|
||||
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
|
||||
.firstOrNull { pkg ->
|
||||
try { packageManager.getPackageInfo(pkg, 0); true }
|
||||
catch (_: PackageManager.NameNotFoundException) { false }
|
||||
}
|
||||
|
||||
val intent = if (!isOnTv() && systemPickerPackage != null) {
|
||||
val intent = if (!isOnTv()) {
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
@@ -915,7 +789,6 @@ class AmneziaActivity : QtActivity() {
|
||||
else -> type = "*/*"
|
||||
}
|
||||
}
|
||||
`package` = systemPickerPackage
|
||||
}
|
||||
} else {
|
||||
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||
@@ -927,11 +800,8 @@ class AmneziaActivity : QtActivity() {
|
||||
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||
showNoFileBrowserAlertDialog()
|
||||
}
|
||||
val uri = it?.data?.let { u ->
|
||||
if (u.scheme == "content") {
|
||||
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
|
||||
}
|
||||
u
|
||||
val uri = it?.data?.apply {
|
||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}?.toString() ?: ""
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
if (uri.isNotEmpty()) {
|
||||
@@ -971,12 +841,7 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.v(TAG, "Get fd for $fileName")
|
||||
return blockingCall(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(fileName)
|
||||
pfd = if (uri.scheme == "file") {
|
||||
ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)
|
||||
} else {
|
||||
contentResolver.openFileDescriptor(uri, "r")
|
||||
}
|
||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||
pfd?.fd ?: -1
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get fd: $e")
|
||||
@@ -1196,10 +1061,12 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun sendTouch(x: Float, y: Float) {
|
||||
Log.v(TAG, "Send touch: $x, $y")
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
blockingCall {
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import org.amnezia.vpn.util.Log
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "TvFilePicker"
|
||||
private const val READ_STORAGE_REQUEST_CODE = 1001
|
||||
|
||||
class TvFilePicker : ComponentActivity() {
|
||||
|
||||
// SAF launcher for Android 10+ where File API is blocked by scoped storage
|
||||
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
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 (activities.all {
|
||||
if (activitiesToResolveIntent.all {
|
||||
val name = it.activityInfo.packageName
|
||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||
}) {
|
||||
@@ -38,140 +32,38 @@ class TvFilePicker : ComponentActivity() {
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}) { uri ->
|
||||
}) {
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = uri
|
||||
data = it
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
|
||||
private val directoryStack = ArrayDeque<File>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.v(TAG, "onCreate")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
launchSaf()
|
||||
} else {
|
||||
checkPermissionAndBrowse()
|
||||
}
|
||||
getFile()
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
navigateBack()
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent")
|
||||
getFile()
|
||||
}
|
||||
|
||||
private fun launchSaf() {
|
||||
private fun getFile() {
|
||||
try {
|
||||
safLauncher.launch(arrayOf("*/*"))
|
||||
Log.v(TAG, "getFile")
|
||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Log.w(TAG, "No SAF activity found")
|
||||
Log.w(TAG, "Activity not found")
|
||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "SAF launch failed: $e")
|
||||
Log.e(TAG, "Failed to get file: $e")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissionAndBrowse() {
|
||||
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_STORAGE_REQUEST_CODE)
|
||||
} else {
|
||||
showRootDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == READ_STORAGE_REQUEST_CODE &&
|
||||
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
||||
showRootDirectory()
|
||||
} else {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRootDirectory() {
|
||||
@Suppress("DEPRECATION")
|
||||
val primaryExternal = Environment.getExternalStorageDirectory()
|
||||
val storageDir = File("/storage")
|
||||
// Pre-seed stack with /storage so Back from primary storage goes there (USB drives etc.)
|
||||
if (storageDir.exists() && storageDir.canonicalPath != primaryExternal.canonicalPath) {
|
||||
directoryStack.addLast(storageDir)
|
||||
}
|
||||
showDirectory(primaryExternal)
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
if (directoryStack.size > 1) {
|
||||
directoryStack.removeLast()
|
||||
val parent = directoryStack.removeLast()
|
||||
showDirectory(parent)
|
||||
} else {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDirectory(dir: File) {
|
||||
directoryStack.addLast(dir)
|
||||
Log.v(TAG, "Showing directory: ${dir.absolutePath}")
|
||||
|
||||
val entries = try {
|
||||
dir.listFiles()
|
||||
?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
|
||||
?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to list directory: $e")
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val names = entries.map { if (it.isDirectory) "[${it.name}]" else it.name }.toTypedArray()
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
.setTitle(dir.absolutePath)
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
builder.setMessage("No files available")
|
||||
} else {
|
||||
builder.setItems(names) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
val selected = entries[which]
|
||||
if (selected.isDirectory) {
|
||||
showDirectory(selected)
|
||||
} else {
|
||||
Log.v(TAG, "Selected file: ${selected.absolutePath}")
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(selected)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (directoryStack.size > 1) {
|
||||
builder.setNegativeButton("↑ Back") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
navigateBack()
|
||||
}
|
||||
} else {
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
builder.setOnCancelListener {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ open class Wireguard : Protocol() {
|
||||
configExtensionParameters(configData)
|
||||
}
|
||||
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it) }
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
@@ -134,24 +134,6 @@ open class Wireguard : Protocol() {
|
||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||
configData.optStringOrNull("HeaderProtectionKey")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setHeaderProtectionKey(it.base64ToHex()) }
|
||||
configData.optStringOrNull("ContentPaddingAddition")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setContentPaddingAddition(it) }
|
||||
configData.optStringOrNull("RekeyAfterTime")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRekeyAfterTime(it) }
|
||||
configData.optStringOrNull("RekeyTimeout")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRekeyTimeout(it) }
|
||||
configData.optStringOrNull("RejectAfterTime")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRejectAfterTime(it) }
|
||||
configData.optStringOrNull("KeepaliveTimeout")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setKeepaliveTimeout(it) }
|
||||
configData.optStringOrNull("MaxHandshakeAttempts")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setMaxHandshakeAttempts(it) }
|
||||
configData.optStringOrNull("RandomTrailers")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRandomTrailers(it) }
|
||||
configData.optStringOrNull("DisableCookies")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setDisableCookies(it) }
|
||||
}
|
||||
|
||||
private fun start(
|
||||
|
||||
@@ -10,7 +10,7 @@ private const val WIREGUARD_DEFAULT_MTU = 1280
|
||||
open class WireguardConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val endpoint: InetEndpoint,
|
||||
val persistentKeepalive: String?,
|
||||
val persistentKeepalive: Int,
|
||||
val publicKeyHex: String,
|
||||
val preSharedKeyHex: String?,
|
||||
val privateKeyHex: String,
|
||||
@@ -31,15 +31,6 @@ open class WireguardConfig protected constructor(
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
val headerProtectionKeyHex: String?,
|
||||
val contentPaddingAddition: String?,
|
||||
val rekeyAfterTime: String?,
|
||||
val rekeyTimeout: String?,
|
||||
val rejectAfterTime: String?,
|
||||
val keepaliveTimeout: String?,
|
||||
val maxHandshakeAttempts: String?,
|
||||
val randomTrailers: String?,
|
||||
val disableCookies: String?,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -66,15 +57,6 @@ open class WireguardConfig protected constructor(
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.headerProtectionKeyHex,
|
||||
builder.contentPaddingAddition,
|
||||
builder.rekeyAfterTime,
|
||||
builder.rekeyTimeout,
|
||||
builder.rejectAfterTime,
|
||||
builder.keepaliveTimeout,
|
||||
builder.maxHandshakeAttempts,
|
||||
builder.randomTrailers,
|
||||
builder.disableCookies,
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -105,15 +87,6 @@ open class WireguardConfig protected constructor(
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
}
|
||||
headerProtectionKeyHex?.takeIf { it.isNotEmpty() }?.let { appendLine("header_protection_key=$it") }
|
||||
contentPaddingAddition?.takeIf { it.isNotEmpty() }?.let { appendLine("content_padding_addition=$it") }
|
||||
rekeyAfterTime?.takeIf { it.isNotEmpty() }?.let { appendLine("rekey_after_time=$it") }
|
||||
rekeyTimeout?.takeIf { it.isNotEmpty() }?.let { appendLine("rekey_timeout=$it") }
|
||||
rejectAfterTime?.takeIf { it.isNotEmpty() }?.let { appendLine("reject_after_time=$it") }
|
||||
keepaliveTimeout?.takeIf { it.isNotEmpty() }?.let { appendLine("keepalive_timeout=$it") }
|
||||
maxHandshakeAttempts?.takeIf { it.isNotEmpty() }?.let { appendLine("max_handshake_attempts=$it") }
|
||||
randomTrailers?.takeIf { it.isNotEmpty() }?.let { appendLine("random_trailers=${it.toUapiBool()}") }
|
||||
disableCookies?.takeIf { it.isNotEmpty() }?.let { appendLine("disable_cookies=${it.toUapiBool()}") }
|
||||
}
|
||||
|
||||
private fun validateProtocolExtensionParameters() {
|
||||
@@ -134,7 +107,7 @@ open class WireguardConfig protected constructor(
|
||||
appendLine("allowed_ip=${route.inetNetwork}")
|
||||
}
|
||||
appendLine("endpoint=$endpoint")
|
||||
if (!persistentKeepalive.isNullOrEmpty() && persistentKeepalive != "0")
|
||||
if (persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=$persistentKeepalive")
|
||||
if (preSharedKeyHex != null)
|
||||
appendLine("preshared_key=$preSharedKeyHex")
|
||||
@@ -144,7 +117,7 @@ open class WireguardConfig protected constructor(
|
||||
internal lateinit var endpoint: InetEndpoint
|
||||
private set
|
||||
|
||||
internal var persistentKeepalive: String? = null
|
||||
internal var persistentKeepalive: Int = 0
|
||||
private set
|
||||
|
||||
internal lateinit var publicKeyHex: String
|
||||
@@ -176,19 +149,10 @@ open class WireguardConfig protected constructor(
|
||||
internal var i3: String? = null
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
internal var headerProtectionKeyHex: String? = null
|
||||
internal var contentPaddingAddition: String? = null
|
||||
internal var rekeyAfterTime: String? = null
|
||||
internal var rekeyTimeout: String? = null
|
||||
internal var rejectAfterTime: String? = null
|
||||
internal var keepaliveTimeout: String? = null
|
||||
internal var maxHandshakeAttempts: String? = null
|
||||
internal var randomTrailers: String? = null
|
||||
internal var disableCookies: String? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
fun setPersistentKeepalive(persistentKeepalive: String) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
|
||||
fun setPublicKeyHex(publicKeyHex: String) = apply { this.publicKeyHex = publicKeyHex }
|
||||
|
||||
@@ -214,15 +178,6 @@ open class WireguardConfig protected constructor(
|
||||
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 setHeaderProtectionKey(headerProtectionKeyHex: String) = apply { this.headerProtectionKeyHex = headerProtectionKeyHex }
|
||||
fun setContentPaddingAddition(contentPaddingAddition: String) = apply { this.contentPaddingAddition = contentPaddingAddition }
|
||||
fun setRekeyAfterTime(rekeyAfterTime: String) = apply { this.rekeyAfterTime = rekeyAfterTime }
|
||||
fun setRekeyTimeout(rekeyTimeout: String) = apply { this.rekeyTimeout = rekeyTimeout }
|
||||
fun setRejectAfterTime(rejectAfterTime: String) = apply { this.rejectAfterTime = rejectAfterTime }
|
||||
fun setKeepaliveTimeout(keepaliveTimeout: String) = apply { this.keepaliveTimeout = keepaliveTimeout }
|
||||
fun setMaxHandshakeAttempts(maxHandshakeAttempts: String) = apply { this.maxHandshakeAttempts = maxHandshakeAttempts }
|
||||
fun setRandomTrailers(randomTrailers: String) = apply { this.randomTrailers = randomTrailers }
|
||||
fun setDisableCookies(disableCookies: String) = apply { this.disableCookies = disableCookies }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
@@ -234,9 +189,3 @@ open class WireguardConfig protected constructor(
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
internal fun String.base64ToHex(): String = Base64.decode(this, Base64.DEFAULT).toHexString()
|
||||
|
||||
/** Converts awg-quick on/off (and 0/1/true/false) to UAPI 1/0 for amneziawg-go ParseBool. */
|
||||
internal fun String.toUapiBool(): String = when (trim().lowercase()) {
|
||||
"on", "1", "true", "t", "yes" -> "1"
|
||||
else -> "0"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
||||
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel ${CMAKE_BINARY_DIR}/3rd/SortFilterProxyModel)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||
|
||||
@@ -11,24 +12,20 @@ add_compile_definitions(_WINSOCKAPI_)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CMAKE_BINARY_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
|
||||
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 ${CMAKE_BINARY_DIR}/3rd/qtgamepad)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
|
||||
# Link both the C++ module and QML plugin
|
||||
if(TARGET ${PROJECT})
|
||||
if(TARGET GamepadLegacy)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacy)
|
||||
endif()
|
||||
if(TARGET ${PROJECT})
|
||||
if(TARGET GamepadLegacyQuickPrivate)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacyQuickPrivate)
|
||||
endif()
|
||||
message(STATUS "Gamepad support enabled for Android")
|
||||
else()
|
||||
@@ -39,6 +36,7 @@ endif()
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
include_directories(
|
||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||
)
|
||||
|
||||
21
client/cmake/QSimpleCrypto.cmake
Normal file
21
client/cmake/QSimpleCrypto.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
|
||||
|
||||
include_directories(${QSIMPLECRYPTO_DIR})
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${QSIMPLECRYPTO_DIR}/include/QAead.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QRsa.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QX509.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
|
||||
)
|
||||
@@ -1,8 +1,6 @@
|
||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||
|
||||
if(NOT DEFINED APP_ANDROID_MIN_SDK)
|
||||
set(APP_ANDROID_MIN_SDK 28)
|
||||
endif()
|
||||
set(APP_ANDROID_MIN_SDK 28)
|
||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||
"The minimum API level supported by the application or library" FORCE)
|
||||
|
||||
@@ -15,6 +13,7 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
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_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||
)
|
||||
|
||||
set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE_BUILD_TYPE")
|
||||
@@ -54,27 +53,3 @@ file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/androi
|
||||
find_package(openvpn-pt-android REQUIRED)
|
||||
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
|
||||
|
||||
set(APP_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
|
||||
if(APP_ANDROID_MAX_SDK)
|
||||
set(APP_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/android-package-source)
|
||||
file(REMOVE_RECURSE ${APP_ANDROID_PACKAGE_SOURCE_DIR})
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/android/ DESTINATION ${APP_ANDROID_PACKAGE_SOURCE_DIR})
|
||||
|
||||
set(manifest_path ${APP_ANDROID_PACKAGE_SOURCE_DIR}/AndroidManifest.xml)
|
||||
set(manifest_anchor "android:installLocation=\"auto\">")
|
||||
file(READ ${manifest_path} manifest_contents)
|
||||
string(REPLACE
|
||||
"${manifest_anchor}"
|
||||
"${manifest_anchor}\n\n <uses-sdk android:maxSdkVersion=\"${APP_ANDROID_MAX_SDK}\" />"
|
||||
patched_contents "${manifest_contents}")
|
||||
if(patched_contents STREQUAL manifest_contents)
|
||||
message(FATAL_ERROR
|
||||
"Failed to set maxSdkVersion=${APP_ANDROID_MAX_SDK}: anchor '${manifest_anchor}' "
|
||||
"not found in ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml")
|
||||
endif()
|
||||
file(WRITE ${manifest_path} "${patched_contents}")
|
||||
endif()
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${APP_ANDROID_PACKAGE_SOURCE_DIR})
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
if(NOT CLIENT_ANDROID_PACKAGE)
|
||||
set(CLIENT_ANDROID_PACKAGE "org.amnezia.vpn" CACHE STRING "Android package name for Play Store version lookup")
|
||||
endif()
|
||||
@@ -1,9 +0,0 @@
|
||||
if(NOT CLIENT_SWIFT_MODULE_NAME)
|
||||
set(CLIENT_SWIFT_MODULE_NAME "${CLIENT_APPLICATION_NAME}" CACHE STRING "Swift module name for Objective-C interop header")
|
||||
endif()
|
||||
if(NOT CLIENT_SWIFT_OBJC_HEADER_NAME)
|
||||
set(CLIENT_SWIFT_OBJC_HEADER_NAME "${CLIENT_SWIFT_MODULE_NAME}-Swift.h" CACHE STRING "Generated Swift Objective-C header name")
|
||||
endif()
|
||||
if(NOT CLIENT_NETWORK_EXTENSION_NAME)
|
||||
set(CLIENT_NETWORK_EXTENSION_NAME "${CLIENT_APPLICATION_NAME}NetworkExtension" CACHE STRING "Display name for Apple network extension targets")
|
||||
endif()
|
||||
@@ -1,60 +0,0 @@
|
||||
if(NOT CLIENT_TARGET_NAME)
|
||||
set(CLIENT_TARGET_NAME "AmneziaVPN" CACHE STRING "Client executable target name")
|
||||
endif()
|
||||
if(NOT CLIENT_APPLICATION_NAME)
|
||||
set(CLIENT_APPLICATION_NAME "AmneziaVPN" CACHE STRING "Application display and executable name")
|
||||
endif()
|
||||
if(NOT CLIENT_SERVICE_NAME)
|
||||
set(CLIENT_SERVICE_NAME "AmneziaVPN-service" CACHE STRING "Service executable name")
|
||||
endif()
|
||||
if(NOT CLIENT_ORGANIZATION_NAME)
|
||||
set(CLIENT_ORGANIZATION_NAME "AmneziaVPN.ORG" CACHE STRING "QSettings organization name")
|
||||
endif()
|
||||
if(NOT CLIENT_APP_INSTANCE_NAME)
|
||||
set(CLIENT_APP_INSTANCE_NAME "AmneziaVPNInstance" CACHE STRING "Single-instance local server name")
|
||||
endif()
|
||||
if(NOT CLIENT_KEYCHAIN_NAME)
|
||||
set(CLIENT_KEYCHAIN_NAME "${CLIENT_APPLICATION_NAME}-Keychain" CACHE STRING "QtKeychain service name used for encrypted settings keys")
|
||||
endif()
|
||||
|
||||
# -- QML entry points and UI resources
|
||||
if(NOT CLIENT_QML_ENTRYPOINT)
|
||||
set(CLIENT_QML_ENTRYPOINT "qrc:/ui/qml/main2.qml" CACHE STRING "Main QML entrypoint URL")
|
||||
endif()
|
||||
if(NOT CLIENT_QML_IMPORT_PATH)
|
||||
set(CLIENT_QML_IMPORT_PATH "qrc:/ui/qml/Modules/" CACHE STRING "Additional QQmlEngine import path")
|
||||
endif()
|
||||
if(NOT CLIENT_QML_PAGES_PREFIX)
|
||||
set(CLIENT_QML_PAGES_PREFIX "qrc:/ui/qml/Pages2/" CACHE STRING "PageController QML pages prefix")
|
||||
endif()
|
||||
if(NOT CLIENT_UI_FONT_RESOURCE)
|
||||
set(CLIENT_UI_FONT_RESOURCE ":/fonts/pt-root-ui_vf.ttf" CACHE STRING "UI font resource path")
|
||||
endif()
|
||||
if(NOT CLIENT_QML_QRC_FILE)
|
||||
set(CLIENT_QML_QRC_FILE "${_CLIENT_SRC_DIR}/ui/qml/qml.qrc" CACHE FILEPATH "QML resources manifest")
|
||||
endif()
|
||||
if(NOT CLIENT_EXTRA_QRC)
|
||||
set(CLIENT_EXTRA_QRC "" CACHE STRING "Optional semicolon-separated extra QRC files")
|
||||
endif()
|
||||
|
||||
# -- translations
|
||||
if(NOT CLIENT_TS_PREFIX)
|
||||
set(CLIENT_TS_PREFIX "amneziavpn" CACHE STRING "Translation filename prefix")
|
||||
endif()
|
||||
if(NOT CLIENT_TS_FILES)
|
||||
set(CLIENT_TS_FILES "" CACHE STRING "Optional semicolon-separated absolute TS file paths")
|
||||
endif()
|
||||
|
||||
# -- brand-provided headers
|
||||
if(NOT CLIENT_BRANDING_INCLUDE_DIR)
|
||||
set(CLIENT_BRANDING_INCLUDE_DIR "" CACHE PATH "Optional include root for non-generated branding headers (e.g. pageEnum.h)")
|
||||
endif()
|
||||
if(CLIENT_BRANDING_INCLUDE_DIR AND EXISTS "${CLIENT_BRANDING_INCLUDE_DIR}/ui/utils/pageEnum.h")
|
||||
set(CLIENT_PAGE_ENUM_HEADER "${CLIENT_BRANDING_INCLUDE_DIR}/ui/utils/pageEnum.h")
|
||||
else()
|
||||
set(CLIENT_PAGE_ENUM_HEADER "${_CLIENT_SRC_DIR}/ui/utils/pageEnum.h")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_NOTIFICATION_IDENTIFIER)
|
||||
set(CLIENT_NOTIFICATION_IDENTIFIER "${CLIENT_TS_PREFIX}" CACHE STRING "Local notification request identifier")
|
||||
endif()
|
||||
@@ -1,45 +0,0 @@
|
||||
if(NOT CLIENT_IOS_NE_TARGET_NAME)
|
||||
set(CLIENT_IOS_NE_TARGET_NAME "networkextension" CACHE STRING "iOS network extension build target name")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_NE_ENTITLEMENTS_FILE)
|
||||
set(CLIENT_IOS_NE_ENTITLEMENTS_FILE "AmneziaVPNNetworkExtension.entitlements" CACHE STRING "iOS network extension entitlements filename")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_LAUNCHSCREEN_FILE)
|
||||
set(CLIENT_IOS_LAUNCHSCREEN_FILE "AmneziaVPNLaunchScreen.storyboard" CACHE STRING "iOS launch screen storyboard filename")
|
||||
endif()
|
||||
|
||||
get_filename_component(CLIENT_IOS_LAUNCHSCREEN_NAME "${CLIENT_IOS_LAUNCHSCREEN_FILE}" NAME_WE)
|
||||
|
||||
if(NOT CLIENT_IOS_APP_ENTITLEMENTS_PATH)
|
||||
set(CLIENT_IOS_APP_ENTITLEMENTS_PATH "${_CLIENT_SRC_DIR}/ios/app/main.entitlements" CACHE FILEPATH "iOS app entitlements file path")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_NE_ENTITLEMENTS_PATH)
|
||||
set(CLIENT_IOS_NE_ENTITLEMENTS_PATH "${_CLIENT_SRC_DIR}/ios/networkextension/${CLIENT_IOS_NE_ENTITLEMENTS_FILE}" CACHE FILEPATH "iOS network extension entitlements file path")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_LAUNCHSCREEN_PATH)
|
||||
set(CLIENT_IOS_LAUNCHSCREEN_PATH "${_CLIENT_SRC_DIR}/ios/app/${CLIENT_IOS_LAUNCHSCREEN_FILE}" CACHE FILEPATH "iOS launch screen storyboard path")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_MEDIA_ASSETS_PATH)
|
||||
set(CLIENT_IOS_MEDIA_ASSETS_PATH "${_CLIENT_SRC_DIR}/ios/app/Media.xcassets" CACHE FILEPATH "iOS app Media.xcassets path")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_IOS_BUNDLE_ID)
|
||||
set(CLIENT_IOS_BUNDLE_ID "org.amnezia.AmneziaVPN" CACHE STRING "iOS bundle id for App Store version lookup")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_STORE_URL_FALLBACK)
|
||||
set(CLIENT_IOS_STORE_URL_FALLBACK "itms-apps://itunes.apple.com/app/id1600529900" CACHE STRING "Fallback App Store URL when lookup omits trackViewUrl")
|
||||
endif()
|
||||
|
||||
# -- signing
|
||||
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER)
|
||||
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for release")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
|
||||
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for debug")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER)
|
||||
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for release")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
|
||||
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for debug")
|
||||
endif()
|
||||
@@ -1,37 +0,0 @@
|
||||
if(NOT CLIENT_MACOS_NE_TARGET_NAME)
|
||||
set(CLIENT_MACOS_NE_TARGET_NAME "${CLIENT_NETWORK_EXTENSION_NAME}" CACHE STRING "macOS network extension build target name")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_NE_ENTITLEMENTS_FILE)
|
||||
set(CLIENT_MACOS_NE_ENTITLEMENTS_FILE "AmneziaVPNNetworkExtension.entitlements" CACHE STRING "macOS network extension entitlements filename")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_MACOS_APP_ENTITLEMENTS_PATH)
|
||||
set(CLIENT_MACOS_APP_ENTITLEMENTS_PATH "${_CLIENT_SRC_DIR}/macos/app/app.entitlements" CACHE FILEPATH "macOS app entitlements file path")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_NE_ENTITLEMENTS_PATH)
|
||||
set(CLIENT_MACOS_NE_ENTITLEMENTS_PATH "${_CLIENT_SRC_DIR}/macos/networkextension/${CLIENT_MACOS_NE_ENTITLEMENTS_FILE}" CACHE FILEPATH "macOS network extension entitlements file path")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_MEDIA_ASSETS_PATH)
|
||||
set(CLIENT_MACOS_MEDIA_ASSETS_PATH "${_CLIENT_SRC_DIR}/macos/app/Images.xcassets" CACHE FILEPATH "macOS app Images.xcassets path")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_APP_ICNS_PATH)
|
||||
set(CLIENT_MACOS_APP_ICNS_PATH "${_CLIENT_SRC_DIR}/images/app.icns" CACHE FILEPATH "macOS app icon (.icns) used by non-App Store builds")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_MACOS_EXTRA_PLIST_ENTRIES)
|
||||
set(CLIENT_MACOS_EXTRA_PLIST_ENTRIES "" CACHE STRING "Extra macOS Info.plist entries appended verbatim (e.g. appearance pinning)")
|
||||
endif()
|
||||
|
||||
# -- signing
|
||||
if(NOT CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER)
|
||||
set(CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN" CACHE STRING "macOS app provisioning profile specifier for release")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
|
||||
set(CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev macos.org.amnezia.AmneziaVPN" CACHE STRING "macOS app provisioning profile specifier for debug")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_NE_PROVISIONING_PROFILE_SPECIFIER)
|
||||
set(CLIENT_MACOS_NE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE" CACHE STRING "macOS network extension provisioning profile specifier for release")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
|
||||
set(CLIENT_MACOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev macos.org.amnezia.amneziaVPN.NE" CACHE STRING "macOS network extension provisioning profile specifier for debug")
|
||||
endif()
|
||||
@@ -1,9 +0,0 @@
|
||||
get_filename_component(_CLIENT_SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/branding/common.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/branding/apple.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/branding/ios.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/branding/macos.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/branding/android.cmake)
|
||||
|
||||
unset(_CLIENT_SRC_DIR)
|
||||
@@ -24,17 +24,6 @@ set(IOS_TARGET_COMPILED_TYPES
|
||||
EXECUTABLE
|
||||
)
|
||||
|
||||
## Simulator architecture: honor CMAKE_OSX_ARCHITECTURES when set, otherwise
|
||||
## match the host — arm64 Macs run arm64 simulators (Xcode 26 has no Rosetta
|
||||
## simulator destinations), Intel Macs need x86_64.
|
||||
if(CMAKE_OSX_ARCHITECTURES)
|
||||
string(REPLACE ";" " " IOS_SIMULATOR_ARCHS "${CMAKE_OSX_ARCHITECTURES}")
|
||||
elseif(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
|
||||
set(IOS_SIMULATOR_ARCHS "arm64")
|
||||
else()
|
||||
set(IOS_SIMULATOR_ARCHS "x86_64")
|
||||
endif()
|
||||
|
||||
## Inspect all the targets, and add extra properties if necessary.
|
||||
while(IOS_TARGETS)
|
||||
list(POP_FRONT IOS_TARGETS TARGET_NAME)
|
||||
@@ -49,6 +38,6 @@ while(IOS_TARGETS)
|
||||
message("Patching architectures for ${TARGET_NAME}")
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64"
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "${IOS_SIMULATOR_ARCHS}"
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64"
|
||||
)
|
||||
endwhile()
|
||||
@@ -31,7 +31,6 @@ 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/ioscontextmenu.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
|
||||
@@ -43,7 +42,6 @@ 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/ioscontextmenu.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
|
||||
@@ -51,47 +49,43 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
# The context menu helper uses ARC-only constructs (weak references); the
|
||||
# rest of the Objective-C++ sources build with manual reference counting.
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.mm
|
||||
PROPERTIES COMPILE_OPTIONS "-fobjc-arc"
|
||||
)
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_APPLICATION_NAME}"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
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 "${CLIENT_IOS_APP_ENTITLEMENTS_PATH}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements"
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "${CLIENT_APPLICATION_NAME}"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
|
||||
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 ON
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS ${CLIENT_IOS_NE_TARGET_NAME}
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
if(DEFINED 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 "${CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER}"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "${CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG}"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
@@ -103,11 +97,11 @@ 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 "${CLIENT_SWIFT_OBJC_HEADER_NAME}"
|
||||
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 "${BUILD_VPN_DEVELOPMENT_TEAM}"
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
@@ -122,28 +116,22 @@ 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_GROUP_IDENTIFIER_SWIFT_FILE}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
${CLIENT_IOS_MEDIA_ASSETS_PATH}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_IOS_LAUNCHSCREEN_PATH}
|
||||
${CLIENT_IOS_MEDIA_ASSETS_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CLIENT_IOS_LAUNCHSCREEN_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(ios/networkextension)
|
||||
add_dependencies(${PROJECT} ${CLIENT_IOS_NE_TARGET_NAME})
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
@@ -34,8 +34,8 @@ set(SOURCES ${SOURCES}
|
||||
|
||||
|
||||
|
||||
set(ICON_FILE ${CLIENT_MACOS_APP_ICNS_PATH})
|
||||
get_filename_component(MACOSX_BUNDLE_ICON_FILE "${CLIENT_MACOS_APP_ICNS_PATH}" NAME)
|
||||
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})
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||
@@ -34,6 +33,7 @@ set(LIBS ${LIBS}
|
||||
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
|
||||
@@ -44,12 +44,19 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
|
||||
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}
|
||||
@@ -60,25 +67,28 @@ 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 "${CLIENT_APPLICATION_NAME}"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_APPLICATION_NAME}"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
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 "${CLIENT_MACOS_APP_ENTITLEMENTS_PATH}"
|
||||
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 "${CLIENT_APPLICATION_NAME}"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
|
||||
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 ${CLIENT_MACOS_NE_TARGET_NAME}
|
||||
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -86,8 +96,8 @@ if(DEPLOY)
|
||||
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 "${CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER}"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "${CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG}"
|
||||
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
|
||||
@@ -99,11 +109,11 @@ 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 "${CLIENT_SWIFT_OBJC_HEADER_NAME}"
|
||||
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 "${BUILD_VPN_DEVELOPMENT_TEAM}"
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
@@ -117,20 +127,14 @@ 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_GROUP_IDENTIFIER_SWIFT_FILE}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
${CLIENT_MACOS_MEDIA_ASSETS_PATH}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_MACOS_MEDIA_ASSETS_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
@@ -139,7 +143,7 @@ set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
)
|
||||
|
||||
add_subdirectory(macos/networkextension)
|
||||
add_dependencies(${PROJECT} ${CLIENT_MACOS_NE_TARGET_NAME})
|
||||
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
@@ -148,5 +152,5 @@ message(${QtCore_location})
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:${PROJECT}> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
@@ -35,8 +35,6 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
|
||||
@@ -51,23 +49,17 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/pages.h
|
||||
${CLIENT_PAGE_ENUM_HEADER}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/payloadSender.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/swiftBridge.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants.h
|
||||
@@ -118,8 +110,6 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
|
||||
@@ -133,11 +123,9 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/payloadSender.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
|
||||
@@ -147,10 +135,8 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
||||
@@ -215,14 +201,12 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/utils/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -96,19 +96,13 @@ ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credential
|
||||
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
|
||||
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
|
||||
|
||||
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
|
||||
|
||||
newClientConfig.headerProtectionKey = configMap.value(configKey::headerProtectionKey);
|
||||
newClientConfig.contentPaddingAddition = configMap.value(configKey::contentPaddingAddition);
|
||||
newClientConfig.rekeyAfterTime = configMap.value(configKey::rekeyAfterTime);
|
||||
newClientConfig.rekeyTimeout = configMap.value(configKey::rekeyTimeout);
|
||||
newClientConfig.rejectAfterTime = configMap.value(configKey::rejectAfterTime);
|
||||
newClientConfig.keepaliveTimeout = configMap.value(configKey::keepaliveTimeout);
|
||||
newClientConfig.maxHandshakeAttempts = configMap.value(configKey::maxHandshakeAttempts);
|
||||
newClientConfig.randomTrailers = configMap.value(configKey::randomTrailers);
|
||||
newClientConfig.disableCookies = configMap.value(configKey::disableCookies);
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
|
||||
}
|
||||
|
||||
newClientConfig.isObfuscationEnabled = false;
|
||||
|
||||
protocolConfig.setClientConfig(newClientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
|
||||
@@ -233,13 +233,6 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config = m_sshSession->replaceVars(scriptData, vars);
|
||||
|
||||
// The template lists every possible key, but each parameter is optional -
|
||||
// drop the lines whose value came out empty
|
||||
static const QRegularExpression emptyValueLine(R"(^\s*\S+\s*=\s*$)");
|
||||
auto configTemplateLines = config.split("\n");
|
||||
configTemplateLines.removeIf([](const QString &line) { return emptyValueLine.match(line).hasMatch(); });
|
||||
config = configTemplateLines.join("\n");
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return WireGuardProtocolConfig{};
|
||||
@@ -273,9 +266,7 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
|
||||
clientConfig.presharedKey = connData.pskKey;
|
||||
clientConfig.clientId = connData.clientPubKey;
|
||||
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
|
||||
const bool useKeepAliveRange = awgServerConfig && awgServerConfig->hasAwg3Params();
|
||||
clientConfig.persistentKeepAlive = useKeepAliveRange ? protocols::awg::defaultPersistentKeepAlive
|
||||
: protocols::wireguard::defaultPersistentKeepAlive;
|
||||
clientConfig.persistentKeepAlive = "25";
|
||||
clientConfig.mtu = mtu;
|
||||
clientConfig.isObfuscationEnabled = false;
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
#include <QUuid>
|
||||
#include "logger.h"
|
||||
|
||||
@@ -22,775 +20,183 @@
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
|
||||
QString effectiveClientFlow(const amnezia::XrayServerConfig &srv)
|
||||
{
|
||||
return amnezia::xrayEffective::clientFlow(srv);
|
||||
}
|
||||
|
||||
QString effectiveSecurity(const amnezia::XrayServerConfig &srv)
|
||||
{
|
||||
return amnezia::xrayEffective::security(srv);
|
||||
}
|
||||
|
||||
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
|
||||
void sanitizeXrayNativeConfig(amnezia::ProtocolConfig &pc)
|
||||
{
|
||||
QString c = pc.nativeConfig();
|
||||
if (c.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
bool changed = false;
|
||||
if (c.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.replace(QLatin1String("Mozilla/5.0"), QString::fromLatin1(amnezia::protocols::xray::defaultFingerprint),
|
||||
Qt::CaseInsensitive);
|
||||
changed = true;
|
||||
}
|
||||
const QString legacyListen = QString::fromLatin1(amnezia::protocols::xray::defaultLocalAddr);
|
||||
const QString listenOk = QString::fromLatin1(amnezia::protocols::xray::defaultLocalListenAddr);
|
||||
if (c.contains(legacyListen)) {
|
||||
c.replace(legacyListen, listenOk);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
pc.setNativeConfig(c);
|
||||
}
|
||||
}
|
||||
|
||||
QString tlsFingerprintFromEnsureOutput(const QString &stdOut)
|
||||
{
|
||||
const QString prefix = QStringLiteral("amnezia_tls_fp=");
|
||||
const auto lines = stdOut.split(QLatin1Char('\n'));
|
||||
for (QString line : lines) {
|
||||
line = line.trimmed();
|
||||
if (!line.startsWith(prefix))
|
||||
continue;
|
||||
QString fp = line.mid(prefix.size());
|
||||
fp.remove(QLatin1Char(':'));
|
||||
fp.remove(QLatin1Char(' '));
|
||||
fp = fp.toLower();
|
||||
if (fp.size() != 64)
|
||||
return {};
|
||||
for (const QChar ch : fp) {
|
||||
const ushort u = ch.unicode();
|
||||
const bool hex = (u >= '0' && u <= '9') || (u >= 'a' && u <= 'f');
|
||||
if (!hex)
|
||||
return {};
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
Logger logger("XrayConfigurator");
|
||||
}
|
||||
|
||||
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig)
|
||||
{
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
sanitizeXrayNativeConfig(protocolConfig);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::uploadServerConfigJson(const ServerCredentials &credentials, DockerContainer container,
|
||||
const DnsSettings &dnsSettings, const QJsonObject &serverConfig) const
|
||||
{
|
||||
const QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container, credentials, updatedConfig, amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(restartScript,
|
||||
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
|
||||
dnsSettings.secondaryDns)));
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::readRealityKeyFiles(const DockerContainer container, const ServerCredentials &credentials,
|
||||
QString &outPublicKey, QString &outShortId) const
|
||||
{
|
||||
outPublicKey.clear();
|
||||
outShortId.clear();
|
||||
|
||||
auto readKeyFile = [&](const QString &path, QString &out) -> ErrorCode {
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
ErrorCode fileError = ErrorCode::NoError;
|
||||
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
|
||||
out.replace(QLatin1Char('\n'), QString());
|
||||
out.replace(QLatin1Char('\r'), QString());
|
||||
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
if (attempt < 2) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
logger.error() << "Xray readRealityKeyFiles: failed path=" << path;
|
||||
return ErrorCode::XrayRealityKeysReadFailed;
|
||||
};
|
||||
|
||||
ErrorCode errorCode = readKeyFile(QString::fromLatin1(amnezia::protocols::xray::PublicKeyPath), outPublicKey);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
return readKeyFile(QString::fromLatin1(amnezia::protocols::xray::shortidPath), outShortId);
|
||||
}
|
||||
|
||||
|
||||
ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials &credentials, DockerContainer container,
|
||||
ContainerConfig &containerConfig, const DnsSettings &dnsSettings,
|
||||
bool appendNewClient, QString *outClientId)
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
|
||||
if (!xrayCfg) {
|
||||
logger.error() << "Xray applyServerSettings: missing XrayProtocolConfig";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const XrayServerConfig &srv = xrayCfg->serverConfig;
|
||||
if (srv.isThirdPartyConfig) {
|
||||
if (outClientId && xrayCfg->hasClientConfig()) {
|
||||
*outClientId = xrayCfg->clientConfig->id;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (effectiveSecurity(srv) == QLatin1String("reality")) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
|
||||
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
const QJsonObject originalConfig = serverConfig;
|
||||
|
||||
QJsonArray clients = XrayServerConfig::clientsFromServerInboundJson(serverConfig);
|
||||
QString clientId;
|
||||
|
||||
if (appendNewClient) {
|
||||
const bool adoptingOwnAccount = !(xrayCfg->hasClientConfig() && !xrayCfg->clientConfig->id.isEmpty());
|
||||
|
||||
QString ownClientId;
|
||||
if (readContainerKeyFile(container, credentials, QString::fromLatin1(amnezia::protocols::xray::uuidPath),
|
||||
ownClientId)
|
||||
!= ErrorCode::NoError) {
|
||||
ownClientId.clear();
|
||||
}
|
||||
|
||||
const int existingIndex =
|
||||
adoptingOwnAccount ? XrayServerConfig::indexOfClient(clients, ownClientId) : -1;
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
clientId = ownClientId;
|
||||
clients[existingIndex] =
|
||||
XrayServerConfig::applyFlowToClient(clients[existingIndex].toObject(), flowValue);
|
||||
logger.info() << "Xray applyServerSettings: reusing the account from the key file, clients="
|
||||
<< clients.size();
|
||||
} else {
|
||||
const bool takeOverOwnAccount = adoptingOwnAccount && !ownClientId.isEmpty();
|
||||
clientId = takeOverOwnAccount ? ownClientId : QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
clients.append(XrayServerConfig::makeClientEntry(clientId, flowValue));
|
||||
logger.info() << "Xray applyServerSettings: added an account,"
|
||||
<< (takeOverOwnAccount ? "taken over from the key file" : "freshly minted for a new user")
|
||||
<< ", clients=" << clients.size();
|
||||
}
|
||||
} else {
|
||||
if (clients.isEmpty()) {
|
||||
logger.error() << "Server config has no VLESS clients";
|
||||
return ErrorCode::XrayServerNoVlessClients;
|
||||
}
|
||||
clientId = XrayServerConfig::firstClientId(clients);
|
||||
if (clientId.isEmpty()) {
|
||||
logger.error() << "Server config VLESS client has empty id";
|
||||
return ErrorCode::XrayServerNoVlessClients;
|
||||
}
|
||||
clients = XrayServerConfig::applyFlowToClients(clients, flowValue);
|
||||
}
|
||||
|
||||
const XrayServerJsonStatus writeStatus =
|
||||
XrayServerConfig::setClientsInServerInboundJson(serverConfig, clients);
|
||||
if (writeStatus != XrayServerJsonStatus::Ok) {
|
||||
logger.error() << "Xray applyServerSettings: server config has no place for the client list, status="
|
||||
<< static_cast<int>(writeStatus);
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
const QString listenPort = srv.port.isEmpty() ? QString::fromLatin1(amnezia::protocols::xray::defaultPort)
|
||||
: srv.port;
|
||||
if (serverConfig == originalConfig) {
|
||||
logger.info() << "Xray applyServerSettings: server config already matches, no rewrite and no restart,"
|
||||
<< "clients=" << clients.size();
|
||||
} else {
|
||||
errorCode = uploadServerConfigAtomically(credentials, container, listenPort, serverConfig);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: apply failed, error=" << static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
if (outClientId) {
|
||||
*outClientId = clientId;
|
||||
}
|
||||
|
||||
XrayProtocolConfig updated =
|
||||
buildClientProtocolConfig(credentials, container, srv, xrayCfg->clientTemplate, clientId, errorCode,
|
||||
realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: buildClientProtocolConfig failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
containerConfig.protocolConfig = updated;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::readContainerKeyFile(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, QString &out) const
|
||||
{
|
||||
out.clear();
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
ErrorCode fileError = ErrorCode::NoError;
|
||||
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
|
||||
out.replace(QLatin1Char('\n'), QString());
|
||||
out.replace(QLatin1Char('\r'), QString());
|
||||
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
if (attempt < 2) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
logger.error() << "Xray readContainerKeyFile: failed path=" << path;
|
||||
return ErrorCode::XrayRealityKeysReadFailed;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::writeServerConfigForSetup(const ServerCredentials &credentials, DockerContainer container,
|
||||
ContainerConfig &containerConfig, const DnsSettings &dnsSettings)
|
||||
{
|
||||
Q_UNUSED(dnsSettings);
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
|
||||
if (!xrayCfg) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: missing XrayProtocolConfig";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const XrayServerConfig &srv = xrayCfg->serverConfig;
|
||||
if (srv.isThirdPartyConfig) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
QString clientId;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::uuidPath), clientId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: could not read the container uuid, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString securityEff = effectiveSecurity(srv);
|
||||
QString tlsPin;
|
||||
if (securityEff == QLatin1String("tls")) {
|
||||
errorCode = ensureTlsCertificate(credentials, container, tlsPin);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: TLS certificate is not on the server, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
QString realityPrivateKey;
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PrivateKeyPath), realityPrivateKey);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PublicKeyPath), realityPublicKey);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::shortidPath), realityShortId);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
const QJsonArray clients = collectServerClients(credentials, container, flowValue, clientId, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: refusing to write over an unreadable client list";
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString portEff = srv.port.isEmpty() ? QString::fromLatin1(px::defaultPort) : srv.port;
|
||||
|
||||
if (securityEff != srv.security) {
|
||||
logger.warning() << "Xray writeServerConfigForSetup: security downgraded for the server config, requested="
|
||||
<< srv.security << "written=" << securityEff << "transport=" << srv.transport;
|
||||
}
|
||||
|
||||
XrayServerInboundInputs inputs;
|
||||
inputs.clients = clients;
|
||||
inputs.realityPrivateKey = realityPrivateKey;
|
||||
inputs.realityShortId = realityShortId;
|
||||
|
||||
const QJsonObject inboundJson = srv.toServerInboundJson(inputs);
|
||||
const QString json = QString::fromUtf8(QJsonDocument(inboundJson).toJson());
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, json,
|
||||
QString::fromLatin1(px::serverConfigPath),
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: upload failed, error=" << static_cast<int>(errorCode)
|
||||
<< "port=" << portEff;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
XrayProtocolConfig updated =
|
||||
buildClientProtocolConfig(credentials, container, srv, xrayCfg->clientTemplate, clientId, errorCode,
|
||||
realityPublicKey, realityShortId, tlsPin);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: buildClientProtocolConfig failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
updated.clientTemplate.updatedAt = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
// Written to the volume so a second administrator device can adopt the client-side
|
||||
// settings. Failure is not fatal: the settings already apply on this device.
|
||||
uploadClientTemplate(credentials, container, updated.clientTemplate);
|
||||
containerConfig.protocolConfig = updated;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QJsonArray XrayConfigurator::collectServerClients(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QString &flowValue, const QString &fallbackClientId,
|
||||
ErrorCode &outError) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
outError = ErrorCode::NoError;
|
||||
const QString configPath = QString::fromLatin1(px::serverConfigPath);
|
||||
|
||||
QString currentConfig;
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
ErrorCode readError = ErrorCode::NoError;
|
||||
currentConfig = m_sshSession->getTextFileFromContainer(container, credentials, configPath, readError);
|
||||
if (readError == ErrorCode::NoError && !currentConfig.trimmed().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
currentConfig.clear();
|
||||
if (attempt < 2) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray existing;
|
||||
if (currentConfig.trimmed().isEmpty()) {
|
||||
QString probeOut;
|
||||
auto collect = [&probeOut](const QString &data, libssh::Client &) {
|
||||
probeOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const QString probe =
|
||||
QStringLiteral("if [ -s %1 ]; then echo amnezia_config=present; else echo amnezia_config=absent; fi")
|
||||
.arg(configPath);
|
||||
const ErrorCode probeError = m_sshSession->runContainerScript(credentials, container, probe, collect, collect);
|
||||
|
||||
if (probeError != ErrorCode::NoError || !probeOut.contains(QLatin1String("amnezia_config="))) {
|
||||
logger.error() << "Xray collectServerClients: read outcome=unknown, cannot tell whether a server config "
|
||||
"exists, probeError="
|
||||
<< static_cast<int>(probeError);
|
||||
outError = ErrorCode::XrayServerConfigInvalid;
|
||||
return {};
|
||||
}
|
||||
if (probeOut.contains(QLatin1String("amnezia_config=present"))) {
|
||||
logger.error() << "Xray collectServerClients: read outcome=unreadable, server config exists but could not "
|
||||
"be read, aborting so the existing client list is not lost";
|
||||
outError = ErrorCode::XrayServerConfigInvalid;
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (!doc.isObject()) {
|
||||
logger.error() << "Xray collectServerClients: read outcome=unparseable, server config is not valid JSON, "
|
||||
"bytes="
|
||||
<< currentConfig.size();
|
||||
outError = ErrorCode::XrayServerConfigInvalid;
|
||||
return {};
|
||||
}
|
||||
existing = XrayServerConfig::clientsFromServerInboundJson(doc.object());
|
||||
}
|
||||
|
||||
QJsonArray clients =
|
||||
XrayServerConfig::applyFlowToClients(existing, flowValue, XrayClientListFilter::DropWithoutId);
|
||||
|
||||
if (!fallbackClientId.isEmpty()
|
||||
&& XrayServerConfig::indexOfClient(clients, fallbackClientId) < 0) {
|
||||
clients.append(XrayServerConfig::makeClientEntry(fallbackClientId, flowValue));
|
||||
}
|
||||
|
||||
return clients;
|
||||
}
|
||||
|
||||
bool XrayConfigurator::isSecuritySupportedOnSelfHosted(const XrayServerConfig &srv)
|
||||
{
|
||||
Q_UNUSED(srv);
|
||||
// TLS is issued in writeServerConfigForSetup (self-signed pems in the volume).
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::ensureTlsCertificate(const ServerCredentials &credentials, DockerContainer container,
|
||||
QString &outFingerprint) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
outFingerprint.clear();
|
||||
|
||||
QString stdOut;
|
||||
auto collect = [&stdOut](const QString &data, libssh::Client &) {
|
||||
stdOut += data + QLatin1Char('\n');
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
const QString certPath = QString::fromLatin1(px::tlsCertPath);
|
||||
const QString keyPath = QString::fromLatin1(px::tlsKeyPath);
|
||||
const QString script = QStringLiteral(
|
||||
"has_key() { [ -f \"$1\" ] && [ -n \"$(tr -d '[:space:]' < \"$1\" 2>/dev/null)\" ]; }\n"
|
||||
"CERT=%1\n"
|
||||
"KEY=%2\n"
|
||||
"if has_key \"$CERT\" && has_key \"$KEY\"; then echo amnezia_tls=present;\n"
|
||||
"else\n"
|
||||
" openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj \"/CN=amnezia-xray\" "
|
||||
"-keyout \"$KEY\" -out \"$CERT\" || true\n"
|
||||
" if has_key \"$CERT\" && has_key \"$KEY\"; then echo amnezia_tls=created;\n"
|
||||
" else echo amnezia_tls=failed; exit 1; fi\n"
|
||||
"fi\n"
|
||||
"FP=$(openssl x509 -noout -fingerprint -sha256 -in \"$CERT\" 2>/dev/null "
|
||||
"| sed 's/.*Fingerprint=//' | tr -d ':' | tr 'A-F' 'a-f')\n"
|
||||
"if [ -z \"$FP\" ]; then echo amnezia_tls=failed; exit 1; fi\n"
|
||||
"echo amnezia_tls_fp=$FP\n"
|
||||
"exit 0\n")
|
||||
.arg(certPath, keyPath);
|
||||
|
||||
const ErrorCode scriptError = m_sshSession->runContainerScript(credentials, container, script, collect, collect);
|
||||
const bool present = stdOut.contains(QLatin1String("amnezia_tls=present"));
|
||||
const bool created = stdOut.contains(QLatin1String("amnezia_tls=created"));
|
||||
outFingerprint = tlsFingerprintFromEnsureOutput(stdOut);
|
||||
if (scriptError != ErrorCode::NoError || (!present && !created) || outFingerprint.isEmpty()) {
|
||||
logger.error() << "Xray ensureTlsCertificate: openssl failed, error=" << static_cast<int>(scriptError)
|
||||
<< "out=" << stdOut.trimmed();
|
||||
outFingerprint.clear();
|
||||
return ErrorCode::XrayTlsNotSupported;
|
||||
}
|
||||
if (created) {
|
||||
logger.info() << "Xray ensureTlsCertificate: issued a self-signed certificate into the volume";
|
||||
} else {
|
||||
logger.info() << "Xray ensureTlsCertificate: using the existing certificate in the volume";
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::uploadServerConfigAtomically(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QString &listenPort,
|
||||
const QJsonObject &serverConfig) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
const QString livePath = QString::fromLatin1(px::serverConfigPath);
|
||||
const QString stagedPath = QStringLiteral("/opt/amnezia/xray/server.new.json");
|
||||
|
||||
const QString json = QString::fromUtf8(QJsonDocument(serverConfig).toJson());
|
||||
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, json, stagedPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray atomic apply: failed to stage config, error=" << static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString script = QStringLiteral(
|
||||
"LIVE=%1\n"
|
||||
"STAGED=%2\n"
|
||||
"PORT=%3\n"
|
||||
"BACKUP=\"$LIVE.bak\"\n"
|
||||
"if [ ! -s \"$STAGED\" ]; then echo \"amnezia_apply=missing\"; exit 0; fi\n"
|
||||
"if ! xray -test -format json -config \"$STAGED\" > /tmp/xray_test.log 2>&1; then\n"
|
||||
" echo \"amnezia_apply=invalid\"\n"
|
||||
" cat /tmp/xray_test.log\n"
|
||||
" rm -f \"$STAGED\"\n"
|
||||
" exit 0\n"
|
||||
"fi\n"
|
||||
"serving() {\n"
|
||||
" netstat -lntup 2>/dev/null | grep -E \"[:.]$PORT[[:space:]]\" | grep -q xray\n"
|
||||
"}\n"
|
||||
"if [ -f \"$LIVE\" ]; then cp \"$LIVE\" \"$BACKUP\"; fi\n"
|
||||
"mv \"$STAGED\" \"$LIVE\"\n"
|
||||
"killall -KILL xray 2>/dev/null\n"
|
||||
"sleep 1\n"
|
||||
"(xray -config \"$LIVE\" > /dev/null 2>&1 &)\n"
|
||||
"sleep 3\n"
|
||||
"if serving; then\n"
|
||||
" rm -f \"$BACKUP\"\n"
|
||||
" echo \"amnezia_apply=applied\"\n"
|
||||
" exit 0\n"
|
||||
"fi\n"
|
||||
"if [ -f \"$BACKUP\" ]; then\n"
|
||||
" cp \"$BACKUP\" \"$LIVE\"\n"
|
||||
" killall -KILL xray 2>/dev/null\n"
|
||||
" sleep 1\n"
|
||||
" (xray -config \"$LIVE\" > /dev/null 2>&1 &)\n"
|
||||
" sleep 3\n"
|
||||
" if serving; then echo \"amnezia_apply=rolled_back\"; else echo \"amnezia_apply=rollback_failed\"; fi\n"
|
||||
" exit 0\n"
|
||||
"fi\n"
|
||||
"echo \"amnezia_apply=rollback_failed\"\n")
|
||||
.arg(livePath, stagedPath, listenPort);
|
||||
|
||||
QString stdOut;
|
||||
auto collect = [&stdOut](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode = m_sshSession->runContainerScript(credentials, container, script, collect, collect);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray atomic apply: script failed, error=" << static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (stdOut.contains(QLatin1String("amnezia_apply=applied"))) {
|
||||
logger.info() << "Xray atomic apply: outcome=applied, new config is live and serving on port=" << listenPort;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
if (stdOut.contains(QLatin1String("amnezia_apply=missing"))) {
|
||||
logger.error() << "Xray atomic apply: outcome=staged-file-missing, staged config was not on the server, "
|
||||
"live config untouched, port="
|
||||
<< listenPort;
|
||||
return ErrorCode::XrayServerConfigRejected;
|
||||
}
|
||||
if (stdOut.contains(QLatin1String("amnezia_apply=invalid"))) {
|
||||
logger.error() << "Xray atomic apply: outcome=rejected, xray -test refused the config, live config untouched, "
|
||||
"port="
|
||||
<< listenPort << "testOutputBytes=" << stdOut.size() << "(output withheld, may contain keys)";
|
||||
return ErrorCode::XrayServerConfigRejected;
|
||||
}
|
||||
if (stdOut.contains(QLatin1String("amnezia_apply=rolled_back"))) {
|
||||
logger.error() << "Xray atomic apply: outcome=rolled-back, new config did not serve on port=" << listenPort
|
||||
<< ", previous config restored and serving";
|
||||
return ErrorCode::XrayServerConfigRolledBack;
|
||||
}
|
||||
if (stdOut.contains(QLatin1String("amnezia_apply=rollback_failed"))) {
|
||||
logger.error() << "Xray atomic apply: outcome=ROLLBACK-FAILED, nothing is serving on port=" << listenPort
|
||||
<< ", the xray container is down and the previous config could not be brought back";
|
||||
return ErrorCode::XrayServerNotServing;
|
||||
}
|
||||
|
||||
logger.error() << "Xray atomic apply: outcome=unrecognised, no marker in script output, port=" << listenPort
|
||||
<< "outputBytes=" << stdOut.size() << "output="
|
||||
<< stdOut.simplified().left(200);
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
ContainerConfig mutableConfig = containerConfig;
|
||||
QString clientId;
|
||||
const ErrorCode applyError =
|
||||
applyServerSettingsToRemote(credentials, container, mutableConfig, dnsSettings, true, &clientId);
|
||||
errorCode = applyError;
|
||||
if (applyError != ErrorCode::NoError || clientId.isEmpty()) {
|
||||
return QString();
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get server config file";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse current config as JSON
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
|
||||
if (!settings.contains(amnezia::protocols::xray::clients)) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{amnezia::protocols::xray::id, clientId},
|
||||
{amnezia::protocols::xray::flow, "xtls-rprx-vision"}
|
||||
};
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings[amnezia::protocols::xray::clients] = clients;
|
||||
inbound[amnezia::protocols::xray::settings] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
|
||||
);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
return "";
|
||||
}
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
bool XrayConfigurator::uploadClientTemplate(const ServerCredentials &credentials, DockerContainer container,
|
||||
const XrayClientTemplate &clientTemplate) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
XrayClientTemplate stamped = clientTemplate;
|
||||
stamped.updatedAt = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
const QJsonObject templateJson = stamped.toJson();
|
||||
const QString json = QString::fromUtf8(QJsonDocument(templateJson).toJson());
|
||||
const ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container, credentials, json, QString::fromLatin1(px::clientTemplatePath),
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.warning() << "Xray uploadClientTemplate: failed, error=" << static_cast<int>(errorCode)
|
||||
<< ", this device is now ahead of the template stored on the server";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
XrayClientTemplate XrayConfigurator::readClientTemplate(const ServerCredentials &credentials,
|
||||
DockerContainer container, bool &outFound) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
outFound = false;
|
||||
ErrorCode readError = ErrorCode::NoError;
|
||||
const QString content = QString::fromUtf8(m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString::fromLatin1(px::clientTemplatePath), readError));
|
||||
if (readError != ErrorCode::NoError || content.trimmed().isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(content.toUtf8());
|
||||
if (!doc.isObject()) {
|
||||
logger.warning() << "Xray readClientTemplate: template on the server is not valid json";
|
||||
return {};
|
||||
}
|
||||
|
||||
XrayClientTemplate tpl = XrayClientTemplate::fromJson(doc.object());
|
||||
if (tpl.formatVersion == 0) {
|
||||
logger.warning() << "Xray readClientTemplate: template on the server has no format version, ignoring it";
|
||||
return {};
|
||||
}
|
||||
|
||||
outFound = true;
|
||||
return tpl;
|
||||
}
|
||||
XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const XrayServerConfig &srv,
|
||||
const XrayClientTemplate &tpl, const QString &clientId,
|
||||
ErrorCode &errorCode,
|
||||
const QString &prefetchedRealityPublicKey,
|
||||
const QString &prefetchedRealityShortId,
|
||||
const QString &prefetchedTlsPin) const
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
QString xrayPublicKey = prefetchedRealityPublicKey;
|
||||
QString xrayShortId = prefetchedRealityShortId;
|
||||
QString tlsPin = prefetchedTlsPin;
|
||||
|
||||
const QString securityEff = effectiveSecurity(srv);
|
||||
|
||||
if (securityEff == QLatin1String(px::securityReality)) {
|
||||
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (securityEff == QLatin1String(px::securityTls) && !srv.isThirdPartyConfig) {
|
||||
if (tlsPin.isEmpty()) {
|
||||
errorCode = ensureTlsCertificate(credentials, container, tlsPin);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (tlsPin.isEmpty()) {
|
||||
logger.error() << "Xray buildClientProtocolConfig: TLS pin is empty after ensure";
|
||||
errorCode = ErrorCode::XrayTlsNotSupported;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (securityEff != srv.security) {
|
||||
logger.warning() << "Xray buildClientProtocolConfig: security downgraded for the client config, requested="
|
||||
<< srv.security << "written=" << securityEff << "transport=" << srv.transport;
|
||||
}
|
||||
|
||||
XrayProtocolConfig protocolConfig;
|
||||
protocolConfig.serverConfig = srv;
|
||||
protocolConfig.clientTemplate = tpl;
|
||||
|
||||
XrayClientOutboundInputs inputs;
|
||||
inputs.serverAddress = credentials.hostName;
|
||||
inputs.clientId = clientId;
|
||||
inputs.realityPublicKey = xrayPublicKey;
|
||||
inputs.realityShortId = xrayShortId;
|
||||
inputs.tlsPinnedPeerCertSha256 = tlsPin;
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = QString::fromUtf8(
|
||||
QJsonDocument(protocolConfig.toClientOutboundJson(inputs)).toJson(QJsonDocument::Compact));
|
||||
clientConfig.localPort = QString::fromLatin1(px::defaultLocalProxyPort);
|
||||
clientConfig.id = clientId;
|
||||
clientConfig.templateFingerprint = tpl.contentFingerprint();
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
if (xrayCfg->serverConfig.isThirdPartyConfig && xrayCfg->hasClientConfig()) {
|
||||
logger.info() << "Xray createConfig: returning existing third-party client config without server SSH";
|
||||
return *xrayCfg;
|
||||
}
|
||||
const XrayServerConfig* serverConfig = nullptr;
|
||||
if (auto* xrayConfig = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
serverConfig = &xrayConfig->serverConfig;
|
||||
}
|
||||
|
||||
const XrayServerConfig *serverConfig = nullptr;
|
||||
XrayClientTemplate clientTemplate;
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
serverConfig = &xrayCfg->serverConfig;
|
||||
clientTemplate = xrayCfg->clientTemplate;
|
||||
}
|
||||
|
||||
if (!serverConfig) {
|
||||
logger.error() << "No XrayProtocolConfig found";
|
||||
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
const XrayServerConfig &srv = *serverConfig;
|
||||
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
|
||||
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
|
||||
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars);
|
||||
|
||||
if (config.isEmpty()) {
|
||||
logger.error() << "Failed to get config template";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
return buildClientProtocolConfig(credentials, container, srv, clientTemplate, xrayClientId, errorCode);
|
||||
}
|
||||
QString xrayPublicKey =
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||
logger.error() << "Failed to get public key";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayShortId =
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||
logger.error() << "Failed to get short ID";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayShortId.replace("\n", "");
|
||||
|
||||
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
|
||||
logger.error() << "Config template missing required variables:"
|
||||
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
|
||||
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
|
||||
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayClientId);
|
||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||
|
||||
XrayProtocolConfig protocolConfig;
|
||||
if (serverConfig) {
|
||||
protocolConfig.serverConfig = *serverConfig;
|
||||
}
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
clientConfig.localPort = "";
|
||||
clientConfig.id = xrayClientId;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
#define XRAY_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
|
||||
class XrayConfigurator : public ConfiguratorBase
|
||||
{
|
||||
@@ -21,67 +18,10 @@ public:
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
amnezia::ErrorCode applyServerSettingsToRemote(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
bool appendNewClient,
|
||||
QString *outClientId = nullptr);
|
||||
|
||||
amnezia::ErrorCode writeServerConfigForSetup(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings);
|
||||
|
||||
bool uploadClientTemplate(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::XrayClientTemplate &clientTemplate) const;
|
||||
|
||||
amnezia::XrayClientTemplate readClientTemplate(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, bool &outFound) const;
|
||||
|
||||
static bool isSecuritySupportedOnSelfHosted(const amnezia::XrayServerConfig &srv);
|
||||
|
||||
private:
|
||||
QJsonArray collectServerClients(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, const QString &flowValue,
|
||||
const QString &fallbackClientId, amnezia::ErrorCode &outError) const;
|
||||
|
||||
amnezia::ErrorCode uploadServerConfigAtomically(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, const QString &listenPort,
|
||||
const QJsonObject &serverConfig) const;
|
||||
|
||||
amnezia::ErrorCode readContainerKeyFile(amnezia::DockerContainer container,
|
||||
const amnezia::ServerCredentials &credentials,
|
||||
const QString &path, QString &out) const;
|
||||
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
|
||||
amnezia::ErrorCode uploadServerConfigJson(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::DnsSettings &dnsSettings, const QJsonObject &serverConfig) const;
|
||||
|
||||
amnezia::XrayProtocolConfig buildClientProtocolConfig(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::XrayServerConfig &srv,
|
||||
const amnezia::XrayClientTemplate &tpl,
|
||||
const QString &clientId,
|
||||
amnezia::ErrorCode &errorCode,
|
||||
const QString &prefetchedRealityPublicKey = {},
|
||||
const QString &prefetchedRealityShortId = {},
|
||||
const QString &prefetchedTlsPin = {}) const;
|
||||
|
||||
amnezia::ErrorCode readRealityKeyFiles(amnezia::DockerContainer container,
|
||||
const amnezia::ServerCredentials &credentials,
|
||||
QString &outPublicKey,
|
||||
QString &outShortId) const;
|
||||
|
||||
amnezia::ErrorCode ensureTlsCertificate(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
QString &outFingerprint) const;
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/api/gatewayPayloadBuilder.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
@@ -80,15 +78,17 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
// both country codes and service types are arrays here, one entry per gateway stack the user has
|
||||
const QJsonObject payload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::locale, apiUtils::getAppLanguageCode(m_appSettingsRepository))
|
||||
.addField(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode))
|
||||
.addField(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType))
|
||||
.build();
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
|
||||
|
||||
if (services.contains(apiDefs::key::userCountryCode)) {
|
||||
payload.insert(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode));
|
||||
}
|
||||
if (services.contains(apiDefs::key::serviceType)) {
|
||||
payload.insert(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType));
|
||||
}
|
||||
|
||||
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
|
||||
return future.then([gatewayController](QPair<ErrorCode, QByteArray> result) -> QPair<ErrorCode, QJsonArray> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "servicesCatalogController.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QSysInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QEventLoop>
|
||||
#include <QDebug>
|
||||
@@ -10,7 +11,6 @@
|
||||
#include <limits>
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/gatewayPayloadBuilder.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
@@ -212,7 +212,11 @@ ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository
|
||||
|
||||
ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData)
|
||||
{
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository).build();
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
|
||||
apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||
@@ -238,7 +242,7 @@ ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &services
|
||||
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QSysInfo>
|
||||
#include <QUuid>
|
||||
#include <QVariantMap>
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/api/gatewayPayloadBuilder.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
@@ -32,7 +31,7 @@
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include "core/utils/swiftBridge.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -56,38 +55,6 @@ QString getSubscriptionStatusForRenewal(const ApiConfig &apiConfig)
|
||||
|
||||
return QStringLiteral("active");
|
||||
}
|
||||
|
||||
QString normalizeCaptchaSolution(const QString &captchaSolution)
|
||||
{
|
||||
QString normalizedSolution;
|
||||
normalizedSolution.reserve(captchaSolution.size());
|
||||
for (const QChar &ch : captchaSolution) {
|
||||
const ushort u = ch.unicode();
|
||||
if (u >= '0' && u <= '9') {
|
||||
normalizedSolution += ch;
|
||||
} else if (u >= 0xFF10 && u <= 0xFF19) {
|
||||
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
|
||||
}
|
||||
}
|
||||
return normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
|
||||
}
|
||||
|
||||
bool fillCaptchaInfoFromResponse(const QByteArray &responseBody, SubscriptionController::CaptchaInfo &captchaInfo)
|
||||
{
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (!jsonDoc.isObject()) {
|
||||
return false;
|
||||
}
|
||||
const QJsonObject jsonObj = jsonDoc.object();
|
||||
if (!jsonObj.contains(QStringLiteral("captcha_id")) || !jsonObj.contains(QStringLiteral("captcha_image"))) {
|
||||
return false;
|
||||
}
|
||||
captchaInfo.captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
|
||||
captchaInfo.captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
|
||||
captchaInfo.hint = jsonObj.value(QStringLiteral("hint")).toString();
|
||||
captchaInfo.isRequired = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +64,39 @@ SubscriptionController::SubscriptionController(SecureServersRepository* serversR
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject SubscriptionController::GatewayRequestData::toJsonObject() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
if (!osVersion.isEmpty()) {
|
||||
obj[apiDefs::key::osVersion] = osVersion;
|
||||
}
|
||||
if (!appVersion.isEmpty()) {
|
||||
obj[apiDefs::key::appVersion] = appVersion;
|
||||
}
|
||||
if (!appLanguage.isEmpty()) {
|
||||
obj[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
if (!installationUuid.isEmpty()) {
|
||||
obj[apiDefs::key::uuid] = installationUuid;
|
||||
}
|
||||
if (!userCountryCode.isEmpty()) {
|
||||
obj[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
}
|
||||
if (!serverCountryCode.isEmpty()) {
|
||||
obj[apiDefs::key::serverCountryCode] = serverCountryCode;
|
||||
}
|
||||
if (!serviceType.isEmpty()) {
|
||||
obj[apiDefs::key::serviceType] = serviceType;
|
||||
}
|
||||
if (!serviceProtocol.isEmpty()) {
|
||||
obj[apiDefs::key::serviceProtocol] = serviceProtocol;
|
||||
}
|
||||
if (!authData.isEmpty()) {
|
||||
obj[apiDefs::key::authData] = authData;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
SubscriptionController::ProtocolData SubscriptionController::generateProtocolData(const QString &protocol)
|
||||
{
|
||||
ProtocolData protocolData;
|
||||
@@ -111,15 +111,13 @@ SubscriptionController::ProtocolData SubscriptionController::generateProtocolDat
|
||||
return protocolData;
|
||||
}
|
||||
|
||||
QString SubscriptionController::publicKeyForProtocol(const QString &protocol, const ProtocolData &protocolData)
|
||||
void SubscriptionController::appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload)
|
||||
{
|
||||
if (protocol == configKey::awg) {
|
||||
return protocolData.wireGuardClientPubKey;
|
||||
apiPayload[apiDefs::key::publicKey] = protocolData.wireGuardClientPubKey;
|
||||
} else if (protocol == configKey::vless) {
|
||||
apiPayload[apiDefs::key::publicKey] = protocolData.xrayUuid;
|
||||
}
|
||||
if (protocol == configKey::vless) {
|
||||
return protocolData.xrayUuid;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
@@ -157,14 +155,23 @@ ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByt
|
||||
|
||||
// TODO looks like this block can be removed after v1 configs EOL
|
||||
|
||||
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
|
||||
serverProtocolConfig[configKey::junkPacketCount] = clientProtocolConfig.value(configKey::junkPacketCount);
|
||||
serverProtocolConfig[configKey::junkPacketMinSize] = clientProtocolConfig.value(configKey::junkPacketMinSize);
|
||||
serverProtocolConfig[configKey::junkPacketMaxSize] = clientProtocolConfig.value(configKey::junkPacketMaxSize);
|
||||
serverProtocolConfig[configKey::initPacketJunkSize] = clientProtocolConfig.value(configKey::initPacketJunkSize);
|
||||
serverProtocolConfig[configKey::responsePacketJunkSize] = clientProtocolConfig.value(configKey::responsePacketJunkSize);
|
||||
serverProtocolConfig[configKey::initPacketMagicHeader] = clientProtocolConfig.value(configKey::initPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::responsePacketMagicHeader] = clientProtocolConfig.value(configKey::responsePacketMagicHeader);
|
||||
serverProtocolConfig[configKey::underloadPacketMagicHeader] = clientProtocolConfig.value(configKey::underloadPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::transportPacketMagicHeader] = clientProtocolConfig.value(configKey::transportPacketMagicHeader);
|
||||
|
||||
for (const QString &key : awgProtocolKeys) {
|
||||
const QJsonValue value = clientProtocolConfig.value(key);
|
||||
if (value.isString() && !value.toString().isEmpty()) {
|
||||
serverProtocolConfig[key] = value;
|
||||
}
|
||||
}
|
||||
serverProtocolConfig[configKey::cookieReplyPacketJunkSize] = clientProtocolConfig.value(configKey::cookieReplyPacketJunkSize);
|
||||
serverProtocolConfig[configKey::transportPacketJunkSize] = clientProtocolConfig.value(configKey::transportPacketJunkSize);
|
||||
serverProtocolConfig[configKey::specialJunk1] = clientProtocolConfig.value(configKey::specialJunk1);
|
||||
serverProtocolConfig[configKey::specialJunk2] = clientProtocolConfig.value(configKey::specialJunk2);
|
||||
serverProtocolConfig[configKey::specialJunk3] = clientProtocolConfig.value(configKey::specialJunk3);
|
||||
serverProtocolConfig[configKey::specialJunk4] = clientProtocolConfig.value(configKey::specialJunk4);
|
||||
serverProtocolConfig[configKey::specialJunk5] = clientProtocolConfig.value(configKey::specialJunk5);
|
||||
|
||||
//
|
||||
|
||||
@@ -190,6 +197,9 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
|
||||
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
|
||||
}
|
||||
if (responseObj.contains(apiDefs::key::serviceInfo)) {
|
||||
apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject());
|
||||
}
|
||||
@@ -201,29 +211,28 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
|
||||
ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo)
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData)
|
||||
{
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, serviceType)
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.build();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
|
||||
fillCaptchaInfoFromResponse(responseBody, captchaInfo);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -233,9 +242,9 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
@@ -254,15 +263,20 @@ ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCoun
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, serviceType)
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::email, trimmedEmail)
|
||||
.build();
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload.insert(apiDefs::key::email, trimmedEmail);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody);
|
||||
@@ -303,13 +317,19 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
int *duplicateServerIndex)
|
||||
{
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, serviceType)
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::transactionId, transactionId)
|
||||
.build();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
userCountryCode,
|
||||
"",
|
||||
serviceType,
|
||||
serviceProtocol,
|
||||
QJsonObject() };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload[apiDefs::key::transactionId] = transactionId;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
|
||||
@@ -364,6 +384,8 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
|
||||
ApiV2ServerConfig* apiV2 = &apiV2ServerConfig;
|
||||
apiV2->apiConfig.vpnKey = normalizedKey;
|
||||
apiV2->apiConfig.isTestPurchase = isTestPurchase;
|
||||
apiV2->apiConfig.isInAppPurchase = true;
|
||||
apiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
apiV2->crc = crc;
|
||||
|
||||
@@ -373,8 +395,7 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
@@ -382,43 +403,29 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
|
||||
if (!newCountryCode.isEmpty()) {
|
||||
const auto availableCountries = apiV2->apiConfig.availableCountries;
|
||||
for (const auto &country : availableCountries) {
|
||||
const auto countryObject = country.toObject();
|
||||
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != newCountryCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto availableProtocols = countryObject.value(apiDefs::key::availableProtocols).toArray();
|
||||
if (!availableProtocols.isEmpty() && !availableProtocols.contains(serviceProtocol)) {
|
||||
serviceProtocol = availableProtocols.first().toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
newCountryCode,
|
||||
apiV2->serviceType(),
|
||||
serviceProtocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, newCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.addField(apiDefs::key::isConnectEvent, isConnectEvent ? QJsonValue(true) : QJsonValue())
|
||||
.build();
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
if (isConnectEvent) {
|
||||
apiPayload[apiDefs::key::isConnectEvent] = true;
|
||||
}
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfoOut) {
|
||||
if (fillCaptchaInfoFromResponse(responseBody, *captchaInfoOut) && usedProtocolDataOut) {
|
||||
*usedProtocolDataOut = protocolData;
|
||||
}
|
||||
}
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
@@ -428,41 +435,31 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData, const QByteArray &responseBody)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
ErrorCode errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
|
||||
|
||||
|
||||
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
|
||||
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
newApiV2->apiConfig.isInAppPurchase = apiV2->apiConfig.isInAppPurchase;
|
||||
newApiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
|
||||
|
||||
newApiV2->authData = apiV2->authData;
|
||||
newApiV2->crc = apiV2->crc;
|
||||
|
||||
|
||||
if (apiV2->nameOverriddenByUser) {
|
||||
newApiV2->name = apiV2->name;
|
||||
newApiV2->displayName = apiV2->displayName;
|
||||
newApiV2->nameOverriddenByUser = true;
|
||||
}
|
||||
|
||||
@@ -471,50 +468,6 @@ ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serve
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode,
|
||||
bool isConnectEvent, const ProtocolData &protocolData,
|
||||
const QString &captchaId, const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, newCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.addField(apiDefs::key::captchaId, captchaId)
|
||||
.addField(apiDefs::key::captchaSolution, normalizeCaptchaSolution(captchaSolution))
|
||||
.addField(apiDefs::key::isConnectEvent, isConnectEvent ? QJsonValue(true) : QJsonValue())
|
||||
.build();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
|
||||
}
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
@@ -526,12 +479,18 @@ ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, apiV2->apiConfig.serverCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
apiV2->apiConfig.serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
@@ -557,13 +516,18 @@ ErrorCode SubscriptionController::deactivateExternalDevice(const QString &server
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::installationUuid, uuid)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
uuid,
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
@@ -591,14 +555,19 @@ ErrorCode SubscriptionController::exportNativeConfig(const QString &serverId, co
|
||||
QString protocol = configKey::awg;
|
||||
ProtocolData protocolData = generateProtocolData(protocol);
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::serviceProtocol, protocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(protocol, protocolData))
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
protocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(protocol, protocolData, apiPayload);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
@@ -621,13 +590,18 @@ ErrorCode SubscriptionController::revokeNativeConfig(const QString &serverId, co
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString protocol = configKey::awg;
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::serviceProtocol, protocol)
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
serverCountryCode,
|
||||
apiV2->serviceType(),
|
||||
protocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
@@ -658,20 +632,19 @@ ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, Q
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
|
||||
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
|
||||
{
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (!hasInstalledContainers) {
|
||||
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
if (isApiKeyExpired(serverId)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
@@ -692,7 +665,7 @@ void SubscriptionController::removeApiConfig(const QString &serverId)
|
||||
.arg(hostName)
|
||||
.arg("");
|
||||
|
||||
SWIFT_BRIDGE_NAMESPACE::removeVPNC(vpncName.toStdString());
|
||||
AmneziaVPN::removeVPNC(vpncName.toStdString());
|
||||
#endif
|
||||
|
||||
apiV2->dns1.clear();
|
||||
@@ -763,36 +736,6 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
QString SubscriptionController::currentProtocol(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() ? apiV2->serviceProtocol() : QString();
|
||||
}
|
||||
|
||||
QStringList SubscriptionController::availableProtocols(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto currentCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
const auto availableCountries = apiV2->apiConfig.availableCountries;
|
||||
|
||||
QStringList protocols;
|
||||
for (const auto &country : availableCountries) {
|
||||
const auto countryObject = country.toObject();
|
||||
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != currentCountryCode) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &protocol : countryObject.value(apiDefs::key::availableProtocols).toArray()) {
|
||||
protocols.push_back(protocol.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return protocols;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
int *duplicateServerIndex)
|
||||
@@ -934,13 +877,20 @@ ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonO
|
||||
}
|
||||
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
|
||||
.addField(apiDefs::key::subscriptionStatus, getSubscriptionStatusForRenewal(apiV2->apiConfig))
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
"",
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/account_info"), apiPayload, responseBody, isTestPurchase);
|
||||
@@ -948,8 +898,7 @@ ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonO
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
accountInfo = QJsonDocument::fromJson(responseBody).object();
|
||||
|
||||
accountInfo = QJsonDocument::fromJson(responseBody).object();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -966,19 +915,25 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
|
||||
}
|
||||
|
||||
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
|
||||
.addField(apiDefs::key::subscriptionStatus, getSubscriptionStatusForRenewal(apiV2->apiConfig))
|
||||
.build();
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
"",
|
||||
apiV2->serviceType(),
|
||||
"",
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
|
||||
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
|
||||
auto *watcher = new QFutureWatcher<QPair<ErrorCode, QByteArray>>();
|
||||
QObject::connect(watcher, &QFutureWatcher<QPair<ErrorCode, QByteArray>>::finished,
|
||||
@@ -1000,48 +955,3 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &userCountryCode,
|
||||
const QString &serviceType,
|
||||
const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData,
|
||||
const QString &captchaId,
|
||||
const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut)
|
||||
{
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, serviceType)
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::captchaId, captchaId)
|
||||
.addField(apiDefs::key::captchaSolution, normalizeCaptchaSolution(captchaSolution))
|
||||
.build();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -27,23 +27,29 @@ public:
|
||||
QString xrayUuid;
|
||||
};
|
||||
|
||||
struct CaptchaInfo {
|
||||
QString captchaId;
|
||||
QString captchaImageBase64;
|
||||
QString hint;
|
||||
bool isRequired = false;
|
||||
struct GatewayRequestData
|
||||
{
|
||||
QString osVersion;
|
||||
QString appVersion;
|
||||
QString appLanguage;
|
||||
QString installationUuid;
|
||||
QString userCountryCode;
|
||||
QString serverCountryCode;
|
||||
QString serviceType;
|
||||
QString serviceProtocol;
|
||||
QJsonObject authData;
|
||||
|
||||
QJsonObject toJsonObject() const;
|
||||
};
|
||||
|
||||
explicit SubscriptionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
ProtocolData generateProtocolData(const QString &protocol);
|
||||
|
||||
static QString publicKeyForProtocol(const QString &protocol, const ProtocolData &protocolData);
|
||||
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
|
||||
|
||||
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo);
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
@@ -52,12 +58,7 @@ public:
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
|
||||
|
||||
ErrorCode resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
const ProtocolData &protocolData, const QString &captchaId,
|
||||
const QString &captchaSolution, CaptchaInfo *retryCaptchaOut = nullptr);
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
ErrorCode deactivateDevice(const QString &serverId);
|
||||
|
||||
@@ -69,8 +70,7 @@ public:
|
||||
|
||||
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
|
||||
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
|
||||
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
|
||||
|
||||
void removeApiConfig(const QString &serverId);
|
||||
|
||||
@@ -78,8 +78,6 @@ public:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
QString currentProtocol(const QString &serverId) const;
|
||||
QStringList availableProtocols(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
@@ -100,19 +98,12 @@ public:
|
||||
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol);
|
||||
|
||||
ErrorCode resolveImportServiceCaptcha(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &captchaId, const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut = nullptr);
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(const QString &serverId) const;
|
||||
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
|
||||
ErrorCode applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData, const QByteArray &responseBody);
|
||||
void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &userCountryCode,
|
||||
const QByteArray &apiResponseBody);
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/payloadSender.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
@@ -31,6 +32,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
|
||||
@@ -44,99 +46,19 @@ bool ConnectionController::isConnected() const
|
||||
|
||||
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
{
|
||||
emit connectionStateChanged(state);
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
if (m_vpnConnection) {
|
||||
emit setConnectionStateRequested(state);
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
if (serverConfigUtils::isLegacyApiSubscription(kind)) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (serverConfigUtils::isApiV2Subscription(kind)) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -145,15 +67,13 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
bool isApiConfig = false;
|
||||
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
const QString primaryDns = m_appSettingsRepository->primaryDns();
|
||||
const QString secondaryDns = m_appSettingsRepository->secondaryDns();
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = cfg->getDnsPair(m_appSettingsRepository->useAmneziaDns(), primaryDns, secondaryDns);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -163,7 +83,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -173,7 +93,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -185,7 +105,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
|
||||
@@ -200,6 +120,20 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (m_appSettingsRepository->useAmneziaDns()) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else {
|
||||
dns.first = m_appSettingsRepository->primaryDns();
|
||||
}
|
||||
}
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_appSettingsRepository->secondaryDns();
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
@@ -216,11 +150,6 @@ ErrorCode ConnectionController::openConnection(const QString &serverId)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value() && !apiV2->sendPayload.isEmpty()) {
|
||||
PayloadSender::sendAll(apiV2->sendPayload);
|
||||
}
|
||||
|
||||
emit openConnectionRequested(serverId, container, vpnConfiguration);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -67,6 +65,7 @@ signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -74,8 +73,6 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "core/controllers/coreSignalHandlers.h"
|
||||
#include "logger.h"
|
||||
#include "secureQSettings.h"
|
||||
#include "core/utils/appUiConfig.h"
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/utils/installedAppsImageProvider.h"
|
||||
@@ -19,12 +18,11 @@
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include "core/utils/swiftBridge.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent,
|
||||
bool skipPlatformControllerInit)
|
||||
QQmlApplicationEngine *engine, QObject *parent)
|
||||
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||
{
|
||||
initRepositories();
|
||||
@@ -33,10 +31,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
if (!skipPlatformControllerInit) {
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
}
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
initLogging();
|
||||
|
||||
m_translator = new QTranslator(this);
|
||||
@@ -90,9 +86,6 @@ void CoreController::initModels()
|
||||
m_xrayConfigModel = new XrayConfigModel(this);
|
||||
setQmlContextProperty("XrayConfigModel", m_xrayConfigModel);
|
||||
|
||||
m_xrayConfigSnapshotsModel = new XrayConfigSnapshotsModel(m_appSettingsRepository, m_xrayConfigModel, this);
|
||||
setQmlContextProperty("XrayConfigSnapshotsModel", m_xrayConfigSnapshotsModel);
|
||||
|
||||
m_torConfigModel = new TorConfigModel(this);
|
||||
setQmlContextProperty("TorConfigModel", m_torConfigModel);
|
||||
|
||||
@@ -107,12 +100,6 @@ void CoreController::initModels()
|
||||
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
|
||||
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
|
||||
|
||||
m_mtProxyConfigModel = new MtProxyConfigModel(this);
|
||||
setQmlContextProperty("MtProxyConfigModel", m_mtProxyConfigModel);
|
||||
|
||||
m_telemtConfigModel = new TelemtConfigModel(this);
|
||||
setQmlContextProperty("TelemtConfigModel", m_telemtConfigModel);
|
||||
|
||||
m_clientManagementModel = new ClientManagementModel(this);
|
||||
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
|
||||
|
||||
@@ -182,8 +169,7 @@ void CoreController::initControllers()
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel,
|
||||
#endif
|
||||
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel,
|
||||
m_connectionController, this);
|
||||
m_sftpConfigModel, m_socks5ConfigModel, this);
|
||||
setQmlContextProperty("InstallController", m_installUiController);
|
||||
|
||||
m_importController = new ImportUiController(m_importCoreController, this);
|
||||
@@ -195,7 +181,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
@@ -216,17 +202,12 @@ void CoreController::initControllers()
|
||||
m_systemController = new SystemController(this);
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
m_networkReachabilityController = new NetworkReachabilityController(this);
|
||||
setQmlContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
setQmlContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
|
||||
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
|
||||
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
|
||||
|
||||
m_subscriptionUiController = new SubscriptionUiController(m_serversController, m_apiServicesModel, m_servicesCatalogController, m_subscriptionController,
|
||||
m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_apiAccountInfoModel,
|
||||
m_apiCountryModel, m_apiDevicesModel, m_settingsController,
|
||||
m_connectionController, this);
|
||||
m_apiCountryModel, m_apiDevicesModel, m_settingsController, this);
|
||||
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
@@ -259,7 +240,7 @@ void CoreController::initAppleController()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
QTimer::singleShot(0, this, [this]() { SWIFT_BRIDGE_NAMESPACE::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -286,10 +267,6 @@ void CoreController::initSignalHandlers()
|
||||
if (m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_updateController->checkForUpdates();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
@@ -299,15 +276,15 @@ void CoreController::updateTranslator(const QLocale &locale)
|
||||
}
|
||||
|
||||
QStringList availableTranslations;
|
||||
QDirIterator it(":/translations", QStringList(APP_TS_PREFIX "_*.qm"), QDir::Files);
|
||||
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||
while (it.hasNext()) {
|
||||
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/" APP_TS_PREFIX "_") + lang;
|
||||
QString strFileName = QString(":/translations/" APP_TS_PREFIX "_%1.qm").arg(locale.name());
|
||||
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||
for (const QString &translation : availableTranslations) {
|
||||
if (translation.contains(translationFilePrefix)) {
|
||||
strFileName = translation;
|
||||
@@ -318,7 +295,7 @@ void CoreController::updateTranslator(const QLocale &locale)
|
||||
if (m_translator->load(strFileName)) {
|
||||
QCoreApplication::installTranslator(m_translator);
|
||||
} else {
|
||||
if (m_translator->load(QString(":/translations/" APP_TS_PREFIX "_en.qm"))) {
|
||||
if (m_translator->load(QString(":/translations/amneziavpn_en.qm"))) {
|
||||
QCoreApplication::installTranslator(m_translator);
|
||||
}
|
||||
}
|
||||
@@ -352,6 +329,9 @@ void CoreController::openConnectionByIndex(int serverIndex)
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
}
|
||||
if (m_serversController) {
|
||||
m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
#include "ui/controllers/networkReachabilityController.h"
|
||||
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/selfhosted/usersController.h"
|
||||
@@ -65,15 +64,11 @@
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigSnapshotsModel.h"
|
||||
#include "ui/models/protocolsModel.h"
|
||||
#include "ui/models/services/torConfigModel.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/services/mtProxyConfigModel.h"
|
||||
#include "ui/models/services/telemtConfigModel.h"
|
||||
|
||||
#include "ui/models/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
@@ -82,16 +77,37 @@
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr,
|
||||
bool skipPlatformControllerInit = false);
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||
|
||||
PageController* pageController() const;
|
||||
void setQmlRoot();
|
||||
@@ -104,36 +120,6 @@ signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
protected:
|
||||
SecureServersRepository* serversRepositoryProtected() const { return m_serversRepository; }
|
||||
SecureAppSettingsRepository* appSettingsRepositoryProtected() const { return m_appSettingsRepository; }
|
||||
ServersModel* serversModelProtected() const { return m_serversModel; }
|
||||
ContainersModel* containersModelProtected() const { return m_containersModel; }
|
||||
ApiServicesModel* apiServicesModelProtected() const { return m_apiServicesModel; }
|
||||
NewsModel* newsModelProtected() const { return m_newsModel; }
|
||||
AllowedDnsModel* allowedDnsModelProtected() const { return m_allowedDnsModel; }
|
||||
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
|
||||
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
|
||||
LanguageModel* languageModelProtected() const { return m_languageModel; }
|
||||
ConnectionUiController* connectionUiControllerProtected() const { return m_connectionUiController; }
|
||||
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
|
||||
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
|
||||
ExportController* exportControllerProtected() const { return m_exportController; }
|
||||
InstallController* installControllerProtected() const { return m_installController; }
|
||||
ServersController* serversControllerProtected() const { return m_serversController; }
|
||||
SettingsUiController* settingsUiControllerProtected() const { return m_settingsUiController; }
|
||||
SettingsController* settingsControllerProtected() const { return m_settingsController; }
|
||||
AllowedDnsUiController* allowedDnsUiControllerProtected() const { return m_allowedDnsUiController; }
|
||||
AllowedDnsController* allowedDnsControllerProtected() const { return m_allowedDnsController; }
|
||||
LanguageUiController* languageUiControllerProtected() const { return m_languageUiController; }
|
||||
IpSplitTunnelingController* ipSplitTunnelingControllerProtected() const { return m_ipSplitTunnelingController; }
|
||||
IpSplitTunnelingUiController* ipSplitTunnelingUiControllerProtected() const { return m_ipSplitTunnelingUiController; }
|
||||
AppSplitTunnelingController* appSplitTunnelingControllerProtected() const { return m_appSplitTunnelingController; }
|
||||
AppSplitTunnelingUiController* appSplitTunnelingUiControllerProtected() const { return m_appSplitTunnelingUiController; }
|
||||
ServersUiController* serversUiControllerProtected() const { return m_serversUiController; }
|
||||
ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; }
|
||||
ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; }
|
||||
|
||||
private:
|
||||
void initRepositories();
|
||||
void initCoreControllers();
|
||||
@@ -170,7 +156,6 @@ private:
|
||||
ServersUiController* m_serversUiController;
|
||||
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
|
||||
SystemController* m_systemController;
|
||||
NetworkReachabilityController* m_networkReachabilityController;
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
@@ -215,7 +200,6 @@ private:
|
||||
|
||||
OpenVpnConfigModel* m_openVpnConfigModel;
|
||||
XrayConfigModel* m_xrayConfigModel;
|
||||
XrayConfigSnapshotsModel* m_xrayConfigSnapshotsModel;
|
||||
TorConfigModel* m_torConfigModel;
|
||||
WireGuardConfigModel* m_wireGuardConfigModel;
|
||||
AwgConfigModel* m_awgConfigModel;
|
||||
@@ -224,8 +208,6 @@ private:
|
||||
#endif
|
||||
SftpConfigModel* m_sftpConfigModel;
|
||||
Socks5ProxyConfigModel* m_socks5ConfigModel;
|
||||
MtProxyConfigModel* m_mtProxyConfigModel;
|
||||
TelemtConfigModel* m_telemtConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "coreSignalHandlers.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -34,6 +33,7 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include "core/utils/swiftBridge.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
CoreSignalHandlers::CoreSignalHandlers(CoreController* coreController, QObject* parent)
|
||||
@@ -125,9 +125,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIdChanged,
|
||||
m_coreController->m_installUiController, [this](const QString &serverId) {
|
||||
if (!serverId.isEmpty()) {
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
|
||||
m_coreController->m_installUiController, [this](int serverIndex) {
|
||||
if (serverIndex >= 0) {
|
||||
m_coreController->m_installUiController->clearProcessedServerCredentials();
|
||||
}
|
||||
});
|
||||
@@ -145,9 +145,7 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
QtConcurrent::run([this, serverId, row, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
@@ -158,17 +156,15 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,14 +176,17 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,12 +205,12 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
}, Qt::DirectConnection);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
}, Qt::DirectConnection);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
|
||||
&InstallController::clearCachedProfile);
|
||||
@@ -238,16 +237,13 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +284,6 @@ void CoreSignalHandlers::initClientManagementModelUpdateHandler()
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
|
||||
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
|
||||
connect(m_coreController->m_usersController, &UsersController::revokeFinished,
|
||||
m_coreController->m_exportController, &ExportController::revokeFinished);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initSitesModelUpdateHandler()
|
||||
@@ -354,9 +348,6 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
@@ -413,7 +404,7 @@ void CoreSignalHandlers::initIosImportHandler()
|
||||
void CoreSignalHandlers::initIosSettingsHandler()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { SWIFT_BRIDGE_NAMESPACE::toggleScreenshots(enabled); });
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -440,6 +431,9 @@ void CoreSignalHandlers::initNotificationHandler()
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
|
||||
&UpdateUiController::checkForUpdates);
|
||||
|
||||
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
|
||||
const QString version = m_coreController->m_updateUiController->getVersion();
|
||||
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "cryptoUtils.h"
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/utils/ipcClient.h"
|
||||
@@ -30,8 +30,6 @@ namespace
|
||||
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 errorResponsePatternQrSessionNotFound("QR session not found");
|
||||
constexpr QLatin1String errorResponsePatternSessionNotFound("Session not found");
|
||||
|
||||
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||
|
||||
@@ -39,81 +37,20 @@ namespace
|
||||
constexpr int httpStatusCodeConflict = 409;
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
constexpr int httpStatusCodePaymentRequired = 402;
|
||||
constexpr int httpStatusCodeRequestTimeout = 408;
|
||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
|
||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||
|
||||
QStringList shuffledProxyUrls(const QStringList &proxyUrls)
|
||||
{
|
||||
QStringList shuffled = proxyUrls;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
QString getProxyUrlsCacheKey(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
return QStringLiteral("service_%1_country_%2").arg(serviceType, userCountryCode);
|
||||
}
|
||||
|
||||
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
|
||||
{
|
||||
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!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(encryptedPayload);
|
||||
|
||||
decryptedPayload = CryptoUtils::decryptAes256Cbc(ba, decKey, iv);
|
||||
if (decryptedPayload.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
decryptedPayload = encryptedPayload;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
|
||||
{
|
||||
if (cachedProxyUrlsEncrypted.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray cachedProxyUrlsDecrypted;
|
||||
if (!decryptProxyUrlsPayload(cachedProxyUrlsEncrypted, isDevEnvironment, cachedProxyUrlsDecrypted)) {
|
||||
qCritical() << "error decrypting cached proxy urls payload";
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonArray endpointsArray = QJsonDocument::fromJson(cachedProxyUrlsDecrypted).array();
|
||||
QStringList endpoints;
|
||||
endpoints.reserve(endpointsArray.size());
|
||||
for (const QJsonValue &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isDevEnvironment(isDevEnvironment),
|
||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -147,29 +84,40 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
||||
}
|
||||
#endif
|
||||
|
||||
encRequestData.key = CryptoUtils::generateRandomBytes(32);
|
||||
encRequestData.iv = CryptoUtils::generateRandomBytes(32);
|
||||
encRequestData.salt = CryptoUtils::generateRandomBytes(8);
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
EVP_PKEY *publicKey = CryptoUtils::loadPublicKeyFromPem(rsaKey);
|
||||
if (publicKey == nullptr) {
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
QByteArray encryptedKeyPayload = CryptoUtils::rsaEncrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
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;
|
||||
}
|
||||
|
||||
QByteArray encryptedApiPayload = CryptoUtils::encryptAes256Cbc(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv);
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
if (encryptedKeyPayload.isEmpty() || encryptedApiPayload.isEmpty()) {
|
||||
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;
|
||||
@@ -191,19 +139,15 @@ GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(co
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
|
||||
QByteArray decrypted = CryptoUtils::decryptAes256Cbc(encryptedResponseBody, key, iv);
|
||||
if (!decrypted.isEmpty()) {
|
||||
result.decryptedBody = decrypted;
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
result.isDecryptionSuccessful = true;
|
||||
} else {
|
||||
} catch (...) {
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
}
|
||||
|
||||
qDebug() << "tryDecryptResponseBody: inputBytes=" << encryptedResponseBody.size() << "keyBytes=" << key.size()
|
||||
<< "ivBytes=" << iv.size() << "saltBytes=" << salt.size() << "replyError=" << replyError
|
||||
<< "decrypted=" << result.isDecryptionSuccessful << "outputBytes=" << decrypted.size();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -262,9 +206,8 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
const auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, responseBody);
|
||||
auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -274,6 +217,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -312,12 +256,13 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||
decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, decryptionResult.decryptedBody));
|
||||
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();
|
||||
@@ -360,9 +305,8 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, proxyUrlsCacheKey, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
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,
|
||||
@@ -408,6 +352,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
@@ -423,12 +369,10 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||
|
||||
if (proxyStorageUrls.empty()) {
|
||||
qDebug() << "empty storage endpoint list";
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
@@ -443,8 +387,26 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
if (!decryptProxyUrlsPayload(encryptedResponseBody, m_isDevEnvironment, 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;
|
||||
}
|
||||
@@ -455,8 +417,6 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encryptedResponseBody);
|
||||
|
||||
return endpoints;
|
||||
} else {
|
||||
auto replyError = reply->error();
|
||||
@@ -468,7 +428,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||
@@ -486,9 +446,7 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
||||
apiErrorMessage = jsonObj.value(QStringLiteral("message")).toString().trimmed();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "failed to decrypt the data, bypassing the proxy: replyError=" << replyError
|
||||
<< "bodyBytes=" << responseBody.size()
|
||||
<< "(an empty body looks the same here as a wrong key, compare with tryDecryptResponseBody above)";
|
||||
qDebug() << "failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -501,20 +459,15 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeRequestTimeout) {
|
||||
qDebug() << "keeping the proxy: apiHttpStatus=" << apiHttpStatus;
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotFound) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3) || responseBody.contains(errorResponsePatternQrSessionNotFound)
|
||||
|| responseBody.contains(errorResponsePatternSessionNotFound)) {
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
@@ -524,24 +477,18 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeConflict) {
|
||||
qDebug() << "keeping the proxy: apiHttpStatus=" << apiHttpStatus;
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodePaymentRequired) {
|
||||
qDebug() << "keeping the proxy: apiHttpStatus=" << apiHttpStatus;
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeUnprocessableEntity) {
|
||||
const bool bypass = apiErrorMessage != unprocessableSubscriptionMessage;
|
||||
qDebug() << "apiHttpStatus=" << apiHttpStatus << "bypass=" << bypass
|
||||
<< "(decided by the api message, which is not printed)";
|
||||
return bypass;
|
||||
return apiErrorMessage != unprocessableSubscriptionMessage;
|
||||
}
|
||||
if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << "bypassing the proxy on a reply error:" << replyError << "apiHttpStatus=" << apiHttpStatus;
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
qDebug() << "keeping the proxy: nothing matched, apiHttpStatus=" << apiHttpStatus << "replyError=" << replyError;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -619,12 +566,10 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete)
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete(shuffledProxyUrls(readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment)));
|
||||
onComplete({});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -637,17 +582,33 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this,
|
||||
[this, proxyStorageUrls, currentProxyStorageIndex, proxyUrlsCacheKey, onComplete, reply]() {
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
if (!decryptProxyUrlsPayload(encrypted, m_isDevEnvironment, 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, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -655,9 +616,13 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encrypted);
|
||||
|
||||
onComplete(shuffledProxyUrls(endpoints));
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -666,7 +631,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class GatewayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
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);
|
||||
@@ -58,7 +55,7 @@ private:
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete);
|
||||
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,
|
||||
@@ -68,7 +65,6 @@ private:
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "ipSplitTunnelingController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include <QJsonObject>
|
||||
#include <QDebug>
|
||||
|
||||
IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent)
|
||||
: QObject(parent),
|
||||
@@ -15,56 +14,47 @@ IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsReposito
|
||||
fillSites();
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QStringList &ips)
|
||||
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip)
|
||||
{
|
||||
QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
if (existing.contains(hostname) && ips.isEmpty()) {
|
||||
if (existing.contains(hostname) && ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
bool changed = false;
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_sites.append(qMakePair(hostname, ips));
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::addSites(const QMap<QString, QStringList> &sites, bool replaceExisting)
|
||||
void IpSplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
|
||||
{
|
||||
if (replaceExisting) {
|
||||
m_sites.clear();
|
||||
}
|
||||
for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) {
|
||||
const QString &hostname = it.key();
|
||||
const QStringList &ips = it.value();
|
||||
const QString &ip = it.value();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
}
|
||||
if (!ip.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_sites.append(qMakePair(hostname, ips));
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
}
|
||||
}
|
||||
if (replaceExisting) {
|
||||
@@ -82,11 +72,11 @@ bool IpSplitTunnelingController::addSite(const QString &hostname)
|
||||
}
|
||||
|
||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) {
|
||||
processSite(normalizedHostname, {});
|
||||
processSite(normalizedHostname, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addSiteInternal(normalizedHostname, {})) {
|
||||
if (addSiteInternal(normalizedHostname, "")) {
|
||||
QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo)));
|
||||
return true;
|
||||
}
|
||||
@@ -134,7 +124,7 @@ bool IpSplitTunnelingController::isSplitTunnelingEnabled() const
|
||||
return m_appSettingsRepository->isSitesSplitTunnelingEnabled();
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QStringList>> IpSplitTunnelingController::getCurrentSites() const
|
||||
QVector<QPair<QString, QString>> IpSplitTunnelingController::getCurrentSites() const
|
||||
{
|
||||
return m_sites;
|
||||
}
|
||||
@@ -144,7 +134,7 @@ void IpSplitTunnelingController::fillSites()
|
||||
QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
m_sites.clear();
|
||||
for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) {
|
||||
m_sites.append(qMakePair(it.key(), SecureAppSettingsRepository::siteIpList(it.value())));
|
||||
m_sites.append(qMakePair(it.key(), it.value().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,13 +144,8 @@ QString IpSplitTunnelingController::normalizeHostname(const QString &hostname) c
|
||||
normalized.replace("https://", "");
|
||||
normalized.replace("http://", "");
|
||||
normalized.replace("ftp://", "");
|
||||
|
||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const QStringList parts = normalized.split("/", Qt::SkipEmptyParts);
|
||||
return parts.isEmpty() ? QString() : parts.first();
|
||||
normalized = normalized.split("/", Qt::SkipEmptyParts).first();
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::validateHostname(const QString &hostname) const
|
||||
@@ -179,40 +164,29 @@ void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo)
|
||||
{
|
||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||
QString hostname = hostInfo.hostName();
|
||||
|
||||
QStringList allIpv4;
|
||||
|
||||
for (const QHostAddress &addr : addresses) {
|
||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||
allIpv4.append(addr.toString());
|
||||
}
|
||||
}
|
||||
allIpv4.removeDuplicates();
|
||||
qDebug() << "[SplitTunneling] Host resolved:" << hostname
|
||||
<< "-> adding all IPv4 addresses to list:" << allIpv4;
|
||||
|
||||
if (!allIpv4.isEmpty()) {
|
||||
processSiteAfterResolve(hostname, allIpv4);
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QStringList &ips)
|
||||
{
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
}
|
||||
}
|
||||
processSiteAfterResolve(hostname, addr.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSite(const QString &hostname, const QStringList &ips)
|
||||
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip)
|
||||
{
|
||||
addSiteInternal(hostname, ips);
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip)
|
||||
{
|
||||
addSiteInternal(hostname, ip);
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage)
|
||||
@@ -231,25 +205,12 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
}
|
||||
|
||||
QJsonArray jsonArray = jsonDocument.array();
|
||||
QMap<QString, QStringList> sites;
|
||||
QMap<QString, QString> sites;
|
||||
|
||||
for (auto jsonValue : jsonArray) {
|
||||
QJsonObject jsonObject = jsonValue.toObject();
|
||||
QString hostname = jsonObject.value("hostname").toString("");
|
||||
|
||||
QStringList ips;
|
||||
if (jsonObject.value("ips").isArray()) {
|
||||
const QJsonArray ipsArray = jsonObject.value("ips").toArray();
|
||||
for (const auto &ipValue : ipsArray) {
|
||||
ips.append(ipValue.toString());
|
||||
}
|
||||
}
|
||||
const QString singleIp = jsonObject.value("ip").toString("");
|
||||
if (!singleIp.isEmpty()) {
|
||||
ips.append(singleIp);
|
||||
}
|
||||
ips.removeAll(QString());
|
||||
ips.removeDuplicates();
|
||||
QString ip = jsonObject.value("ip").toString("");
|
||||
|
||||
QString normalizedHostname = normalizeHostname(hostname);
|
||||
|
||||
@@ -258,7 +219,7 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
continue;
|
||||
}
|
||||
|
||||
sites.insert(normalizedHostname, ips);
|
||||
sites.insert(normalizedHostname, ip);
|
||||
}
|
||||
|
||||
addSites(sites, replaceExisting);
|
||||
@@ -268,21 +229,13 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
|
||||
QByteArray IpSplitTunnelingController::exportSitesToJson() const
|
||||
{
|
||||
QVector<QPair<QString, QStringList>> sites = getCurrentSites();
|
||||
QVector<QPair<QString, QString>> sites = getCurrentSites();
|
||||
QJsonArray jsonArray;
|
||||
|
||||
for (const auto &site : sites) {
|
||||
QJsonObject jsonObject;
|
||||
jsonObject["hostname"] = site.first;
|
||||
|
||||
QJsonArray ipsArray;
|
||||
for (const QString &ip : site.second) {
|
||||
ipsArray.append(ip);
|
||||
}
|
||||
jsonObject["ips"] = ipsArray;
|
||||
// Keep the legacy "ip" field (first address) for backward compatibility.
|
||||
jsonObject["ip"] = site.second.isEmpty() ? QString() : site.second.first();
|
||||
|
||||
jsonObject["ip"] = site.second;
|
||||
jsonArray.append(jsonObject);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr);
|
||||
|
||||
bool addSite(const QString &hostname);
|
||||
void addSites(const QMap<QString, QStringList> &sites, bool replaceExisting);
|
||||
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
|
||||
bool removeSite(const QString &hostname);
|
||||
void removeSites();
|
||||
void setRouteMode(RouteMode routeMode);
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
|
||||
RouteMode getRouteMode() const;
|
||||
bool isSplitTunnelingEnabled() const;
|
||||
QVector<QPair<QString, QStringList>> getCurrentSites() const;
|
||||
QVector<QPair<QString, QString>> getCurrentSites() const;
|
||||
|
||||
bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage);
|
||||
QByteArray exportSitesToJson() const;
|
||||
@@ -43,15 +43,15 @@ private slots:
|
||||
|
||||
private:
|
||||
void fillSites();
|
||||
bool addSiteInternal(const QString &hostname, const QStringList &ips);
|
||||
bool addSiteInternal(const QString &hostname, const QString &ip);
|
||||
QString normalizeHostname(const QString &hostname) const;
|
||||
bool validateHostname(const QString &hostname) const;
|
||||
void processSiteAfterResolve(const QString &hostname, const QStringList &ips);
|
||||
void processSite(const QString &hostname, const QStringList &ips);
|
||||
void processSiteAfterResolve(const QString &hostname, const QString &ip);
|
||||
void processSite(const QString &hostname, const QString &ip);
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
RouteMode m_currentRouteMode;
|
||||
QVector<QPair<QString, QStringList>> m_sites;
|
||||
QVector<QPair<QString, QString>> m_sites;
|
||||
};
|
||||
|
||||
#endif // IPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
#include "version.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
@@ -221,10 +220,7 @@ ExportController::ExportResult ExportController::generateWireGuardConfig(const Q
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
const QString qr = qrCodeUtils::generatePlainQrCodeImage(result.config.toUtf8());
|
||||
if (!qr.isEmpty()) {
|
||||
result.qrCodes << qr;
|
||||
}
|
||||
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -255,10 +251,7 @@ ExportController::ExportResult ExportController::generateAwgConfig(const QString
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
const QString qr = qrCodeUtils::generatePlainQrCodeImage(result.config.toUtf8());
|
||||
if (!qr.isEmpty()) {
|
||||
result.qrCodes << qr;
|
||||
}
|
||||
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -330,21 +323,9 @@ ExportController::ExportResult ExportController::generateXrayConfig(const QStrin
|
||||
vlessServer.shortId = realitySettings.value(amnezia::protocols::xray::shortId).toString();
|
||||
vlessServer.fingerprint = realitySettings.value(amnezia::protocols::xray::fingerprint).toString("chrome");
|
||||
vlessServer.spiderX = realitySettings.value(amnezia::protocols::xray::spiderX).toString("");
|
||||
} else if (vlessServer.security == "tls") {
|
||||
QJsonObject tlsSettings = streamSettings.value("tlsSettings").toObject();
|
||||
vlessServer.serverName = tlsSettings.value(amnezia::protocols::xray::serverName).toString();
|
||||
vlessServer.fingerprint = tlsSettings.value(amnezia::protocols::xray::fingerprint).toString();
|
||||
// alpn: serialize array back to comma-separated for VLESS URI
|
||||
QJsonArray alpnArr = tlsSettings.value("alpn").toArray();
|
||||
QStringList alpnList;
|
||||
for (const QJsonValue &v : alpnArr) {
|
||||
alpnList << v.toString();
|
||||
}
|
||||
// alpn goes into vless URI query param — handled by Serialize via serverName/alpn fields
|
||||
// VlessServerObject doesn't have alpn field, so we embed in serverName if needed
|
||||
}
|
||||
|
||||
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, APPLICATION_NAME);
|
||||
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -376,3 +357,9 @@ QList<QString> ExportController::generateQrCodesFromConfig(const QByteArray &dat
|
||||
{
|
||||
return qrCodeUtils::generateQrCodeImageSeries(data);
|
||||
}
|
||||
|
||||
QString ExportController::generateSingleQrCode(const QByteArray &data)
|
||||
{
|
||||
auto qr = qrCodeUtils::generateQrCode(data);
|
||||
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ signals:
|
||||
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(const QString &serverId, DockerContainer container);
|
||||
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
|
||||
void revokeFinished(ErrorCode errorCode);
|
||||
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
@@ -69,6 +68,7 @@ private:
|
||||
|
||||
QString generateVpnUrl(const QByteArray &compressedConfig);
|
||||
QList<QString> generateQrCodesFromConfig(const QByteArray &data);
|
||||
QString generateSingleQrCode(const QByteArray &data);
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
@@ -486,7 +486,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
@@ -511,10 +511,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
const qsizetype separatorIndex = trimmedLine.indexOf('=');
|
||||
if (separatorIndex > 0) {
|
||||
configMap[trimmedLine.left(separatorIndex).trimmed()] =
|
||||
trimmedLine.mid(separatorIndex + 1).trimmed();
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,26 +566,53 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
|
||||
const QStringList allowedIps = configMap.value(protocols::wireguard::AllowedIPs).split(
|
||||
QRegularExpression("\\s*,\\s*"), Qt::SkipEmptyParts);
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIps);
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
|
||||
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
|
||||
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
||||
|
||||
QString protocolName = configKey::wireguard;
|
||||
QString protocolVersion;
|
||||
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
||||
|
||||
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
|
||||
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
|
||||
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
|
||||
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
|
||||
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
|
||||
configKey::transportPacketMagicHeader };
|
||||
|
||||
bool hasAwgKeys = std::any_of(awgProtocolKeys.begin(), awgProtocolKeys.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
if (hasAwgKeys) {
|
||||
for (const QString &key : awgProtocolKeys) {
|
||||
if (!configMap.value(key).isEmpty()) {
|
||||
lastConfig[key] = configMap.value(key);
|
||||
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
|
||||
configKey::transportPacketJunkSize,
|
||||
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
|
||||
configKey::specialJunk4, configKey::specialJunk5
|
||||
};
|
||||
|
||||
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
if (hasAllRequiredFields) {
|
||||
for (const QString &field : requiredJunkFields) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
|
||||
for (const QString &field : optionalJunkFields) {
|
||||
if (!configMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk5).isEmpty();
|
||||
|
||||
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
||||
protocolVersion = "2";
|
||||
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
|
||||
protocolVersion = "1.5";
|
||||
}
|
||||
protocolName = configKey::awg;
|
||||
detectedType = ConfigTypes::Awg;
|
||||
}
|
||||
@@ -604,6 +630,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
wireguardConfig[configKey::isThirdPartyConfig] = true;
|
||||
wireguardConfig[configKey::port] = port;
|
||||
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
|
||||
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
|
||||
wireguardConfig[configKey::protocolVersion] = protocolVersion;
|
||||
}
|
||||
|
||||
QJsonObject containers;
|
||||
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;
|
||||
@@ -616,7 +645,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
@@ -637,24 +666,11 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
{
|
||||
QJsonParseError parserErr;
|
||||
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
||||
if (parserErr.error != QJsonParseError::NoError || !jsonConf.isObject()) {
|
||||
qDebug() << "Xray config JSON parse failed:" << parserErr.errorString();
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
const QJsonObject parsedConfig = jsonConf.object();
|
||||
if (!parsedConfig.value(protocols::xray::inbounds).isArray()
|
||||
|| !parsedConfig.value(protocols::xray::outbounds).isArray()) {
|
||||
qDebug() << "Xray config is missing inbounds or outbounds";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
const QString serializedConfig = QString::fromUtf8(jsonConf.toJson());
|
||||
|
||||
QJsonObject xrayVpnConfig;
|
||||
xrayVpnConfig[configKey::config] = serializedConfig;
|
||||
xrayVpnConfig[configKey::config] = jsonConf.toJson().constData();
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::lastConfig] = serializedConfig;
|
||||
lastConfig[configKey::lastConfig] = jsonConf.toJson().constData();
|
||||
lastConfig[configKey::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject containers;
|
||||
@@ -683,7 +699,7 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/installers/mtProxyInstaller.h"
|
||||
|
||||
class SshSession;
|
||||
class InstallerBase;
|
||||
@@ -34,27 +33,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
ErrorCode removeContainer(const QString &serverId, DockerContainer container);
|
||||
|
||||
ErrorCode setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled);
|
||||
|
||||
/// statusOut: 0 = not deployed, 1 = running, 2 = stopped, 3 = error
|
||||
ErrorCode queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut);
|
||||
|
||||
ErrorCode queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out);
|
||||
|
||||
QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container);
|
||||
|
||||
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
|
||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
|
||||
|
||||
@@ -69,8 +53,7 @@ public:
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback = nullptr);
|
||||
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
|
||||
|
||||
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
|
||||
|
||||
@@ -104,11 +87,6 @@ private:
|
||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession);
|
||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession);
|
||||
|
||||
ErrorCode readXrayStateBeforeVolumeMigration(const ServerCredentials &credentials, DockerContainer container,
|
||||
SshSession &sshSession, QMap<QString, QString> &outFiles);
|
||||
ErrorCode restoreXrayStateIntoDataVolume(const ServerCredentials &credentials, DockerContainer container,
|
||||
SshSession &sshSession, const QMap<QString, QString> &files);
|
||||
|
||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession);
|
||||
ErrorCode isUserInSudo(const ServerCredentials &credentials, SshSession &sshSession);
|
||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, SshSession &sshSession);
|
||||
@@ -131,7 +109,7 @@ private:
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
bool m_cancelInstallation = false;
|
||||
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
QList<QSharedPointer<QProcess>> m_sftpMountProcesses;
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
@@ -56,34 +55,6 @@ int UsersController::clientIndexById(const QString &clientId, const QJsonArray &
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString UsersController::readXrayVolumeUuid(const DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession) const
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
const QString uuid = QString::fromUtf8(sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString::fromLatin1(amnezia::protocols::xray::uuidPath), error));
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.warning() << "readXrayVolumeUuid: failed, error=" << static_cast<int>(error);
|
||||
return {};
|
||||
}
|
||||
return uuid.trimmed();
|
||||
}
|
||||
|
||||
int UsersController::stripXrayVolumeUuidFromTable(QJsonArray &clientsTable, const QString &volumeUuid)
|
||||
{
|
||||
if (volumeUuid.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int removed = 0;
|
||||
for (int i = clientsTable.size() - 1; i >= 0; --i) {
|
||||
if (clientsTable.at(i).toObject().value(configKey::clientId).toString() == volumeUuid) {
|
||||
clientsTable.removeAt(i);
|
||||
++removed;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void UsersController::migration(const QByteArray &clientsTableString, QJsonArray &clientsTable)
|
||||
{
|
||||
QJsonObject clientsTableObj = QJsonDocument::fromJson(clientsTableString).object();
|
||||
@@ -293,7 +264,6 @@ ErrorCode UsersController::getXrayClients(const DockerContainer container, const
|
||||
}
|
||||
|
||||
const QJsonArray clients = settings[protocols::xray::clients].toArray();
|
||||
const QString xrayDefaultUuid = readXrayVolumeUuid(container, credentials, sshSession);
|
||||
for (const auto &clientValue : clients) {
|
||||
const QJsonObject clientObj = clientValue.toObject();
|
||||
if (!clientObj.contains(protocols::xray::id)) {
|
||||
@@ -301,6 +271,9 @@ ErrorCode UsersController::getXrayClients(const DockerContainer container, const
|
||||
continue;
|
||||
}
|
||||
QString clientId = clientObj[protocols::xray::id].toString();
|
||||
|
||||
QString xrayDefaultUuid = sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error);
|
||||
xrayDefaultUuid.replace("\n", "");
|
||||
|
||||
if (!isClientExists(clientId, clientsTable) && clientId != xrayDefaultUuid) {
|
||||
QJsonObject client;
|
||||
@@ -324,14 +297,10 @@ ErrorCode UsersController::updateClients(const QString &serverId, const DockerCo
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
logger.error() << "updateClients: no admin config for this server, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
logger.error() << "updateClients: credentials are not valid, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
@@ -344,8 +313,7 @@ ErrorCode UsersController::updateClients(const QString &serverId, const DockerCo
|
||||
|
||||
const QByteArray clientsTableString = sshSession.getTextFileFromContainer(container, credentials, clientsTableFile, error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "updateClients: failed to read the clientsTable file, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container);
|
||||
logger.error() << "Failed to get the clientsTable file from the server";
|
||||
emit clientsUpdated(QJsonArray());
|
||||
return error;
|
||||
}
|
||||
@@ -353,10 +321,6 @@ ErrorCode UsersController::updateClients(const QString &serverId, const DockerCo
|
||||
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
|
||||
|
||||
if (m_clientsTable.isEmpty()) {
|
||||
logger.info() << "updateClients: the clientsTable is empty, rebuilding it from the server, readBytes="
|
||||
<< clientsTableString.size()
|
||||
<< (clientsTableString.trimmed().isEmpty() ? ", the file was empty"
|
||||
: ", the file had content but did not parse as an array");
|
||||
migration(clientsTableString, m_clientsTable);
|
||||
|
||||
int count = 0;
|
||||
@@ -369,8 +333,6 @@ ErrorCode UsersController::updateClients(const QString &serverId, const DockerCo
|
||||
error = getXrayClients(container, credentials, &sshSession, count, m_clientsTable);
|
||||
}
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "updateClients: failed to rebuild the client list from the server, error="
|
||||
<< static_cast<int>(error) << "container=" << ContainerUtils::containerTypeToString(container);
|
||||
emit clientsUpdated(QJsonArray());
|
||||
return error;
|
||||
}
|
||||
@@ -379,27 +341,7 @@ ErrorCode UsersController::updateClients(const QString &serverId, const DockerCo
|
||||
if (clientsTableString != newClientsTableString) {
|
||||
error = sshSession.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "updateClients: failed to write back the rebuilt clientsTable, error="
|
||||
<< static_cast<int>(error) << "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << m_clientsTable.size();
|
||||
logger.warning() << "updateClients: the app now holds a client list the server does not have;"
|
||||
<< "this error is returned but every caller discards it";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Xray) {
|
||||
const QString volumeUuid = readXrayVolumeUuid(container, credentials, &sshSession);
|
||||
const int stripped = stripXrayVolumeUuidFromTable(m_clientsTable, volumeUuid);
|
||||
if (stripped > 0) {
|
||||
logger.info() << "updateClients: removed the volume uuid from the user list, rows="
|
||||
<< stripped;
|
||||
const QByteArray strippedTable = QJsonDocument(m_clientsTable).toJson();
|
||||
const ErrorCode writeError =
|
||||
sshSession.uploadTextFileToContainer(container, credentials, strippedTable, clientsTableFile);
|
||||
if (writeError != ErrorCode::NoError) {
|
||||
logger.error() << "updateClients: failed to write the user list without the volume uuid, error="
|
||||
<< static_cast<int>(writeError);
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,32 +393,18 @@ ErrorCode UsersController::appendClient(const QString &serverId, const QString &
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
logger.error() << "appendClient: no admin config for this server, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
logger.error() << "appendClient: credentials are not valid, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
error = updateClients(serverId, container);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "appendClient: could not read the current client list, error=" << static_cast<int>(error)
|
||||
<< ", the new account will not appear in the user list";
|
||||
return error;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Xray) {
|
||||
const QString volumeUuid = readXrayVolumeUuid(container, credentials, &sshSession);
|
||||
if (!volumeUuid.isEmpty() && clientId == volumeUuid) {
|
||||
logger.info() << "appendClient: skipped the volume uuid, it is not listed in Share Users";
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
}
|
||||
|
||||
int existingIndex = clientIndexById(clientId, m_clientsTable);
|
||||
if (existingIndex >= 0) {
|
||||
return renameClient(serverId, existingIndex, clientName, container, true);
|
||||
@@ -502,11 +430,7 @@ ErrorCode UsersController::appendClient(const QString &serverId, const QString &
|
||||
|
||||
error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "appendClient: failed to upload the clientsTable, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << m_clientsTable.size();
|
||||
logger.warning() << "appendClient: the account exists on the server but is missing from the user list;"
|
||||
<< "the caller discards this error, so nothing is reported to the user";
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -553,9 +477,7 @@ ErrorCode UsersController::renameClient(const QString &serverId, const int row,
|
||||
|
||||
ErrorCode error = sshSession.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "renameClient: failed to upload the clientsTable, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << m_clientsTable.size();
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -600,9 +522,7 @@ ErrorCode UsersController::revokeOpenVpn(const int row, const DockerContainer co
|
||||
clientsTableFile = clientsTableFile.arg(ContainerUtils::containerTypeToString(DockerContainer::OpenVpn));
|
||||
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "revokeOpenVpn: failed to upload the clientsTable, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << clientsTable.size();
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -662,9 +582,7 @@ ErrorCode UsersController::revokeWireGuard(const int row, const DockerContainer
|
||||
}
|
||||
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "revokeWireGuard: failed to upload the clientsTable, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << clientsTable.size();
|
||||
logger.error() << "Failed to upload the clientsTable file to the server";
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -697,24 +615,6 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
auto client = clientsTable.at(row).toObject();
|
||||
QString clientId = client.value(configKey::clientId).toString();
|
||||
|
||||
const QString volumeUuid = readXrayVolumeUuid(container, credentials, sshSession);
|
||||
if (!volumeUuid.isEmpty() && clientId == volumeUuid) {
|
||||
logger.info() << "revokeXray: refused, this is the volume uuid";
|
||||
clientsTable.removeAt(row);
|
||||
const QByteArray clientsTableString = QJsonDocument(clientsTable).toJson();
|
||||
const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable")
|
||||
.arg(ContainerUtils::containerTypeToString(container));
|
||||
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "revokeXray: refused the volume uuid but failed to drop it from the user list, error="
|
||||
<< static_cast<int>(error);
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
|
||||
const QString configString = sshSession->getTextFileFromContainer(container, credentials, serverConfigPath, error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -728,6 +628,9 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto client = clientsTable.at(row).toObject();
|
||||
QString clientId = client.value(configKey::clientId).toString();
|
||||
|
||||
QJsonObject configObj = serverConfig.object();
|
||||
if (!configObj.contains(protocols::xray::inbounds)) {
|
||||
logger.error() << "Missing inbounds in xray config";
|
||||
@@ -766,10 +669,6 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
}
|
||||
}
|
||||
|
||||
if (clients.isEmpty()) {
|
||||
logger.warning() << "revokeXray: inbound left with zero clients";
|
||||
}
|
||||
|
||||
settings[protocols::xray::clients] = clients;
|
||||
inbound[protocols::xray::settings] = settings;
|
||||
inbounds[0] = inbound;
|
||||
@@ -794,16 +693,12 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
|
||||
error = sshSession->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "revokeXray: failed to upload the clientsTable, error=" << static_cast<int>(error)
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container)
|
||||
<< "rows=" << clientsTable.size();
|
||||
logger.warning() << "revokeXray: the account is gone from the server config but still listed in the user table;"
|
||||
<< "this error is then overwritten by the container restart below and never reaches the caller";
|
||||
logger.error() << "Failed to upload the clientsTable file";
|
||||
}
|
||||
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
error = sshSession->runScript(
|
||||
credentials,
|
||||
credentials,
|
||||
sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString()))
|
||||
);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -817,22 +712,16 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
ErrorCode UsersController::revokeClient(const QString &serverId, const int index, const DockerContainer container)
|
||||
{
|
||||
if (index < 0 || index >= m_clientsTable.size()) {
|
||||
logger.error() << "revokeClient: row" << index << "is outside the client table of" << m_clientsTable.size()
|
||||
<< "rows, container=" << ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
logger.error() << "revokeClient: no admin config for this server, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
logger.error() << "revokeClient: credentials are not valid, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
@@ -869,17 +758,14 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const int index
|
||||
ContainerConfig containerCfg = adminConfig->containerConfig(container);
|
||||
QString containerClientId = containerCfg.protocolConfig.clientId();
|
||||
|
||||
const bool isAdminMatch = !clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId);
|
||||
if (isAdminMatch) {
|
||||
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
}
|
||||
|
||||
emit clientRevoked(index);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
emit revokeFinished(errorCode);
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -888,22 +774,16 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const Container
|
||||
SshSession sshSession;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
logger.error() << "revokeClient by config: no admin config for this server, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
logger.error() << "revokeClient by config: credentials are not valid, container="
|
||||
<< ContainerUtils::containerTypeToString(container);
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
errorCode = updateClients(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "revokeClient by config: could not read the current client list, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -929,9 +809,6 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const Container
|
||||
|
||||
int row = clientIndexById(clientId, m_clientsTable);
|
||||
if (row < 0) {
|
||||
logger.warning() << "revokeClient by config: this account is not in the client table of"
|
||||
<< m_clientsTable.size() << "rows, nothing was revoked and success is reported,"
|
||||
<< "container=" << ContainerUtils::containerTypeToString(container);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ signals:
|
||||
void clientAdded(const QJsonObject &client);
|
||||
void clientRenamed(int row, const QString &newName);
|
||||
void clientRevoked(int row);
|
||||
void revokeFinished(ErrorCode errorCode);
|
||||
void adminConfigRevoked(const QString &serverId, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
@@ -66,10 +65,6 @@ private:
|
||||
ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
|
||||
SshSession* sshSession, int &count, QJsonArray &clientsTable);
|
||||
|
||||
QString readXrayVolumeUuid(const DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession) const;
|
||||
int stripXrayVolumeUuidFromTable(QJsonArray &clientsTable, const QString &volumeUuid);
|
||||
|
||||
ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, std::vector<WgShowData> &data);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "core/models/serverDescription.h"
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include "core/utils/swiftBridge.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -52,7 +51,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -60,7 +58,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -70,7 +67,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->name = name;
|
||||
cfg->displayName = name;
|
||||
cfg->nameOverriddenByUser = true;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
|
||||
@@ -217,11 +217,6 @@ void SettingsController::toggleAutoStart(bool enable)
|
||||
|
||||
bool SettingsController::isStartMinimizedEnabled() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (!isAutoStartEnabled()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return m_appSettingsRepository->isStartMinimized();
|
||||
}
|
||||
|
||||
@@ -351,8 +346,18 @@ void SettingsController::setAppLanguage(const QLocale &locale)
|
||||
m_appSettingsRepository->setAppLanguage(locale);
|
||||
}
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
bool SettingsController::isPremV1MigrationReminderActive() const
|
||||
{
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
return m_appSettingsRepository->isPremV1MigrationReminderActive();
|
||||
}
|
||||
|
||||
void SettingsController::disablePremV1MigrationReminder()
|
||||
{
|
||||
m_appSettingsRepository->disablePremV1MigrationReminder();
|
||||
}
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
{
|
||||
return m_appSettingsRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ public:
|
||||
|
||||
void enableDevMode();
|
||||
|
||||
bool isPremV1MigrationReminderActive() const;
|
||||
void disablePremV1MigrationReminder();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
bool isDevModeEnabled() const;
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/gatewayPayloadBuilder.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
@@ -20,13 +21,13 @@ namespace
|
||||
Logger logger("UpdateController");
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_windows_x64.exe");
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-win64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos_x64.pkg");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Darwin.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Linux.run");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
||||
#endif
|
||||
}
|
||||
@@ -95,20 +96,20 @@ void UpdateController::fetchGatewayUrl()
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
|
||||
.build();
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::installationUuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
|
||||
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
|
||||
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
|
||||
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << "Gateway request failed, error code:" << static_cast<int>(err);
|
||||
logger.error() << errorString(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +184,7 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
@@ -195,13 +196,21 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
@@ -211,7 +220,7 @@ QString UpdateController::composeDownloadUrl() const
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
@@ -243,15 +252,23 @@ void UpdateController::runInstaller()
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
logger.error() << "Installer download failed, network error:" << static_cast<int>(reply->error())
|
||||
<< reply->errorString();
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
@@ -275,7 +292,7 @@ int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
#if defined(Q_OS_MACOS)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef CONTAINERDIAGNOSTICS_H
|
||||
#define CONTAINERDIAGNOSTICS_H
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
struct ContainerDiagnostics
|
||||
{
|
||||
bool available = false;
|
||||
bool portReachable = false;
|
||||
|
||||
virtual ~ContainerDiagnostics() = default;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // CONTAINERDIAGNOSTICS_H
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef MTPROXYDIAGNOSTICS_H
|
||||
#define MTPROXYDIAGNOSTICS_H
|
||||
|
||||
#include "containerDiagnostics.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace amnezia {
|
||||
struct MtProxyDiagnostics : ContainerDiagnostics {
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // MTPROXYDIAGNOSTICS_H
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef TELEMTDIAGNOSTICS_H
|
||||
#define TELEMTDIAGNOSTICS_H
|
||||
|
||||
#include "containerDiagnostics.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
struct TelemtDiagnostics : ContainerDiagnostics
|
||||
{
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // TELEMTDIAGNOSTICS_H
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "awgInstaller.h"
|
||||
|
||||
#include <QPair>
|
||||
#include <QRandomGenerator>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#include "core/configurators/wireguardConfigurator.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
@@ -27,59 +28,109 @@ AwgInstaller::AwgInstaller(QObject *parent)
|
||||
ContainerConfig AwgInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto)
|
||||
{
|
||||
ContainerConfig config = createBaseConfig(container, port, transportProto);
|
||||
|
||||
bool isAwg2 = (container == DockerContainer::Awg2);
|
||||
|
||||
if (auto* awgConfig = config.getAwgProtocolConfig()) {
|
||||
generateAwgParameters(awgConfig->serverConfig);
|
||||
awgConfig->serverConfig.protocolVersion = protocols::awg::awgV3;
|
||||
generateAwgParameters(awgConfig->serverConfig, isAwg2);
|
||||
|
||||
if (isAwg2) {
|
||||
awgConfig->serverConfig.protocolVersion = "2";
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig)
|
||||
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig, bool isAwg2)
|
||||
{
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
|
||||
int s1 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::initPacketJunkSizeMax);
|
||||
int s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
|
||||
int s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
|
||||
int s4 = protocols::awg::defaultTransportPacketJunkSize;
|
||||
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
||||
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||
int s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||
int s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||
|
||||
// Ensure all values are unique and don't create equal packet sizes
|
||||
QSet<int> usedValues { s1, s4 };
|
||||
QSet<int> usedValues;
|
||||
usedValues.insert(s1);
|
||||
|
||||
while (usedValues.contains(s2) || s1 + amnezia::AwgConstant::messageInitiationSize == s2 + amnezia::AwgConstant::messageResponseSize) {
|
||||
s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
|
||||
s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||
}
|
||||
usedValues.insert(s2);
|
||||
|
||||
while (usedValues.contains(s3) || s1 + amnezia::AwgConstant::messageInitiationSize == s3 + amnezia::AwgConstant::messageCookieReplySize
|
||||
|| s2 + amnezia::AwgConstant::messageResponseSize == s3 + amnezia::AwgConstant::messageCookieReplySize) {
|
||||
s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
|
||||
s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||
}
|
||||
usedValues.insert(s3);
|
||||
|
||||
while (usedValues.contains(s4)) {
|
||||
s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||
}
|
||||
|
||||
QString initPacketJunkSize = QString::number(s1);
|
||||
QString responsePacketJunkSize = QString::number(s2);
|
||||
QString cookieReplyPacketJunkSize = QString::number(s3);
|
||||
QString transportPacketJunkSize = QString::number(s4);
|
||||
|
||||
QString initPacketMagicHeader;
|
||||
QString responsePacketMagicHeader;
|
||||
QString underloadPacketMagicHeader;
|
||||
QString transportPacketMagicHeader;
|
||||
|
||||
if (isAwg2) {
|
||||
// AWG 2.0: use range format for magic headers
|
||||
QVector<QPair<QString, QString>> headersValue;
|
||||
int min = 5;
|
||||
auto max = (std::numeric_limits<qint32>::max)();
|
||||
while (headersValue.size() != 4) {
|
||||
auto first = QRandomGenerator::global()->bounded(min, max);
|
||||
auto second = QRandomGenerator::global()->bounded(first, max);
|
||||
min = second;
|
||||
headersValue.push_back(QPair<QString, QString>(QString::number(first), QString::number(second)));
|
||||
}
|
||||
|
||||
initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second;
|
||||
responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second;
|
||||
underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second;
|
||||
transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second;
|
||||
} else {
|
||||
// AWG legacy: use single values for magic headers
|
||||
QSet<QString> headersValue;
|
||||
while (headersValue.size() != 4) {
|
||||
auto max = (std::numeric_limits<qint32>::max)();
|
||||
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max)));
|
||||
}
|
||||
|
||||
auto headersValueList = headersValue.values();
|
||||
initPacketMagicHeader = headersValueList.at(0);
|
||||
responsePacketMagicHeader = headersValueList.at(1);
|
||||
underloadPacketMagicHeader = headersValueList.at(2);
|
||||
transportPacketMagicHeader = headersValueList.at(3);
|
||||
}
|
||||
|
||||
serverConfig.junkPacketCount = junkPacketCount;
|
||||
serverConfig.junkPacketMinSize = junkPacketMinSize;
|
||||
serverConfig.junkPacketMaxSize = junkPacketMaxSize;
|
||||
serverConfig.initPacketJunkSize = QString::number(s1);
|
||||
serverConfig.responsePacketJunkSize = QString::number(s2);
|
||||
serverConfig.cookieReplyPacketJunkSize = QString::number(s3);
|
||||
serverConfig.transportPacketJunkSize = QString::number(s4);
|
||||
serverConfig.initPacketJunkSize = initPacketJunkSize;
|
||||
serverConfig.responsePacketJunkSize = responsePacketJunkSize;
|
||||
serverConfig.initPacketMagicHeader = initPacketMagicHeader;
|
||||
serverConfig.responsePacketMagicHeader = responsePacketMagicHeader;
|
||||
serverConfig.underloadPacketMagicHeader = underloadPacketMagicHeader;
|
||||
serverConfig.transportPacketMagicHeader = transportPacketMagicHeader;
|
||||
|
||||
serverConfig.initPacketMagicHeader = protocols::awg::defaultInitPacketMagicHeader;
|
||||
serverConfig.responsePacketMagicHeader = protocols::awg::defaultResponsePacketMagicHeader;
|
||||
serverConfig.underloadPacketMagicHeader = protocols::awg::defaultUnderloadPacketMagicHeader;
|
||||
serverConfig.transportPacketMagicHeader = protocols::awg::defaultTransportPacketMagicHeader;
|
||||
serverConfig.cookieReplyPacketJunkSize = cookieReplyPacketJunkSize;
|
||||
serverConfig.transportPacketJunkSize = transportPacketJunkSize;
|
||||
|
||||
serverConfig.headerProtectionKey = WireguardConfigurator::genClientKeys().clientPrivKey;
|
||||
serverConfig.contentPaddingAddition = protocols::awg::defaultContentPaddingAddition;
|
||||
serverConfig.rekeyAfterTime = protocols::awg::defaultRekeyAfterTime;
|
||||
serverConfig.rekeyTimeout = protocols::awg::defaultRekeyTimeout;
|
||||
serverConfig.rejectAfterTime = protocols::awg::defaultRejectAfterTime;
|
||||
serverConfig.keepaliveTimeout = protocols::awg::defaultKeepaliveTimeout;
|
||||
serverConfig.maxHandshakeAttempts = protocols::awg::defaultMaxHandshakeAttempts;
|
||||
serverConfig.randomTrailers = protocols::awg::defaultRandomTrailers;
|
||||
serverConfig.disableCookies = protocols::awg::defaultDisableCookies;
|
||||
serverConfig.specialJunk1 = protocols::awg::defaultSpecialJunk1;
|
||||
serverConfig.specialJunk2 = protocols::awg::defaultSpecialJunk2;
|
||||
serverConfig.specialJunk3 = protocols::awg::defaultSpecialJunk3;
|
||||
serverConfig.specialJunk4 = protocols::awg::defaultSpecialJunk4;
|
||||
serverConfig.specialJunk5 = protocols::awg::defaultSpecialJunk5;
|
||||
}
|
||||
|
||||
ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
@@ -136,22 +187,14 @@ ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, co
|
||||
awgConfig->serverConfig.specialJunk4 = serverConfigMap.value(QString("# ") + configKey::specialJunk4);
|
||||
awgConfig->serverConfig.specialJunk5 = serverConfigMap.value(QString("# ") + configKey::specialJunk5);
|
||||
|
||||
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
|
||||
|
||||
awgConfig->serverConfig.headerProtectionKey = serverConfigMap.value(configKey::headerProtectionKey);
|
||||
awgConfig->serverConfig.contentPaddingAddition = serverConfigMap.value(configKey::contentPaddingAddition);
|
||||
awgConfig->serverConfig.rekeyAfterTime = serverConfigMap.value(configKey::rekeyAfterTime);
|
||||
awgConfig->serverConfig.rekeyTimeout = serverConfigMap.value(configKey::rekeyTimeout);
|
||||
awgConfig->serverConfig.rejectAfterTime = serverConfigMap.value(configKey::rejectAfterTime);
|
||||
awgConfig->serverConfig.keepaliveTimeout = serverConfigMap.value(configKey::keepaliveTimeout);
|
||||
awgConfig->serverConfig.maxHandshakeAttempts = serverConfigMap.value(configKey::maxHandshakeAttempts);
|
||||
awgConfig->serverConfig.randomTrailers = serverConfigMap.value(configKey::randomTrailers);
|
||||
awgConfig->serverConfig.disableCookies = serverConfigMap.value(configKey::disableCookies);
|
||||
|
||||
awgConfig->serverConfig.protocolVersion = awgConfig->serverProtocolVersion();
|
||||
// AWG 2.0 specific fields
|
||||
if (container == DockerContainer::Awg2) {
|
||||
awgConfig->serverConfig.protocolVersion = "2";
|
||||
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public:
|
||||
SshSession* serverController, amnezia::ContainerConfig &config) override;
|
||||
|
||||
private:
|
||||
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig);
|
||||
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig, bool isAwg2 = false);
|
||||
};
|
||||
|
||||
#endif // AWGINSTALLER_H
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "core/models/protocols/sftpProtocolConfig.h"
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.h"
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/torProtocolConfig.h"
|
||||
|
||||
@@ -56,7 +54,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
AwgProtocolConfig awgConfig;
|
||||
awgConfig.serverConfig.port = portStr;
|
||||
awgConfig.serverConfig.transportProto = transportProtoStr;
|
||||
awgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
|
||||
config.protocolConfig = awgConfig;
|
||||
break;
|
||||
}
|
||||
@@ -64,7 +61,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
WireGuardProtocolConfig wgConfig;
|
||||
wgConfig.serverConfig.port = portStr;
|
||||
wgConfig.serverConfig.transportProto = transportProtoStr;
|
||||
wgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
|
||||
config.protocolConfig = wgConfig;
|
||||
break;
|
||||
}
|
||||
@@ -78,18 +74,8 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
case Proto::Xray:
|
||||
case Proto::SSXray: {
|
||||
XrayProtocolConfig xrayConfig;
|
||||
XrayServerConfig &srv = xrayConfig.serverConfig;
|
||||
srv.port = portStr;
|
||||
srv.transportProto = transportProtoStr;
|
||||
srv.transport = protocols::xray::defaultTransport;
|
||||
srv.security = protocols::xray::defaultSecurity;
|
||||
srv.flow = protocols::xray::defaultFlow;
|
||||
srv.site = protocols::xray::defaultSite;
|
||||
srv.sni = protocols::xray::defaultSni;
|
||||
srv.alpn = protocols::xray::defaultAlpn;
|
||||
xrayConfig.clientTemplate.fingerprint = protocols::xray::defaultFingerprint;
|
||||
xrayConfig.clientTemplate.uplinkMethod = protocols::xray::defaultXhttpUplinkMethod;
|
||||
xrayConfig.clientTemplate.formatVersion = 1;
|
||||
xrayConfig.serverConfig.port = portStr;
|
||||
xrayConfig.serverConfig.transportProto = transportProtoStr;
|
||||
config.protocolConfig = xrayConfig;
|
||||
break;
|
||||
}
|
||||
@@ -105,18 +91,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
config.protocolConfig = socks5Config;
|
||||
break;
|
||||
}
|
||||
case Proto::MtProxy: {
|
||||
MtProxyProtocolConfig mtConfig;
|
||||
mtConfig.port = portStr;
|
||||
config.protocolConfig = mtConfig;
|
||||
break;
|
||||
}
|
||||
case Proto::Telemt: {
|
||||
TelemtProtocolConfig telemtConfig;
|
||||
telemtConfig.port = portStr;
|
||||
config.protocolConfig = telemtConfig;
|
||||
break;
|
||||
}
|
||||
case Proto::Ikev2: {
|
||||
Ikev2ProtocolConfig ikev2Config;
|
||||
config.protocolConfig = ikev2Config;
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
#include "mtProxyInstaller.h"
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxySecretPath("/data/secret");
|
||||
constexpr QLatin1String kMtProxyMetaPath("/data/mtproxy-meta");
|
||||
constexpr QLatin1String kMtProxyStartScriptPath("/opt/amnezia/start.sh");
|
||||
}
|
||||
|
||||
MtProxyInstaller::MtProxyInstaller(QObject *parent)
|
||||
: InstallerBase(parent) {
|
||||
}
|
||||
|
||||
ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession, ContainerConfig &config) {
|
||||
if (container != DockerContainer::MtProxy || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode jsonErr = ErrorCode::NoError;
|
||||
const QByteArray jsonRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyClientJsonPath), jsonErr);
|
||||
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
QJsonObject merged = mt->toJson();
|
||||
const QJsonObject snap = doc.object();
|
||||
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
|
||||
merged.insert(it.key(), it.value());
|
||||
}
|
||||
*mt = MtProxyProtocolConfig::fromJson(merged);
|
||||
}
|
||||
}
|
||||
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
const auto addExtra = [&](const QString &s) {
|
||||
if (hex32.match(s).hasMatch() && !mt->additionalSecrets.contains(s)) {
|
||||
mt->additionalSecrets.append(s);
|
||||
}
|
||||
};
|
||||
|
||||
ErrorCode secretErr = ErrorCode::NoError;
|
||||
const QByteArray secretRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
|
||||
const QString sec = QString::fromUtf8(secretRaw).trimmed();
|
||||
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
|
||||
mt->secret = sec;
|
||||
}
|
||||
|
||||
bool metaRestored = false;
|
||||
ErrorCode metaErr = ErrorCode::NoError;
|
||||
const QByteArray metaRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyMetaPath), metaErr);
|
||||
if (metaErr == ErrorCode::NoError && !metaRaw.trimmed().isEmpty()) {
|
||||
QString mode, domain, workersMode, workers, natInternal, natExternal;
|
||||
bool natEnabled = false;
|
||||
const QList<QByteArray> lines = metaRaw.split('\n');
|
||||
for (const QByteArray &rawLine : lines) {
|
||||
const QString line = QString::fromUtf8(rawLine).trimmed();
|
||||
const int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
const QString key = line.left(eq);
|
||||
const QString val = line.mid(eq + 1).trimmed();
|
||||
if (key == QLatin1String("mode")) mode = val;
|
||||
else if (key == QLatin1String("domain")) domain = val;
|
||||
else if (key == QLatin1String("tag")) { if (mt->tag.isEmpty()) mt->tag = val; }
|
||||
else if (key == QLatin1String("additional")) {
|
||||
for (const QString &s : val.split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
|
||||
}
|
||||
else if (key == QLatin1String("workers_mode")) workersMode = val;
|
||||
else if (key == QLatin1String("workers")) workers = val;
|
||||
else if (key == QLatin1String("nat_enabled")) natEnabled = (val == QLatin1String("1"));
|
||||
else if (key == QLatin1String("nat_internal")) natInternal = val;
|
||||
else if (key == QLatin1String("nat_external")) natExternal = val;
|
||||
else if (key == QLatin1String("public_host")) { if (mt->publicHost.isEmpty()) mt->publicHost = val; }
|
||||
}
|
||||
if (!mode.isEmpty()) {
|
||||
mt->transportMode = mode;
|
||||
if (!domain.isEmpty()) mt->tlsDomain = domain;
|
||||
if (!workersMode.isEmpty()) mt->workersMode = workersMode;
|
||||
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual) && !workers.isEmpty()) {
|
||||
mt->workers = workers;
|
||||
}
|
||||
if (natEnabled) {
|
||||
mt->natEnabled = true;
|
||||
mt->natInternalIp = natInternal;
|
||||
mt->natExternalIp = natExternal;
|
||||
}
|
||||
metaRestored = true;
|
||||
}
|
||||
}
|
||||
if (!metaRestored) {
|
||||
ErrorCode startErr = ErrorCode::NoError;
|
||||
const QByteArray startRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyStartScriptPath), startErr);
|
||||
if (startErr == ErrorCode::NoError && !startRaw.trimmed().isEmpty()) {
|
||||
const QString start = QString::fromUtf8(startRaw);
|
||||
static const QRegularExpression modeRe(QStringLiteral("\\[ \"(standard|faketls)\" = \"faketls\" \\]"));
|
||||
const QRegularExpressionMatch m = modeRe.match(start);
|
||||
if (m.hasMatch()) {
|
||||
mt->transportMode = m.captured(1);
|
||||
if (m.captured(1) == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) {
|
||||
static const QRegularExpression domRe(QStringLiteral("--domain ([A-Za-z0-9.\\-]+)"));
|
||||
const QRegularExpressionMatch dm = domRe.match(start);
|
||||
if (dm.hasMatch()) mt->tlsDomain = dm.captured(1);
|
||||
}
|
||||
}
|
||||
static const QRegularExpression tagRe(QStringLiteral("-P ([0-9a-fA-F]{32})"));
|
||||
const QRegularExpressionMatch tm = tagRe.match(start);
|
||||
if (tm.hasMatch() && mt->tag.isEmpty()) mt->tag = tm.captured(1);
|
||||
|
||||
static const QRegularExpression addRe(QStringLiteral("echo \"([0-9a-fA-F,]+)\" \\| tr ',' ' '"));
|
||||
const QRegularExpressionMatch am = addRe.match(start);
|
||||
if (am.hasMatch()) {
|
||||
for (const QString &s : am.captured(1).split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
|
||||
}
|
||||
|
||||
static const QRegularExpression natRe(QStringLiteral("NAT_VALUE=\"([0-9.]+):([0-9.]+)\""));
|
||||
const QRegularExpressionMatch nm = natRe.match(start);
|
||||
if (nm.hasMatch()) {
|
||||
mt->natEnabled = true;
|
||||
mt->natInternalIp = nm.captured(1);
|
||||
mt->natExternalIp = nm.captured(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out)
|
||||
{
|
||||
out = { };
|
||||
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
const bool isTelemt = container == DockerContainer::Telemt;
|
||||
|
||||
const QString sportFilter = QString::number(listenPort);
|
||||
const QString peersCmd = QStringLiteral("sudo conntrack -L -p tcp --dport ") + sportFilter
|
||||
+ QStringLiteral(" 2>/dev/null | grep ESTABLISHED | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){print "
|
||||
"substr($i,5); break}}'");
|
||||
const QString publicFilter = QStringLiteral(" | grep -vE "
|
||||
"'^(10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3["
|
||||
"01])\\.|::1$|fe80:|f[cd][0-9a-f][0-9a-f]:)'");
|
||||
const QString clientsCmd =
|
||||
QStringLiteral("CLIENTS=$(") + peersCmd + publicFilter + QStringLiteral(" | sort -u | grep -c .); ");
|
||||
const QString confFile =
|
||||
isTelemt ? QStringLiteral("/data/config.toml") : QStringLiteral("/data/proxy-multi.conf");
|
||||
const QString statsUrl = QString();
|
||||
|
||||
const QString script = QStringLiteral("CN=") + containerName + QStringLiteral("; ")
|
||||
+ QStringLiteral("PORT_OK=$(sudo ss -tlnp 2>/dev/null | grep -q :") + QString::number(listenPort)
|
||||
+ QStringLiteral(" && echo yes || echo no); ")
|
||||
+ QStringLiteral("TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "
|
||||
"https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || "
|
||||
"echo no); ")
|
||||
+ clientsCmd + QStringLiteral("CONF_TIME=$(sudo docker exec \"$CN\" sh -c 'stat -c \"%y\" ") + confFile
|
||||
+ QStringLiteral(" 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); ")
|
||||
+ QStringLiteral("echo \"PORT_OK=${PORT_OK}\"; ") + QStringLiteral("echo \"TG_OK=${TG_OK}\"; ")
|
||||
+ QStringLiteral("echo \"CLIENTS=${CLIENTS:-0}\"; ") + QStringLiteral("echo \"CONF_TIME=${CONF_TIME}\"; ")
|
||||
+ QStringLiteral("echo \"STATS=") + statsUrl + QStringLiteral("\";");
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith(QLatin1String("PORT_OK="))) {
|
||||
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("TG_OK="))) {
|
||||
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
|
||||
out.clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
|
||||
out.lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith(QLatin1String("STATS="))) {
|
||||
out.statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, const ContainerConfig &config) {
|
||||
const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
|
||||
if (!mt) {
|
||||
return;
|
||||
}
|
||||
const QByteArray payload = QJsonDocument(mt->toJson()).toJson(QJsonDocument::Compact);
|
||||
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
|
||||
QString(kMtProxyClientJsonUploadPath));
|
||||
if (err != ErrorCode::NoError) {
|
||||
qWarning() << "MtProxyInstaller::uploadClientSettingsSnapshot failed" << err;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef MTPROXYINSTALLER_H
|
||||
#define MTPROXYINSTALLER_H
|
||||
|
||||
#include "installerBase.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
struct MtProxyContainerDiagnostics {
|
||||
bool portReachable = false;
|
||||
bool upstreamReachable = false;
|
||||
int clientsConnected = -1;
|
||||
QString lastConfigRefresh;
|
||||
QString statsEndpoint;
|
||||
};
|
||||
|
||||
class MtProxyInstaller : public InstallerBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MtProxyInstaller(QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode
|
||||
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
|
||||
SshSession *sshSession, amnezia::ContainerConfig &config) override;
|
||||
|
||||
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &config);
|
||||
|
||||
static amnezia::ErrorCode queryDiagnostics(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out);
|
||||
};
|
||||
|
||||
#endif // MTPROXYINSTALLER_H
|
||||
@@ -1,17 +1,15 @@
|
||||
#include "socks5Installer.h"
|
||||
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
|
||||
@@ -35,29 +33,10 @@ ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int p
|
||||
ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, ContainerConfig &config)
|
||||
{
|
||||
if (container != DockerContainer::Socks5Proxy || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
Socks5ProxyProtocolConfig *socks5Config = config.getSocks5ProxyProtocolConfig();
|
||||
if (!socks5Config) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode readError = ErrorCode::NoError;
|
||||
const QByteArray configRaw = sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString::fromUtf8(protocols::socks5Proxy::proxyConfigPath), readError);
|
||||
if (readError != ErrorCode::NoError || configRaw.trimmed().isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString proxyConfig = QString::fromUtf8(configRaw);
|
||||
static const QRegularExpression usernameAndPasswordRegExp(QStringLiteral("users (\\w+):CL:(\\w+)"));
|
||||
const QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig);
|
||||
if (usernameAndPasswordMatch.hasMatch()) {
|
||||
socks5Config->userName = usernameAndPasswordMatch.captured(1);
|
||||
socks5Config->password = usernameAndPasswordMatch.captured(2);
|
||||
}
|
||||
|
||||
Q_UNUSED(container);
|
||||
Q_UNUSED(credentials);
|
||||
Q_UNUSED(sshSession);
|
||||
Q_UNUSED(config);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
#include "telemtInstaller.h"
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
constexpr QLatin1String kTelemtClientJsonPath("/data/amnezia-telemt-client.json");
|
||||
constexpr QLatin1String kTelemtClientJsonUploadPath("data/amnezia-telemt-client.json");
|
||||
constexpr QLatin1String kTelemtSecretPath("/data/secret");
|
||||
constexpr QLatin1String kTelemtConfigTomlPath("/data/config.toml");
|
||||
}
|
||||
|
||||
TelemtInstaller::TelemtInstaller(QObject *parent) : InstallerBase(parent) {}
|
||||
|
||||
ErrorCode TelemtInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession *sshSession, ContainerConfig &config) {
|
||||
if (container != DockerContainer::Telemt || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
|
||||
if (!tc) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode jsonErr = ErrorCode::NoError;
|
||||
const QByteArray jsonRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtClientJsonPath), jsonErr);
|
||||
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
QJsonObject merged = tc->toJson();
|
||||
const QJsonObject snap = doc.object();
|
||||
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
|
||||
merged.insert(it.key(), it.value());
|
||||
}
|
||||
*tc = TelemtProtocolConfig::fromJson(merged);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode secretErr = ErrorCode::NoError;
|
||||
const QByteArray secretRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtSecretPath), secretErr);
|
||||
const QString sec = QString::fromUtf8(secretRaw).trimmed();
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
|
||||
tc->secret = sec;
|
||||
}
|
||||
|
||||
ErrorCode tomlErr = ErrorCode::NoError;
|
||||
const QByteArray tomlRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtConfigTomlPath), tomlErr);
|
||||
if (tomlErr == ErrorCode::NoError && !tomlRaw.trimmed().isEmpty()) {
|
||||
QString section;
|
||||
bool userNameSet = false;
|
||||
const QList<QByteArray> lines = tomlRaw.split('\n');
|
||||
for (const QByteArray &rawLine : lines) {
|
||||
const QString line = QString::fromUtf8(rawLine).trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('[')) {
|
||||
section = line;
|
||||
continue;
|
||||
}
|
||||
const int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
const QString key = line.left(eq).trimmed();
|
||||
QString val = line.mid(eq + 1).trimmed();
|
||||
if (val.startsWith('"')) {
|
||||
const int last = val.lastIndexOf('"');
|
||||
val = (last > 0) ? val.mid(1, last - 1) : val.mid(1);
|
||||
} else {
|
||||
const int inlineComment = val.indexOf(QLatin1String(" #"));
|
||||
if (inlineComment >= 0) {
|
||||
val = val.left(inlineComment).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
if (section == QLatin1String("[access.users]")) {
|
||||
if (key.startsWith(QLatin1String("extra"))) {
|
||||
if (hex32.match(val).hasMatch() && !tc->additionalSecrets.contains(val)) {
|
||||
tc->additionalSecrets.append(val);
|
||||
}
|
||||
} else if (!userNameSet) {
|
||||
tc->userName = key;
|
||||
userNameSet = true;
|
||||
if (tc->secret.isEmpty() && hex32.match(val).hasMatch()) {
|
||||
tc->secret = val;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == QLatin1String("tls") && section == QLatin1String("[general.modes]")) {
|
||||
tc->transportMode = (val == QLatin1String("true"))
|
||||
? QString::fromUtf8(protocols::telemt::transportModeFakeTLS)
|
||||
: QString::fromUtf8(protocols::telemt::transportModeStandard);
|
||||
} else if (key == QLatin1String("tls_domain") && section == QLatin1String("[censorship]")) {
|
||||
tc->tlsDomain = val;
|
||||
} else if (key == QLatin1String("mask") && section == QLatin1String("[censorship]")) {
|
||||
tc->maskEnabled = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("tls_emulation") && section == QLatin1String("[censorship]")) {
|
||||
tc->tlsEmulation = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("use_middle_proxy") && section == QLatin1String("[general]")) {
|
||||
tc->useMiddleProxy = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("middle_proxy_nat_ip") && section == QLatin1String("[general]")) {
|
||||
if (!val.isEmpty()) {
|
||||
tc->natExternalIp = val;
|
||||
tc->natEnabled = true;
|
||||
}
|
||||
} else if (key == QLatin1String("ad_tag") && section == QLatin1String("[general]") && tc->tag.isEmpty()) {
|
||||
tc->tag = val;
|
||||
} else if (key == QLatin1String("public_host") && section == QLatin1String("[general.links]")
|
||||
&& tc->publicHost.isEmpty()) {
|
||||
tc->publicHost = val;
|
||||
} else if (key == QLatin1String("port") && section == QLatin1String("[server]") && tc->port.isEmpty()) {
|
||||
tc->port = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void TelemtInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
DockerContainer container, const ContainerConfig &config) {
|
||||
const TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
|
||||
if (!tc) {
|
||||
return;
|
||||
}
|
||||
const QByteArray payload = QJsonDocument(tc->toJson()).toJson(QJsonDocument::Compact);
|
||||
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
|
||||
QString(kTelemtClientJsonUploadPath));
|
||||
if (err != ErrorCode::NoError) {
|
||||
qWarning() << "TelemtInstaller::uploadClientSettingsSnapshot failed" << err;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef TELEMTINSTALLER_H
|
||||
#define TELEMTINSTALLER_H
|
||||
|
||||
#include "installerBase.h"
|
||||
|
||||
class TelemtInstaller : public InstallerBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TelemtInstaller(QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode
|
||||
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
|
||||
SshSession *sshSession, amnezia::ContainerConfig &config) override;
|
||||
|
||||
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &config);
|
||||
};
|
||||
|
||||
#endif // TELEMTINSTALLER_H
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "xrayInstaller.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
@@ -10,30 +11,11 @@
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/configurators/xrayConfigurator.h"
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
Logger logger("XrayInstaller");
|
||||
|
||||
QString describeServerJsonStatus(amnezia::XrayServerJsonStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case amnezia::XrayServerJsonStatus::MissingInbounds:
|
||||
return QStringLiteral("server config missing 'inbounds' field");
|
||||
case amnezia::XrayServerJsonStatus::EmptyInbounds:
|
||||
return QStringLiteral("server config has empty 'inbounds' array");
|
||||
case amnezia::XrayServerJsonStatus::MissingStreamSettings:
|
||||
return QStringLiteral("inbound missing 'streamSettings' field");
|
||||
case amnezia::XrayServerJsonStatus::MissingSettings:
|
||||
return QStringLiteral("inbound missing 'settings' field");
|
||||
case amnezia::XrayServerJsonStatus::Ok:
|
||||
break;
|
||||
}
|
||||
return QStringLiteral("ok");
|
||||
}
|
||||
}
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -47,12 +29,10 @@ XrayInstaller::XrayInstaller(QObject *parent)
|
||||
ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, ContainerConfig &config)
|
||||
{
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
|
||||
QString currentConfig = sshSession->getTextFileFromContainer(
|
||||
container, credentials, px::serverConfigPath, errorCode);
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
@@ -63,38 +43,38 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
auto *xrayConfig = config.getXrayProtocolConfig();
|
||||
if (!xrayConfig) {
|
||||
logger.error() << "No XrayProtocolConfig in ContainerConfig";
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
if (!serverConfig.contains(protocols::xray::inbounds)) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
XrayClientTemplate &tpl = xrayConfig->clientTemplate;
|
||||
const amnezia::XrayServerJsonStatus status =
|
||||
XrayServerConfig::fromServerInboundJson(doc.object(), xrayConfig->serverConfig, tpl);
|
||||
if (status != amnezia::XrayServerJsonStatus::Ok) {
|
||||
logger.error() << "Xray extractConfigFromContainer:" << describeServerJsonStatus(status);
|
||||
QJsonArray inbounds = serverConfig[protocols::xray::inbounds].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
logger.info() << "Xray extractConfigFromContainer: extracted server, port=" << xrayConfig->serverConfig.port
|
||||
<< "transport=" << xrayConfig->serverConfig.transport
|
||||
<< "security=" << xrayConfig->serverConfig.security << "site=" << xrayConfig->serverConfig.site
|
||||
<< "sni=" << xrayConfig->serverConfig.sni;
|
||||
|
||||
{
|
||||
XrayConfigurator configurator(sshSession);
|
||||
bool found = false;
|
||||
const XrayClientTemplate stored = configurator.readClientTemplate(credentials, container, found);
|
||||
if (found) {
|
||||
tpl = stored;
|
||||
logger.info() << "Xray extractConfigFromContainer: adopted the client template from the server,"
|
||||
<< "fingerprint=" << tpl.fingerprint << "uplinkMethod=" << tpl.uplinkMethod;
|
||||
} else {
|
||||
logger.info() << "Xray extractConfigFromContainer: no client template on the server to adopt";
|
||||
}
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains(protocols::xray::streamSettings)) {
|
||||
logger.error() << "Inbound missing 'streamSettings' field";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QJsonObject streamSettings = inbound[protocols::xray::streamSettings].toObject();
|
||||
QJsonObject realitySettings = streamSettings[protocols::xray::realitySettings].toObject();
|
||||
if (!realitySettings.contains(protocols::xray::serverNames)) {
|
||||
logger.error() << "Settings missing 'serverNames' field";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QString siteName = realitySettings[protocols::xray::serverNames][0].toString();
|
||||
|
||||
if (auto* xrayConfig = config.getXrayProtocolConfig()) {
|
||||
xrayConfig->serverConfig.site = siteName;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,10 @@ QJsonObject ApiConfig::toJson() const
|
||||
obj[apiDefs::key::availableCountries] = availableCountries;
|
||||
}
|
||||
|
||||
if (!supportedProtocols.isEmpty()) {
|
||||
obj[apiDefs::key::supportedProtocols] = supportedProtocols;
|
||||
}
|
||||
|
||||
QJsonObject serviceInfoObj = serviceInfo.toJson();
|
||||
if (!serviceInfoObj.isEmpty()) {
|
||||
obj[apiDefs::key::serviceInfo] = serviceInfoObj;
|
||||
@@ -194,6 +198,7 @@ ApiConfig ApiConfig::fromJson(const QJsonObject& json)
|
||||
config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0);
|
||||
|
||||
config.availableCountries = json.value(apiDefs::key::availableCountries).toArray();
|
||||
config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray();
|
||||
|
||||
QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject();
|
||||
if (!serviceInfoObj.isEmpty()) {
|
||||
|
||||
@@ -34,6 +34,7 @@ struct ApiConfig
|
||||
int maxDeviceCount;
|
||||
int issuedConfigs;
|
||||
QJsonArray availableCountries;
|
||||
QJsonArray supportedProtocols;
|
||||
|
||||
struct ServiceInfo {
|
||||
bool isAdVisible = false;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/models/api/apiConfig.h"
|
||||
#include "core/models/api/authData.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -68,20 +67,6 @@ ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) co
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
QPair<QString, QString> ApiV2ServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject ApiV2ServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -95,6 +80,9 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
|
||||
obj[configKey::configVersion] = configVersion;
|
||||
|
||||
@@ -121,11 +109,7 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!dns2.isEmpty()) {
|
||||
obj[configKey::dns2] = dns2;
|
||||
}
|
||||
|
||||
if (!sendPayload.isEmpty()) {
|
||||
obj[configKey::sendPayload] = sendPayload;
|
||||
}
|
||||
|
||||
|
||||
if (crc > 0) {
|
||||
obj[configKey::crc] = crc;
|
||||
}
|
||||
@@ -150,6 +134,7 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(2);
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
@@ -169,7 +154,6 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
config.sendPayload = json.value(configKey::sendPayload).toArray();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#ifndef APIV2SERVERCONFIG_H
|
||||
#define APIV2SERVERCONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
@@ -29,7 +27,6 @@ struct ApiV2ServerConfig {
|
||||
DockerContainer defaultContainer;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
QJsonArray sendPayload;
|
||||
|
||||
QString name;
|
||||
bool nameOverriddenByUser = false;
|
||||
@@ -46,9 +43,6 @@ struct ApiV2ServerConfig {
|
||||
bool isExternalPremium() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static ApiV2ServerConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,9 @@ LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
LegacyApiServerConfig config;
|
||||
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
@@ -113,26 +113,6 @@ const Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig()
|
||||
return protocolConfig.as<Socks5ProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig()
|
||||
{
|
||||
return protocolConfig.as<MtProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
const MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig() const
|
||||
{
|
||||
return protocolConfig.as<MtProxyProtocolConfig>();
|
||||
}
|
||||
|
||||
TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig()
|
||||
{
|
||||
return protocolConfig.as<TelemtProtocolConfig>();
|
||||
}
|
||||
|
||||
const TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig() const
|
||||
{
|
||||
return protocolConfig.as<TelemtProtocolConfig>();
|
||||
}
|
||||
|
||||
Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig()
|
||||
{
|
||||
return protocolConfig.as<Ikev2ProtocolConfig>();
|
||||
|
||||
@@ -57,12 +57,6 @@ struct ContainerConfig {
|
||||
Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig();
|
||||
const Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig() const;
|
||||
|
||||
MtProxyProtocolConfig* getMtProxyProtocolConfig();
|
||||
const MtProxyProtocolConfig* getMtProxyProtocolConfig() const;
|
||||
|
||||
TelemtProtocolConfig* getTelemtProtocolConfig();
|
||||
const TelemtProtocolConfig* getTelemtProtocolConfig() const;
|
||||
|
||||
Ikev2ProtocolConfig* getIkev2ProtocolConfig();
|
||||
const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const;
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/dnsProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -40,10 +38,6 @@ Proto ProtocolConfig::type() const
|
||||
return Proto::TorWebSite;
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return Proto::Dns;
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return Proto::MtProxy;
|
||||
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
|
||||
return Proto::Telemt;
|
||||
}
|
||||
return Proto::Unknown;
|
||||
}, data);
|
||||
@@ -71,10 +65,6 @@ QString ProtocolConfig::port() const
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return arg.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : arg.port;
|
||||
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
|
||||
return arg.port.isEmpty() ? QString(protocols::telemt::defaultPort) : arg.port;
|
||||
}
|
||||
return QString();
|
||||
}, data);
|
||||
@@ -98,10 +88,6 @@ QString ProtocolConfig::transportProto() const
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
|
||||
return QString();
|
||||
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
|
||||
return QStringLiteral("tcp");
|
||||
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
|
||||
return QStringLiteral("tcp");
|
||||
}
|
||||
return QString();
|
||||
}, data);
|
||||
@@ -313,10 +299,6 @@ ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type)
|
||||
return ProtocolConfig{TorProtocolConfig::fromJson(json)};
|
||||
case Proto::Dns:
|
||||
return ProtocolConfig{DnsProtocolConfig::fromJson(json)};
|
||||
case Proto::MtProxy:
|
||||
return ProtocolConfig{MtProxyProtocolConfig::fromJson(json)};
|
||||
case Proto::Telemt:
|
||||
return ProtocolConfig{TelemtProtocolConfig::fromJson(json)};
|
||||
default:
|
||||
return ProtocolConfig{AwgProtocolConfig{}};
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
#include "core/models/protocols/torProtocolConfig.h"
|
||||
#include "core/models/protocols/dnsProtocolConfig.h"
|
||||
#include "core/models/protocols/mtProxyProtocolConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -38,8 +36,6 @@ struct ProtocolConfig {
|
||||
XrayProtocolConfig,
|
||||
SftpProtocolConfig,
|
||||
Socks5ProxyProtocolConfig,
|
||||
MtProxyProtocolConfig,
|
||||
TelemtProtocolConfig,
|
||||
Ikev2ProtocolConfig,
|
||||
TorProtocolConfig,
|
||||
DnsProtocolConfig
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../../../core/utils/protocolEnum.h"
|
||||
#include "../../../core/protocols/protocolUtils.h"
|
||||
@@ -17,61 +13,6 @@ using namespace ProtocolUtils;
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
// A toggle set to "off" behaves exactly like a missing one, so it is not an AWG 3 marker
|
||||
bool isAwgToggleEnabled(const QString &value)
|
||||
{
|
||||
const QString trimmedValue = value.trimmed();
|
||||
return !trimmedValue.isEmpty()
|
||||
&& trimmedValue.compare(QLatin1String(protocols::awg::awgBoolOff), Qt::CaseInsensitive) != 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool hasAwg3Markers(const T &config)
|
||||
{
|
||||
auto hasValue = [](const QString &value) { return !value.trimmed().isEmpty(); };
|
||||
|
||||
const QStringList awg3Params = { config.headerProtectionKey, config.contentPaddingAddition,
|
||||
config.rekeyAfterTime, config.rekeyTimeout,
|
||||
config.rejectAfterTime, config.keepaliveTimeout,
|
||||
config.maxHandshakeAttempts };
|
||||
if (std::any_of(awg3Params.begin(), awg3Params.end(), hasValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isAwgToggleEnabled(config.randomTrailers) || isAwgToggleEnabled(config.disableCookies);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QString awgVersionOf(const T &config)
|
||||
{
|
||||
auto hasValue = [](const QString &value) { return !value.trimmed().isEmpty(); };
|
||||
|
||||
if (hasAwg3Markers(config)) {
|
||||
return protocols::awg::awgV3;
|
||||
}
|
||||
|
||||
const QStringList junkSizes = { config.cookieReplyPacketJunkSize, config.transportPacketJunkSize };
|
||||
const QStringList magicHeaders = { config.initPacketMagicHeader, config.responsePacketMagicHeader,
|
||||
config.underloadPacketMagicHeader, config.transportPacketMagicHeader };
|
||||
bool hasJunkSizes = std::any_of(junkSizes.begin(), junkSizes.end(), hasValue);
|
||||
bool hasHeaderRanges = std::any_of(magicHeaders.begin(), magicHeaders.end(),
|
||||
[](const QString &header) { return header.contains('-'); });
|
||||
if (hasJunkSizes || hasHeaderRanges) {
|
||||
return protocols::awg::awgV2;
|
||||
}
|
||||
|
||||
const QStringList specialJunk = { config.specialJunk1, config.specialJunk2, config.specialJunk3,
|
||||
config.specialJunk4, config.specialJunk5 };
|
||||
if (std::any_of(specialJunk.begin(), specialJunk.end(), hasValue)) {
|
||||
return protocols::awg::awgV1_5;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QJsonObject AwgServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -133,34 +74,6 @@ QJsonObject AwgServerConfig::toJson() const
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
|
||||
if (!headerProtectionKey.isEmpty()) {
|
||||
obj[configKey::headerProtectionKey] = headerProtectionKey;
|
||||
}
|
||||
if (!contentPaddingAddition.isEmpty()) {
|
||||
obj[configKey::contentPaddingAddition] = contentPaddingAddition;
|
||||
}
|
||||
if (!rekeyAfterTime.isEmpty()) {
|
||||
obj[configKey::rekeyAfterTime] = rekeyAfterTime;
|
||||
}
|
||||
if (!rekeyTimeout.isEmpty()) {
|
||||
obj[configKey::rekeyTimeout] = rekeyTimeout;
|
||||
}
|
||||
if (!rejectAfterTime.isEmpty()) {
|
||||
obj[configKey::rejectAfterTime] = rejectAfterTime;
|
||||
}
|
||||
if (!keepaliveTimeout.isEmpty()) {
|
||||
obj[configKey::keepaliveTimeout] = keepaliveTimeout;
|
||||
}
|
||||
if (!maxHandshakeAttempts.isEmpty()) {
|
||||
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
|
||||
}
|
||||
if (!randomTrailers.isEmpty()) {
|
||||
obj[configKey::randomTrailers] = randomTrailers;
|
||||
}
|
||||
if (!disableCookies.isEmpty()) {
|
||||
obj[configKey::disableCookies] = disableCookies;
|
||||
}
|
||||
|
||||
if (isThirdPartyConfig) {
|
||||
obj[configKey::isThirdPartyConfig] = isThirdPartyConfig;
|
||||
}
|
||||
@@ -196,17 +109,7 @@ AwgServerConfig AwgServerConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk3 = json.value(configKey::specialJunk3).toString();
|
||||
config.specialJunk4 = json.value(configKey::specialJunk4).toString();
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.headerProtectionKey = json.value(configKey::headerProtectionKey).toString();
|
||||
config.contentPaddingAddition = json.value(configKey::contentPaddingAddition).toString();
|
||||
config.rekeyAfterTime = json.value(configKey::rekeyAfterTime).toString();
|
||||
config.rekeyTimeout = json.value(configKey::rekeyTimeout).toString();
|
||||
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
|
||||
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
|
||||
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
|
||||
config.randomTrailers = json.value(configKey::randomTrailers).toString();
|
||||
config.disableCookies = json.value(configKey::disableCookies).toString();
|
||||
|
||||
|
||||
config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
||||
|
||||
return config;
|
||||
@@ -293,50 +196,16 @@ QJsonObject AwgClientConfig::toJson() const
|
||||
obj[configKey::transportPacketMagicHeader] = transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
if (!specialJunk1.isEmpty()) {
|
||||
obj[configKey::specialJunk1] = specialJunk1;
|
||||
obj[configKey::specialJunk1] = specialJunk1;
|
||||
obj[configKey::specialJunk2] = specialJunk2;
|
||||
obj[configKey::specialJunk3] = specialJunk3;
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
if (!specialJunk2.isEmpty()) {
|
||||
obj[configKey::specialJunk2] = specialJunk2;
|
||||
}
|
||||
if (!specialJunk3.isEmpty()) {
|
||||
obj[configKey::specialJunk3] = specialJunk3;
|
||||
}
|
||||
if (!specialJunk4.isEmpty()) {
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
}
|
||||
if (!specialJunk5.isEmpty()) {
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
}
|
||||
|
||||
if (!headerProtectionKey.isEmpty()) {
|
||||
obj[configKey::headerProtectionKey] = headerProtectionKey;
|
||||
}
|
||||
if (!contentPaddingAddition.isEmpty()) {
|
||||
obj[configKey::contentPaddingAddition] = contentPaddingAddition;
|
||||
}
|
||||
if (!rekeyAfterTime.isEmpty()) {
|
||||
obj[configKey::rekeyAfterTime] = rekeyAfterTime;
|
||||
}
|
||||
if (!rekeyTimeout.isEmpty()) {
|
||||
obj[configKey::rekeyTimeout] = rekeyTimeout;
|
||||
}
|
||||
if (!rejectAfterTime.isEmpty()) {
|
||||
obj[configKey::rejectAfterTime] = rejectAfterTime;
|
||||
}
|
||||
if (!keepaliveTimeout.isEmpty()) {
|
||||
obj[configKey::keepaliveTimeout] = keepaliveTimeout;
|
||||
}
|
||||
if (!maxHandshakeAttempts.isEmpty()) {
|
||||
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
|
||||
}
|
||||
if (!randomTrailers.isEmpty()) {
|
||||
obj[configKey::randomTrailers] = randomTrailers;
|
||||
}
|
||||
if (!disableCookies.isEmpty()) {
|
||||
obj[configKey::disableCookies] = disableCookies;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -379,28 +248,21 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk3 = json.value(configKey::specialJunk3).toString();
|
||||
config.specialJunk4 = json.value(configKey::specialJunk4).toString();
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.headerProtectionKey = json.value(configKey::headerProtectionKey).toString();
|
||||
config.contentPaddingAddition = json.value(configKey::contentPaddingAddition).toString();
|
||||
config.rekeyAfterTime = json.value(configKey::rekeyAfterTime).toString();
|
||||
config.rekeyTimeout = json.value(configKey::rekeyTimeout).toString();
|
||||
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
|
||||
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
|
||||
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
|
||||
config.randomTrailers = json.value(configKey::randomTrailers).toString();
|
||||
config.disableCookies = json.value(configKey::disableCookies).toString();
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject AwgProtocolConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj = serverConfig.toJson();
|
||||
|
||||
|
||||
if (clientConfig.has_value()) {
|
||||
QJsonObject clientJson = clientConfig->toJson();
|
||||
obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -421,24 +283,6 @@ AwgProtocolConfig AwgProtocolConfig::fromJson(const QJsonObject& json)
|
||||
return config;
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::serverProtocolVersion() const
|
||||
{
|
||||
return awgVersionOf(serverConfig);
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::clientProtocolVersion() const
|
||||
{
|
||||
return clientConfig.has_value() ? awgVersionOf(clientConfig.value()) : QString();
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::protocolVersionString(const QString &version)
|
||||
{
|
||||
if (version == protocols::awg::awgV3) return QObject::tr(" (version 3.1)");
|
||||
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
|
||||
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
|
||||
return "";
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::hasClientConfig() const
|
||||
{
|
||||
return clientConfig.has_value();
|
||||
@@ -466,37 +310,24 @@ bool AwgServerConfig::hasEqualServerSettings(const AwgServerConfig& other) const
|
||||
transportPacketMagicHeader != other.transportPacketMagicHeader ||
|
||||
specialJunk1 != other.specialJunk1 || specialJunk2 != other.specialJunk2 ||
|
||||
specialJunk3 != other.specialJunk3 || specialJunk4 != other.specialJunk4 ||
|
||||
specialJunk5 != other.specialJunk5 ||
|
||||
cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
|
||||
transportPacketJunkSize != other.transportPacketJunkSize ||
|
||||
headerProtectionKey != other.headerProtectionKey ||
|
||||
contentPaddingAddition != other.contentPaddingAddition ||
|
||||
rekeyAfterTime != other.rekeyAfterTime || rekeyTimeout != other.rekeyTimeout ||
|
||||
rejectAfterTime != other.rejectAfterTime || keepaliveTimeout != other.keepaliveTimeout ||
|
||||
maxHandshakeAttempts != other.maxHandshakeAttempts ||
|
||||
randomTrailers != other.randomTrailers || disableCookies != other.disableCookies) {
|
||||
specialJunk5 != other.specialJunk5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isV2 = protocolVersion == protocols::awg::awgV2;
|
||||
if (isV2) {
|
||||
if (cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
|
||||
transportPacketJunkSize != other.transportPacketJunkSize) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AwgServerConfig::hasAwg3Params() const
|
||||
{
|
||||
return hasAwg3Markers(*this);
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4)
|
||||
{
|
||||
QSet<QString> uniqueHeaders;
|
||||
int filledHeaders = 0;
|
||||
for (const QString &header : { h1, h2, h3, h4 }) {
|
||||
if (!header.trimmed().isEmpty()) {
|
||||
++filledHeaders;
|
||||
uniqueHeaders.insert(header);
|
||||
}
|
||||
}
|
||||
return uniqueHeaders.size() != filledHeaders;
|
||||
return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4);
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::isPacketSizeEqual(int s1, int s2, int s3, int s4)
|
||||
|
||||
@@ -39,22 +39,12 @@ struct AwgServerConfig {
|
||||
QString specialJunk3;
|
||||
QString specialJunk4;
|
||||
QString specialJunk5;
|
||||
QString headerProtectionKey;
|
||||
QString contentPaddingAddition;
|
||||
QString rekeyAfterTime;
|
||||
QString rekeyTimeout;
|
||||
QString rejectAfterTime;
|
||||
QString keepaliveTimeout;
|
||||
QString maxHandshakeAttempts;
|
||||
QString randomTrailers;
|
||||
QString disableCookies;
|
||||
bool isThirdPartyConfig = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static AwgServerConfig fromJson(const QJsonObject& json);
|
||||
|
||||
|
||||
bool hasEqualServerSettings(const AwgServerConfig& other) const;
|
||||
bool hasAwg3Params() const;
|
||||
};
|
||||
|
||||
struct AwgClientConfig {
|
||||
@@ -86,15 +76,8 @@ struct AwgClientConfig {
|
||||
QString specialJunk3;
|
||||
QString specialJunk4;
|
||||
QString specialJunk5;
|
||||
QString headerProtectionKey;
|
||||
QString contentPaddingAddition;
|
||||
QString rekeyAfterTime;
|
||||
QString rekeyTimeout;
|
||||
QString rejectAfterTime;
|
||||
QString keepaliveTimeout;
|
||||
QString maxHandshakeAttempts;
|
||||
QString randomTrailers;
|
||||
QString disableCookies;
|
||||
bool isObfuscationEnabled = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static AwgClientConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
@@ -106,10 +89,6 @@ struct AwgProtocolConfig {
|
||||
QJsonObject toJson() const;
|
||||
static AwgProtocolConfig fromJson(const QJsonObject& json);
|
||||
|
||||
QString serverProtocolVersion() const;
|
||||
QString clientProtocolVersion() const;
|
||||
static QString protocolVersionString(const QString &version);
|
||||
|
||||
bool hasClientConfig() const;
|
||||
void setClientConfig(const AwgClientConfig& config);
|
||||
void clearClientConfig();
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "mtProxyProtocolConfig.h"
|
||||
|
||||
#include "../../../core/utils/protocolEnum.h"
|
||||
#include "../../../core/protocols/protocolUtils.h"
|
||||
#include "../../../core/utils/constants/configKeys.h"
|
||||
#include "../../../core/utils/constants/protocolConstants.h"
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
QJsonObject MtProxyProtocolConfig::toJson() const {
|
||||
QJsonObject obj;
|
||||
|
||||
if (!port.isEmpty()) {
|
||||
obj[configKey::port] = port;
|
||||
}
|
||||
if (!secret.isEmpty()) {
|
||||
obj[protocols::mtProxy::secretKey] = secret;
|
||||
}
|
||||
if (!tag.isEmpty()) {
|
||||
obj[protocols::mtProxy::tagKey] = tag;
|
||||
}
|
||||
if (!tgLink.isEmpty()) {
|
||||
obj[protocols::mtProxy::tgLinkKey] = tgLink;
|
||||
}
|
||||
if (!tmeLink.isEmpty()) {
|
||||
obj[protocols::mtProxy::tmeLinkKey] = tmeLink;
|
||||
}
|
||||
obj[protocols::mtProxy::isEnabledKey] = isEnabled;
|
||||
if (!publicHost.isEmpty()) {
|
||||
obj[protocols::mtProxy::publicHostKey] = publicHost;
|
||||
}
|
||||
if (!transportMode.isEmpty()) {
|
||||
obj[protocols::mtProxy::transportModeKey] = transportMode;
|
||||
}
|
||||
if (!tlsDomain.isEmpty()) {
|
||||
obj[protocols::mtProxy::tlsDomainKey] = tlsDomain;
|
||||
}
|
||||
if (!additionalSecrets.isEmpty()) {
|
||||
obj[protocols::mtProxy::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
|
||||
}
|
||||
if (!workersMode.isEmpty()) {
|
||||
obj[protocols::mtProxy::workersModeKey] = workersMode;
|
||||
}
|
||||
if (!workers.isEmpty()) {
|
||||
obj[protocols::mtProxy::workersKey] = workers;
|
||||
}
|
||||
obj[protocols::mtProxy::natEnabledKey] = natEnabled;
|
||||
if (!natInternalIp.isEmpty()) {
|
||||
obj[protocols::mtProxy::natInternalIpKey] = natInternalIp;
|
||||
}
|
||||
if (!natExternalIp.isEmpty()) {
|
||||
obj[protocols::mtProxy::natExternalIpKey] = natExternalIp;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
MtProxyProtocolConfig MtProxyProtocolConfig::fromJson(const QJsonObject &json) {
|
||||
MtProxyProtocolConfig config;
|
||||
|
||||
config.port = json.value(configKey::port).toString();
|
||||
config.secret = json.value(protocols::mtProxy::secretKey).toString();
|
||||
config.tag = json.value(protocols::mtProxy::tagKey).toString();
|
||||
config.tgLink = json.value(protocols::mtProxy::tgLinkKey).toString();
|
||||
config.tmeLink = json.value(protocols::mtProxy::tmeLinkKey).toString();
|
||||
config.isEnabled = json.value(protocols::mtProxy::isEnabledKey).toBool(true);
|
||||
config.publicHost = json.value(protocols::mtProxy::publicHostKey).toString();
|
||||
config.transportMode = json.value(protocols::mtProxy::transportModeKey).toString();
|
||||
config.tlsDomain = json.value(protocols::mtProxy::tlsDomainKey).toString();
|
||||
for (const auto &v: json.value(protocols::mtProxy::additionalSecretsKey).toArray()) {
|
||||
const QString s = v.toString();
|
||||
if (!s.isEmpty()) {
|
||||
config.additionalSecrets.append(s);
|
||||
}
|
||||
}
|
||||
config.workersMode = json.value(protocols::mtProxy::workersModeKey).toString();
|
||||
config.workers = json.value(protocols::mtProxy::workersKey).toString();
|
||||
config.natEnabled = json.value(protocols::mtProxy::natEnabledKey).toBool(false);
|
||||
config.natInternalIp = json.value(protocols::mtProxy::natInternalIpKey).toString();
|
||||
config.natExternalIp = json.value(protocols::mtProxy::natExternalIpKey).toString();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
bool MtProxyProtocolConfig::equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const {
|
||||
const auto normPort = [](const QString &p) {
|
||||
return p.isEmpty() ? QString(protocols::mtProxy::defaultPort) : p;
|
||||
};
|
||||
const auto normTransport = [](const QString &t) {
|
||||
return t.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : t;
|
||||
};
|
||||
const auto normWorkersMode = [](const QString &m) {
|
||||
return m.isEmpty() ? QString(protocols::mtProxy::workersModeAuto) : m;
|
||||
};
|
||||
|
||||
if (normPort(port) != normPort(other.port)) {
|
||||
return false;
|
||||
}
|
||||
if (normTransport(transportMode) != normTransport(other.transportMode)) {
|
||||
return false;
|
||||
}
|
||||
if (tlsDomain != other.tlsDomain) {
|
||||
return false;
|
||||
}
|
||||
if (secret != other.secret) {
|
||||
return false;
|
||||
}
|
||||
if (tag != other.tag) {
|
||||
return false;
|
||||
}
|
||||
if (publicHost != other.publicHost) {
|
||||
return false;
|
||||
}
|
||||
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
|
||||
return false;
|
||||
}
|
||||
if (workers != other.workers) {
|
||||
return false;
|
||||
}
|
||||
if (natEnabled != other.natEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (natInternalIp != other.natInternalIp) {
|
||||
return false;
|
||||
}
|
||||
if (natExternalIp != other.natExternalIp) {
|
||||
return false;
|
||||
}
|
||||
if (isEnabled != other.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList aa = additionalSecrets;
|
||||
QStringList bb = other.additionalSecrets;
|
||||
aa.removeAll(QString());
|
||||
bb.removeAll(QString());
|
||||
std::sort(aa.begin(), aa.end());
|
||||
std::sort(bb.begin(), bb.end());
|
||||
return aa == bb;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef MTPROXYPROTOCOLCONFIG_H
|
||||
#define MTPROXYPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
struct MtProxyProtocolConfig {
|
||||
QString port;
|
||||
QString secret;
|
||||
QString tag;
|
||||
QString tgLink;
|
||||
QString tmeLink;
|
||||
bool isEnabled = true;
|
||||
QString publicHost;
|
||||
QString transportMode;
|
||||
QString tlsDomain;
|
||||
QStringList additionalSecrets;
|
||||
QString workersMode;
|
||||
QString workers;
|
||||
bool natEnabled = false;
|
||||
QString natInternalIp;
|
||||
QString natExternalIp;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static MtProxyProtocolConfig fromJson(const QJsonObject &json);
|
||||
|
||||
// Port, transport, TLS, secrets, NAT, workers, isEnabled, additionalSecrets (order-independent).
|
||||
// Ignores tgLink / tmeLink (derived / display).
|
||||
bool equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // MTPROXYPROTOCOLCONFIG_H
|
||||
@@ -1,150 +0,0 @@
|
||||
#include "telemtProtocolConfig.h"
|
||||
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
QJsonObject TelemtProtocolConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
if (!port.isEmpty()) {
|
||||
obj[QString(configKey::port)] = port;
|
||||
}
|
||||
if (!secret.isEmpty()) {
|
||||
obj[protocols::telemt::secretKey] = secret;
|
||||
}
|
||||
if (!tag.isEmpty()) {
|
||||
obj[protocols::telemt::tagKey] = tag;
|
||||
}
|
||||
if (!tgLink.isEmpty()) {
|
||||
obj[protocols::telemt::tgLinkKey] = tgLink;
|
||||
}
|
||||
if (!tmeLink.isEmpty()) {
|
||||
obj[protocols::telemt::tmeLinkKey] = tmeLink;
|
||||
}
|
||||
obj[protocols::telemt::isEnabledKey] = isEnabled;
|
||||
if (!publicHost.isEmpty()) {
|
||||
obj[protocols::telemt::publicHostKey] = publicHost;
|
||||
}
|
||||
if (!transportMode.isEmpty()) {
|
||||
obj[protocols::telemt::transportModeKey] = transportMode;
|
||||
}
|
||||
if (!tlsDomain.isEmpty()) {
|
||||
obj[protocols::telemt::tlsDomainKey] = tlsDomain;
|
||||
}
|
||||
obj[protocols::telemt::maskEnabledKey] = maskEnabled;
|
||||
obj[protocols::telemt::tlsEmulationKey] = tlsEmulation;
|
||||
obj[protocols::telemt::useMiddleProxyKey] = useMiddleProxy;
|
||||
if (!userName.isEmpty()) {
|
||||
obj[protocols::telemt::userNameKey] = userName;
|
||||
}
|
||||
if (!additionalSecrets.isEmpty()) {
|
||||
obj[protocols::telemt::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
|
||||
}
|
||||
if (!workersMode.isEmpty()) {
|
||||
obj[protocols::telemt::workersModeKey] = workersMode;
|
||||
}
|
||||
if (!workers.isEmpty()) {
|
||||
obj[protocols::telemt::workersKey] = workers;
|
||||
}
|
||||
obj[protocols::telemt::natEnabledKey] = natEnabled;
|
||||
if (!natInternalIp.isEmpty()) {
|
||||
obj[protocols::telemt::natInternalIpKey] = natInternalIp;
|
||||
}
|
||||
if (!natExternalIp.isEmpty()) {
|
||||
obj[protocols::telemt::natExternalIpKey] = natExternalIp;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
TelemtProtocolConfig TelemtProtocolConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
TelemtProtocolConfig c;
|
||||
c.port = json.value(QString(configKey::port)).toString();
|
||||
c.secret = json.value(protocols::telemt::secretKey).toString();
|
||||
c.tag = json.value(protocols::telemt::tagKey).toString();
|
||||
c.tgLink = json.value(protocols::telemt::tgLinkKey).toString();
|
||||
c.tmeLink = json.value(protocols::telemt::tmeLinkKey).toString();
|
||||
c.isEnabled = json.value(protocols::telemt::isEnabledKey).toBool(true);
|
||||
c.publicHost = json.value(protocols::telemt::publicHostKey).toString();
|
||||
c.transportMode = json.value(protocols::telemt::transportModeKey).toString();
|
||||
c.tlsDomain = json.value(protocols::telemt::tlsDomainKey).toString();
|
||||
c.maskEnabled = json.value(protocols::telemt::maskEnabledKey).toBool(true);
|
||||
c.tlsEmulation = json.value(protocols::telemt::tlsEmulationKey).toBool(false);
|
||||
c.useMiddleProxy = json.value(protocols::telemt::useMiddleProxyKey).toBool(true);
|
||||
c.userName = json.value(protocols::telemt::userNameKey).toString();
|
||||
for (const auto &v : json.value(protocols::telemt::additionalSecretsKey).toArray()) {
|
||||
const QString s = v.toString();
|
||||
if (!s.isEmpty()) {
|
||||
c.additionalSecrets.append(s);
|
||||
}
|
||||
}
|
||||
c.workersMode = json.value(protocols::telemt::workersModeKey).toString();
|
||||
c.workers = json.value(protocols::telemt::workersKey).toString();
|
||||
c.natEnabled = json.value(protocols::telemt::natEnabledKey).toBool(false);
|
||||
c.natInternalIp = json.value(protocols::telemt::natInternalIpKey).toString();
|
||||
c.natExternalIp = json.value(protocols::telemt::natExternalIpKey).toString();
|
||||
return c;
|
||||
}
|
||||
|
||||
bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const
|
||||
{
|
||||
const auto normPort = [](const QString &p) {
|
||||
return p.isEmpty() ? QString(protocols::telemt::defaultPort) : p;
|
||||
};
|
||||
const auto normTransport = [](const QString &t) {
|
||||
return t.isEmpty() ? QString(protocols::telemt::transportModeStandard) : t;
|
||||
};
|
||||
|
||||
if (normPort(port) != normPort(other.port)) {
|
||||
return false;
|
||||
}
|
||||
if (normTransport(transportMode) != normTransport(other.transportMode)) {
|
||||
return false;
|
||||
}
|
||||
if (tlsDomain != other.tlsDomain) {
|
||||
return false;
|
||||
}
|
||||
if (secret != other.secret) {
|
||||
return false;
|
||||
}
|
||||
if (tag != other.tag) {
|
||||
return false;
|
||||
}
|
||||
if (publicHost != other.publicHost) {
|
||||
return false;
|
||||
}
|
||||
if (maskEnabled != other.maskEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (tlsEmulation != other.tlsEmulation) {
|
||||
return false;
|
||||
}
|
||||
if (useMiddleProxy != other.useMiddleProxy) {
|
||||
return false;
|
||||
}
|
||||
if (userName != other.userName) {
|
||||
return false;
|
||||
}
|
||||
if (natEnabled != other.natEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (natExternalIp != other.natExternalIp) {
|
||||
return false;
|
||||
}
|
||||
if (isEnabled != other.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList aa = additionalSecrets;
|
||||
QStringList bb = other.additionalSecrets;
|
||||
aa.removeAll(QString());
|
||||
bb.removeAll(QString());
|
||||
std::sort(aa.begin(), aa.end());
|
||||
std::sort(bb.begin(), bb.end());
|
||||
return aa == bb;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef TELEMTPROTOCOLCONFIG_H
|
||||
#define TELEMTPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
struct TelemtProtocolConfig {
|
||||
QString port;
|
||||
QString secret;
|
||||
QString tag;
|
||||
QString tgLink;
|
||||
QString tmeLink;
|
||||
bool isEnabled = true;
|
||||
QString publicHost;
|
||||
QString transportMode;
|
||||
QString tlsDomain;
|
||||
bool maskEnabled = true;
|
||||
bool tlsEmulation = false;
|
||||
bool useMiddleProxy = true;
|
||||
QString userName;
|
||||
QStringList additionalSecrets;
|
||||
QString workersMode;
|
||||
QString workers;
|
||||
bool natEnabled = false;
|
||||
QString natInternalIp;
|
||||
QString natExternalIp;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static TelemtProtocolConfig fromJson(const QJsonObject &json);
|
||||
bool equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // TELEMTPROTOCOLCONFIG_H
|
||||
@@ -105,17 +105,11 @@ QJsonObject WireGuardClientConfig::toJson() const
|
||||
if (!mtu.isEmpty()) {
|
||||
obj[configKey::mtu] = mtu;
|
||||
}
|
||||
|
||||
for (auto it = awgParams.constBegin(); it != awgParams.constEnd(); ++it) {
|
||||
if (!it.value().isEmpty()) {
|
||||
obj[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -139,16 +133,9 @@ WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json)
|
||||
}
|
||||
config.persistentKeepAlive = json.value(configKey::persistentKeepAlive).toString();
|
||||
config.mtu = json.value(configKey::mtu).toString();
|
||||
|
||||
for (const QString &key : configKey::awgProtocolKeys()) {
|
||||
const QString value = json.value(key).toString();
|
||||
if (!value.isEmpty()) {
|
||||
config.awgParams.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define WIREGUARDPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <optional>
|
||||
@@ -37,10 +36,8 @@ struct WireGuardClientConfig {
|
||||
QStringList allowedIps;
|
||||
QString persistentKeepAlive;
|
||||
QString mtu;
|
||||
|
||||
QMap<QString, QString> awgParams;
|
||||
bool isObfuscationEnabled = false;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static WireGuardClientConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,284 +1,48 @@
|
||||
#ifndef XRAYPROTOCOLCONFIG_H
|
||||
#define XRAYPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <optional>
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
// ── xPadding ─────────────────────────────────────────────────────────────────
|
||||
struct XrayXPaddingConfig {
|
||||
QString bytesMin; // xPaddingBytes min
|
||||
QString bytesMax; // xPaddingBytes max
|
||||
bool obfsMode = true; // xPaddingObfsMode
|
||||
QString key; // xPaddingKey
|
||||
QString header; // xPaddingHeader
|
||||
QString placement = protocols::xray::defaultXPaddingPlacement; // xPaddingPlacement: Cookie|Header|Query|Body
|
||||
QString method = protocols::xray::defaultXPaddingMethod; // xPaddingMethod: Repeat-x|Random|Zero
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayXPaddingConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
// ── xmux ─────────────────────────────────────────────────────────────────────
|
||||
struct XrayXmuxConfig {
|
||||
bool enabled = true;
|
||||
|
||||
QString maxConcurrencyMin = "0";
|
||||
QString maxConcurrencyMax = "0";
|
||||
QString maxConnectionsMin = "0";
|
||||
QString maxConnectionsMax = "0";
|
||||
QString cMaxReuseTimesMin = "0";
|
||||
QString cMaxReuseTimesMax = "0";
|
||||
QString hMaxRequestTimesMin = "0";
|
||||
QString hMaxRequestTimesMax = "0";
|
||||
QString hMaxReusableSecsMin = "0";
|
||||
QString hMaxReusableSecsMax = "0";
|
||||
QString hKeepAlivePeriod;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayXmuxConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
struct XrayClientTemplate {
|
||||
int formatVersion = 0;
|
||||
|
||||
QString fingerprint = protocols::xray::defaultFingerprint;
|
||||
QString uplinkMethod = protocols::xray::defaultXhttpUplinkMethod;
|
||||
QString uplinkChunkSize = protocols::xray::defaultXhttpUplinkChunkSize;
|
||||
QString scMinPostsIntervalMsMin = protocols::xray::defaultXhttpScMinPostsIntervalMsMin;
|
||||
QString scMinPostsIntervalMsMax = protocols::xray::defaultXhttpScMinPostsIntervalMsMax;
|
||||
|
||||
XrayXmuxConfig xmux;
|
||||
|
||||
QString updatedAt;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayClientTemplate fromJson(const QJsonObject &json);
|
||||
|
||||
QString contentFingerprint() const;
|
||||
|
||||
void materializeFromLegacy(const QJsonObject &storedServerJson);
|
||||
};
|
||||
|
||||
// ── XHTTP transport ───────────────────────────────────────────────────────────
|
||||
struct XrayXhttpConfig {
|
||||
QString mode = protocols::xray::defaultXhttpMode; // Auto|Packet-up|Stream-up|Stream-one
|
||||
QString host = protocols::xray::defaultXhttpHost;
|
||||
QString path;
|
||||
bool disableGrpc = true;
|
||||
bool disableSse = true;
|
||||
|
||||
// Session & Sequence
|
||||
QString sessionPlacement = protocols::xray::defaultXhttpSessionPlacement;
|
||||
QString sessionKey = protocols::xray::defaultXhttpSessionKey;
|
||||
QString seqPlacement = protocols::xray::defaultXhttpSeqPlacement;
|
||||
QString seqKey;
|
||||
QString uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement;
|
||||
QString uplinkDataKey;
|
||||
|
||||
// Traffic Shaping
|
||||
QString scMaxBufferedPosts;
|
||||
QString scMaxEachPostBytesMin = protocols::xray::defaultXhttpScMaxEachPostBytesMin;
|
||||
QString scMaxEachPostBytesMax = protocols::xray::defaultXhttpScMaxEachPostBytesMax;
|
||||
QString scStreamUpServerSecsMin = protocols::xray::defaultXhttpScStreamUpServerSecsMin;
|
||||
QString scStreamUpServerSecsMax = protocols::xray::defaultXhttpScStreamUpServerSecsMax;
|
||||
|
||||
XrayXPaddingConfig xPadding;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
/// Reads only keys present in JSON (no Amnezia UI defaults). Use XrayConfigModel::applyDefaultsToServerConfig for UI.
|
||||
static XrayXhttpConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
// ── mKCP transport ────────────────────────────────────────────────────────────
|
||||
struct XrayMkcpConfig {
|
||||
QString tti;
|
||||
QString mtu;
|
||||
QString uplinkCapacity;
|
||||
QString downlinkCapacity;
|
||||
QString cwndMultiplier;
|
||||
QString maxSendingWindow;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayMkcpConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
/// Which end of the connection the emitted xray json is meant for. The client
|
||||
/// stream settings are the server ones plus the fields the server never reads.
|
||||
enum class XrayStreamSide {
|
||||
Server,
|
||||
Client,
|
||||
};
|
||||
|
||||
/// Runtime values the server document needs and the structure must not keep:
|
||||
/// the client list of this particular operation and the ssh-read reality secrets.
|
||||
struct XrayServerInboundInputs {
|
||||
QJsonArray clients;
|
||||
QString realityPrivateKey;
|
||||
QString realityShortId;
|
||||
};
|
||||
|
||||
/// Why a server.json could not be read back into the structure. The caller turns
|
||||
/// this into an error code and a log line; parsing itself stays silent.
|
||||
enum class XrayServerJsonStatus {
|
||||
Ok,
|
||||
MissingInbounds,
|
||||
EmptyInbounds,
|
||||
MissingStreamSettings,
|
||||
MissingSettings,
|
||||
};
|
||||
|
||||
/// What to do with account entries that carry no id when the list is rewritten.
|
||||
enum class XrayClientListFilter {
|
||||
KeepAll,
|
||||
DropWithoutId,
|
||||
};
|
||||
|
||||
// ── Server config (settings editable by user) ─────────────────────────────────
|
||||
struct XrayServerConfig {
|
||||
QString port;
|
||||
QString transportProto;
|
||||
QString subnetAddress;
|
||||
QString site;
|
||||
bool isThirdPartyConfig = false;
|
||||
|
||||
QString security;
|
||||
QString flow;
|
||||
QString sni;
|
||||
QString alpn;
|
||||
|
||||
QString transport;
|
||||
XrayXhttpConfig xhttp;
|
||||
XrayMkcpConfig mkcp;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
static XrayServerConfig fromJson(const QJsonObject &json);
|
||||
|
||||
void applyDefaults(bool fillFlowDefault = false);
|
||||
|
||||
/// Single emitter for both ends. The template is only read on the client side;
|
||||
/// pass anything on the server side, its fields are not written there.
|
||||
QJsonObject streamSettingsJson(XrayStreamSide side, const XrayClientTemplate &clientTemplate) const;
|
||||
|
||||
QJsonObject serverStreamSettings() const;
|
||||
|
||||
QJsonObject clientStreamSettings(const XrayClientTemplate &clientTemplate) const;
|
||||
|
||||
/// The whole server.json this configuration means, ready to be uploaded.
|
||||
QJsonObject toServerInboundJson(const XrayServerInboundInputs &inputs) const;
|
||||
|
||||
/// The way back: a server.json read off the container into the two structures.
|
||||
/// Both are written, because a server document also carries client-side fields
|
||||
/// (the uTLS preset, the uplink method, xmux) that belong in the template.
|
||||
static XrayServerJsonStatus fromServerInboundJson(const QJsonObject &serverJson, XrayServerConfig &outServerConfig,
|
||||
XrayClientTemplate &outClientTemplate);
|
||||
|
||||
/// The account list of a server document. Reading is forgiving: a document with
|
||||
/// no inbounds simply has no accounts. Writing is strict, because putting a list
|
||||
/// into a document that has nowhere to hold it would drop accounts in silence.
|
||||
static QJsonArray clientsFromServerInboundJson(const QJsonObject &serverJson);
|
||||
static XrayServerJsonStatus setClientsInServerInboundJson(QJsonObject &serverJson, const QJsonArray &clients);
|
||||
|
||||
/// One account entry, its position in a list, and the flow rewritten across a list.
|
||||
/// An empty flow means the key is taken out, which is how the raw transport wants it.
|
||||
static QJsonObject makeClientEntry(const QString &clientId, const QString &flowValue);
|
||||
static QJsonObject applyFlowToClient(const QJsonObject &client, const QString &flowValue);
|
||||
static int indexOfClient(const QJsonArray &clients, const QString &clientId);
|
||||
static QString firstClientId(const QJsonArray &clients);
|
||||
static QJsonArray applyFlowToClients(const QJsonArray &clients, const QString &flowValue,
|
||||
XrayClientListFilter filter = XrayClientListFilter::KeepAll);
|
||||
|
||||
QJsonObject serverView() const;
|
||||
|
||||
QJsonObject issuedConfigView() const;
|
||||
|
||||
bool hasEqualServerSettings(const XrayServerConfig &other) const;
|
||||
|
||||
QStringList serverViewDifferences(const XrayServerConfig &other) const;
|
||||
|
||||
bool breaksIssuedConfigs(const XrayServerConfig &other) const;
|
||||
static XrayServerConfig fromJson(const QJsonObject& json);
|
||||
|
||||
bool hasEqualServerSettings(const XrayServerConfig& other) const;
|
||||
};
|
||||
|
||||
namespace xrayEffective
|
||||
{
|
||||
QString xhttpMode(const QString &mode);
|
||||
QString sessionSeqPlacement(const QString &placement);
|
||||
QString uplinkDataPlacement(const QString &placement);
|
||||
QString xPaddingPlacement(const QString &placement);
|
||||
QString xPaddingMethod(const QString &method);
|
||||
QString range(const QString &minV, const QString &maxV);
|
||||
void putRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
|
||||
const char *fallbackMax);
|
||||
|
||||
QString security(const XrayServerConfig &srv);
|
||||
QString clientFlow(const XrayServerConfig &srv);
|
||||
QString network(const XrayServerConfig &srv);
|
||||
QString xhttpModeSent(const XrayServerConfig &srv);
|
||||
}
|
||||
|
||||
/// Runtime values the client document needs and the structures must not keep:
|
||||
/// the host we connect to, the account this device was given, and the ssh-read
|
||||
/// reality keys / TLS certificate pin.
|
||||
struct XrayClientOutboundInputs {
|
||||
QString serverAddress;
|
||||
QString clientId;
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
QString tlsPinnedPeerCertSha256;
|
||||
};
|
||||
|
||||
// ── Client config (generated, not edited by user) ─────────────────────────────
|
||||
struct XrayClientConfig {
|
||||
QString nativeConfig;
|
||||
QString localPort;
|
||||
QString id;
|
||||
QString templateFingerprint;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayClientConfig fromJson(const QJsonObject &json);
|
||||
|
||||
/// The two runtime values read back out of a native client document.
|
||||
static QString idFromNativeJson(const QJsonObject &nativeJson);
|
||||
static QString localPortFromNativeJson(const QJsonObject &nativeJson);
|
||||
static XrayClientConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
// ── Top-level protocol config ──────────────────────────────────────────────────
|
||||
struct XrayProtocolConfig {
|
||||
XrayServerConfig serverConfig;
|
||||
XrayClientTemplate clientTemplate;
|
||||
std::optional<XrayClientConfig> clientConfig;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayProtocolConfig fromJson(const QJsonObject &json);
|
||||
|
||||
static XrayProtocolConfig fromJson(const QJsonObject& json);
|
||||
|
||||
bool hasClientConfig() const;
|
||||
void setClientConfig(const XrayClientConfig &config);
|
||||
void setClientConfig(const XrayClientConfig& config);
|
||||
void clearClientConfig();
|
||||
|
||||
bool needsClientHydration = false;
|
||||
bool needsTemplateMaterialization = false;
|
||||
|
||||
bool templateWasMaterialized = false;
|
||||
|
||||
/// The whole client document this configuration means, ready for the core.
|
||||
QJsonObject toClientOutboundJson(const XrayClientOutboundInputs &inputs) const;
|
||||
|
||||
/// The way back: a client document read into the server config, the template and
|
||||
/// the runtime fields of the cached client config.
|
||||
bool fromClientOutboundJson(const QJsonObject &nativeJson);
|
||||
|
||||
bool hydrateServerConfigFromClientNative();
|
||||
|
||||
bool materializeTemplateFromServerConfig();
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // XRAYPROTOCOLCONFIG_H
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -29,25 +28,6 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject NativeServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -55,6 +35,9 @@ QJsonObject NativeServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -87,6 +70,7 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
|
||||
NativeServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user