mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-18 00:45:58 +03:00
Compare commits
427 Commits
improve_na
...
fix/make_d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a53eb556b7 | ||
|
|
fd915a4325 | ||
|
|
970fc26113 | ||
|
|
c7b7ecd1d6 | ||
|
|
0edffc5f88 | ||
|
|
dcb097b0b2 | ||
|
|
a78f42a199 | ||
|
|
fd5051262d | ||
|
|
847bb6923b | ||
|
|
2edd7de413 | ||
|
|
f0da2b003f | ||
|
|
650c1c6ebb | ||
|
|
8dbded1624 | ||
|
|
cebfcc846e | ||
|
|
4c18ceaa50 | ||
|
|
ebe3a5dac6 | ||
|
|
92deee5f67 | ||
|
|
a75bd0cf5e | ||
|
|
46f5b3894b | ||
|
|
493ee22883 | ||
|
|
ad14847eb5 | ||
|
|
cd50e0b8a5 | ||
|
|
78f504e35c | ||
|
|
bf3d11e5c4 | ||
|
|
9a0222aee3 | ||
|
|
f0f0f7c5be | ||
|
|
36b1a863bf | ||
|
|
4103c5bbcf | ||
|
|
fa69da6d56 | ||
|
|
aaf2c9ddeb | ||
|
|
dbbc7119ec | ||
|
|
c57162c4cc | ||
|
|
40e39895c9 | ||
|
|
ec3ab2a03c | ||
|
|
ddecfcad26 | ||
|
|
67bd880cdf | ||
|
|
477afb9d85 | ||
|
|
f969fcdbb8 | ||
|
|
b0ca16d861 | ||
|
|
9963359948 | ||
|
|
ca639d293d | ||
|
|
83d045af64 | ||
|
|
aea8ff4961 | ||
|
|
1892db4375 | ||
|
|
c86a641e05 | ||
|
|
befb2bf19a | ||
|
|
7ad6bc340c | ||
|
|
9164e38c34 | ||
|
|
8f7559f01b | ||
|
|
af56200735 | ||
|
|
3874050fae | ||
|
|
3087163e34 | ||
|
|
1fa152845c | ||
|
|
50e23ef233 | ||
|
|
ea648466de | ||
|
|
b782775016 | ||
|
|
89a7fe1081 | ||
|
|
e8bb096025 | ||
|
|
fd5c7c8322 | ||
|
|
e798d0f503 | ||
|
|
bbb0abb596 | ||
|
|
0925aec86a | ||
|
|
b084c4c284 | ||
|
|
87288ebccd | ||
|
|
fcd7eadf4c | ||
|
|
0373338fb7 | ||
|
|
42f070fe9d | ||
|
|
02be6dc5f9 | ||
|
|
bfcf7f0305 | ||
|
|
2bce595ade | ||
|
|
cd1e561fd4 | ||
|
|
9bd1e6a0f5 | ||
|
|
5058c9aa6f | ||
|
|
d78416835c | ||
|
|
40e6c6aae3 | ||
|
|
911a999c64 | ||
|
|
b4f4184aa6 | ||
|
|
5c6db4b7a4 | ||
|
|
f6277cdbb2 | ||
|
|
99312e61d3 | ||
|
|
9f0ae75a2f | ||
|
|
7960d8015d | ||
|
|
5dcc64e5e5 | ||
|
|
964436ad43 | ||
|
|
4fc3900fd5 | ||
|
|
8f5e42dd61 | ||
|
|
24895752c1 | ||
|
|
87eccfb4ca | ||
|
|
a983d0504e | ||
|
|
d0b8535395 | ||
|
|
f84480cf56 | ||
|
|
de7a026ec1 | ||
|
|
a128c7d247 | ||
|
|
f316f0e25a | ||
|
|
ea5242e29b | ||
|
|
b31a62c55f | ||
|
|
02e3107a23 | ||
|
|
1862850108 | ||
|
|
f73792844c | ||
|
|
a7199ca6f5 | ||
|
|
5e757cdd3b | ||
|
|
92af1f3268 | ||
|
|
aad9d6dae2 | ||
|
|
423fe3fd4f | ||
|
|
b591dd7445 | ||
|
|
a45bb5ea4f | ||
|
|
d859b111ca | ||
|
|
52031efc48 | ||
|
|
d78202c612 | ||
|
|
6bac948633 | ||
|
|
a4c4ef71fb | ||
|
|
127f85f4f0 | ||
|
|
13d4ddd292 | ||
|
|
7265e09c85 | ||
|
|
2e629b6dac | ||
|
|
92aba49705 | ||
|
|
bec06b3a5e | ||
|
|
91cd9474ea | ||
|
|
6178b05643 | ||
|
|
46ce22b85c | ||
|
|
36edafb985 | ||
|
|
d77eaba500 | ||
|
|
6a3d43fbb0 | ||
|
|
4975955bbe | ||
|
|
8f508783e3 | ||
|
|
f50817c43c | ||
|
|
54f67b3d82 | ||
|
|
d669adb707 | ||
|
|
5103bc640e | ||
|
|
3e6f0c0342 | ||
|
|
40950b92ee | ||
|
|
ac77b4ee75 | ||
|
|
fbf652f818 | ||
|
|
bbbf4891e6 | ||
|
|
20d005d66c | ||
|
|
c81ae2b060 | ||
|
|
105c42db1c | ||
|
|
89818ff63d | ||
|
|
414c422177 | ||
|
|
b39ac8556c | ||
|
|
5e1742262d | ||
|
|
5a07a1274f | ||
|
|
7b8ff1fd6e | ||
|
|
c7221832e0 | ||
|
|
eb7d031c7d | ||
|
|
3b3a0aaceb | ||
|
|
01ec79b7d5 | ||
|
|
3d6339e2dd | ||
|
|
b4d78d865a | ||
|
|
b53cdcff08 | ||
|
|
3cc18c5807 | ||
|
|
5fdce1e49e | ||
|
|
2ee61a040b | ||
|
|
741b5cc0f9 | ||
|
|
aaf0e070dc | ||
|
|
e0e126eda8 | ||
|
|
236daf6b3b | ||
|
|
f1481b1b1f | ||
|
|
f6e7d3ccf1 | ||
|
|
a754a11913 | ||
|
|
4d25e3b6f6 | ||
|
|
1fac280497 | ||
|
|
c886c5e6a7 | ||
|
|
cd7f78b9ca | ||
|
|
a587d3230f | ||
|
|
93e7b45136 | ||
|
|
e024f71ce1 | ||
|
|
50d1be7b4a | ||
|
|
3ec6d8973b | ||
|
|
3ea47d31a9 | ||
|
|
30c8cc4548 | ||
|
|
98586d2dd9 | ||
|
|
c66d8ecca0 | ||
|
|
db535f7e7d | ||
|
|
89f30d8c31 | ||
|
|
8bce432824 | ||
|
|
f3539b2632 | ||
|
|
7a96c212f3 | ||
|
|
2d5dc54e0f | ||
|
|
cef4c262e9 | ||
|
|
34309261a8 | ||
|
|
657eeb40c7 | ||
|
|
b4938c2cc9 | ||
|
|
524fefc5cb | ||
|
|
73f13404bb | ||
|
|
5fc68cca83 | ||
|
|
fcb7b8fa8d | ||
|
|
a81e32ff95 | ||
|
|
c897052107 | ||
|
|
4d0efc7ea5 | ||
|
|
a77842c9e3 | ||
|
|
0ded9db780 | ||
|
|
58d480fcb5 | ||
|
|
7154428d26 | ||
|
|
02a52d0169 | ||
|
|
ec60764072 | ||
|
|
17d2fa5532 | ||
|
|
3ca8b534e8 | ||
|
|
e88f7c5e46 | ||
|
|
3ac5d7bd1f | ||
|
|
19cad00a00 | ||
|
|
1ea716a163 | ||
|
|
4551659c2a | ||
|
|
c568bf8c24 | ||
|
|
a412d91105 | ||
|
|
ad01f23bbe | ||
|
|
656070b132 | ||
|
|
c907f5ca36 | ||
|
|
94a13b2b54 | ||
|
|
169f11d9c7 | ||
|
|
816dc3af95 | ||
|
|
b802863de5 | ||
|
|
8dc2a4b76c | ||
|
|
beb1c6dbf2 | ||
|
|
3eb06916c7 | ||
|
|
30d0f84a4f | ||
|
|
251f2aa5db | ||
|
|
16d92ddb7c | ||
|
|
e9d4fd8482 | ||
|
|
9fdcf5ab13 | ||
|
|
a6e6de33c8 | ||
|
|
53c7fd4d81 | ||
|
|
2608ea4367 | ||
|
|
d20ed4ad01 | ||
|
|
eae2936449 | ||
|
|
da8ad1f6ba | ||
|
|
5472347969 | ||
|
|
a43f7a6926 | ||
|
|
47f917de0b | ||
|
|
dbeb7edd7a | ||
|
|
6cede712f5 | ||
|
|
d328739192 | ||
|
|
d15c0bd962 | ||
|
|
d53c794936 | ||
|
|
e5dcb25a4a | ||
|
|
f9002b4f43 | ||
|
|
0531508a75 | ||
|
|
174e85a20a | ||
|
|
e9abb6f1e2 | ||
|
|
5be44f9596 | ||
|
|
90efaaff92 | ||
|
|
99b554e7c3 | ||
|
|
ac0ce8a6f6 | ||
|
|
9f9da885b7 | ||
|
|
f51fd2bf3e | ||
|
|
c8378fd32d | ||
|
|
d767214f10 | ||
|
|
e027c504ae | ||
|
|
669a95d975 | ||
|
|
a96df5d518 | ||
|
|
c5c81735a0 | ||
|
|
c933745707 | ||
|
|
6710fd18b3 | ||
|
|
1b78a71529 | ||
|
|
1909d3c94e | ||
|
|
10a107716c | ||
|
|
5445e6637b | ||
|
|
2380cd5cfb | ||
|
|
42661618dc | ||
|
|
8a7e901d7a | ||
|
|
f8bea71716 | ||
|
|
efcc0b7efc | ||
|
|
4d17e913b5 | ||
|
|
b341934863 | ||
|
|
127f8ed3bb | ||
|
|
9dca80de18 | ||
|
|
b0a6bcc055 | ||
|
|
f0626e2eca | ||
|
|
979ab42c5a | ||
|
|
e152e84ddc | ||
|
|
2605978889 | ||
|
|
a2d30efaab | ||
|
|
d3715d00ae | ||
|
|
c37662dbe2 | ||
|
|
768ca1e73d | ||
|
|
a20516850c | ||
|
|
7a203868ec | ||
|
|
43c3ce9a6e | ||
|
|
369e08844f | ||
|
|
48a5452a65 | ||
|
|
c2f9340db6 | ||
|
|
a6508e642a | ||
|
|
a3e73797c2 | ||
|
|
df7bf204ea | ||
|
|
e16243ff55 | ||
|
|
e23cbe67ad | ||
|
|
7702f2f74c | ||
|
|
b457ef9a3f | ||
|
|
a28ed6a977 | ||
|
|
0c73682cfc | ||
|
|
7e380b6cfb | ||
|
|
63b5257986 | ||
|
|
acc4485e81 | ||
|
|
2c44999a31 | ||
|
|
e59a48f9f4 | ||
|
|
b86356b0cc | ||
|
|
f6d7552b58 | ||
|
|
5bd88ac2e9 | ||
|
|
94fa5b59f3 | ||
|
|
7169480999 | ||
|
|
c44ce0d77c | ||
|
|
7fd71a8408 | ||
|
|
68db721089 | ||
|
|
a180e12bdf | ||
|
|
f3a4a1b1be | ||
|
|
6977a8ecbc | ||
|
|
d00f64e6ad | ||
|
|
d5b3da6ba3 | ||
|
|
c245318339 | ||
|
|
b3b0fec2e1 | ||
|
|
9d571a4c71 | ||
|
|
f283858490 | ||
|
|
76fe203767 | ||
|
|
b9a47f2f50 | ||
|
|
27cb17c640 | ||
|
|
ef8fb89eb3 | ||
|
|
f1b045f8a8 | ||
|
|
050066132b | ||
|
|
2a6e6a1e24 | ||
|
|
92689d084c | ||
|
|
00f314039d | ||
|
|
fcb75e837d | ||
|
|
9fbea76b74 | ||
|
|
b3ff120bcf | ||
|
|
9dea98f020 | ||
|
|
c4701d4e7a | ||
|
|
48903ca3a1 | ||
|
|
0c9fd4aef4 | ||
|
|
b2af2e46ac | ||
|
|
efc76a0683 | ||
|
|
c4a553c166 | ||
|
|
69a00b0252 | ||
|
|
4257c08b43 | ||
|
|
c9e5b92f79 | ||
|
|
99818c2ad8 | ||
|
|
99e3afabad | ||
|
|
d3339a7f3a | ||
|
|
678bfffe49 | ||
|
|
728b48044c | ||
|
|
7ccbfa48bc | ||
|
|
83460bc29b | ||
|
|
c28e1b468a | ||
|
|
abd7fdd19c | ||
|
|
2b1ec9c693 | ||
|
|
19fcddfdaf | ||
|
|
0bca78eca9 | ||
|
|
68046a0b7c | ||
|
|
d19017f87b | ||
|
|
46536bc60a | ||
|
|
6a424e9858 | ||
|
|
8afe50cd87 | ||
|
|
48980c486e | ||
|
|
5f6cd282d3 | ||
|
|
95121c06e2 | ||
|
|
c2b17c128d | ||
|
|
eda24765e7 | ||
|
|
35e0e146e6 | ||
|
|
a5254ac238 | ||
|
|
517b5e5ca6 | ||
|
|
cfeb6cbffd | ||
|
|
c128ba981c | ||
|
|
a1ca994c8b | ||
|
|
52c12940c4 | ||
|
|
25d759374c | ||
|
|
e9250afd2b | ||
|
|
eb83086d5c | ||
|
|
9398e0e695 | ||
|
|
915c8f46c5 | ||
|
|
ec132ac96c | ||
|
|
101838404e | ||
|
|
db3164223a | ||
|
|
5a7b5d34fb | ||
|
|
9420333c76 | ||
|
|
f6403fe82e | ||
|
|
c55b025eee | ||
|
|
fc6fc26148 | ||
|
|
48b43ee102 | ||
|
|
e091020692 | ||
|
|
07baf0ed65 | ||
|
|
42d3d9b98a | ||
|
|
389c1f5327 | ||
|
|
703b9137e0 | ||
|
|
b183a3b232 | ||
|
|
f163f0fc1d | ||
|
|
3b49d5ca59 | ||
|
|
236e5ca2e3 | ||
|
|
2f6e28b980 | ||
|
|
46d96a8887 | ||
|
|
56221881da | ||
|
|
3f55f6a629 | ||
|
|
7c8ae9c311 | ||
|
|
b173dcaa17 | ||
|
|
da5fe1d766 | ||
|
|
a15ea0e8a1 | ||
|
|
fbbba648c4 | ||
|
|
f79bfa9d2e | ||
|
|
3011a0e306 | ||
|
|
76640311ab | ||
|
|
e707471b04 | ||
|
|
6425700d1c | ||
|
|
36045c6694 | ||
|
|
52ecd6899b | ||
|
|
49a6a9ed76 | ||
|
|
4869429eb6 | ||
|
|
956dd6e37a | ||
|
|
665a2911be | ||
|
|
1cfa4e0630 | ||
|
|
5bda624576 | ||
|
|
d1f0560595 | ||
|
|
df07fc1b1f | ||
|
|
8ca31e0c90 | ||
|
|
f1c6067485 | ||
|
|
ca04c63f5e | ||
|
|
89cdd2bece | ||
|
|
73d7dfa54f | ||
|
|
0a5b54a2e4 | ||
|
|
e43aa02a5b | ||
|
|
c3fb62a6ab | ||
|
|
62f3a339b7 | ||
|
|
767b14b37a | ||
|
|
e7fa160c9c | ||
|
|
7350d79c50 | ||
|
|
86f08554cd | ||
|
|
a741186c21 | ||
|
|
6acaab0ffa | ||
|
|
212e9b3a91 | ||
|
|
2bff37efae |
@@ -2,7 +2,7 @@
|
||||
/client/3rd-prebuild
|
||||
/client/android
|
||||
/client/cmake
|
||||
/client/core/serialization
|
||||
/client/core/utils/serialization
|
||||
/client/daemon
|
||||
/client/fonts
|
||||
/client/images
|
||||
|
||||
351
.github/workflows/deploy.yml
vendored
351
.github/workflows/deploy.yml
vendored
@@ -10,16 +10,19 @@ env:
|
||||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: android-runner
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
@@ -28,13 +31,15 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'gcc_64'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
@@ -42,24 +47,31 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
sudo apt-get install libxkbcommon-x11-0
|
||||
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
|
||||
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
|
||||
bash deploy/build_linux.sh
|
||||
|
||||
- name: 'Pack installer'
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_installer.tar
|
||||
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -82,14 +94,17 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
@@ -98,8 +113,16 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -107,32 +130,62 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2019_64'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: 'x64'
|
||||
|
||||
- name: 'Setup .NET SDK'
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: 'Install WiX Toolset'
|
||||
shell: powershell
|
||||
run: |
|
||||
dotnet tool install --global wix --version 4.0.6
|
||||
wix extension add -g WixToolset.UI.wixext/4.0.6
|
||||
wix extension add -g WixToolset.Util.wixext/4.0.6
|
||||
wix extension list -g
|
||||
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
||||
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
|
||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
|
||||
call deploy\\build_windows.bat
|
||||
|
||||
- name: 'Rename Windows installer'
|
||||
shell: cmd
|
||||
run: |
|
||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
||||
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload MSI installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_MSI_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -145,23 +198,26 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.10.1
|
||||
CC: cc
|
||||
CXX: c++
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.2'
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -190,7 +246,7 @@ jobs:
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.1'
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
@@ -205,8 +261,8 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
@@ -243,18 +299,34 @@ jobs:
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
Build-MacOS-old:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
@@ -275,11 +347,6 @@ jobs:
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||
run: |
|
||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
@@ -287,22 +354,185 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
||||
bash deploy/build_macos.sh
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_installer
|
||||
path: AmneziaVPN.dmg
|
||||
name: AmneziaVPN_MacOS_old_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Pack macOS installer'
|
||||
run: |
|
||||
cd deploy/build/pkg
|
||||
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
||||
cd ../../..
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
run: |
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos_ne.sh
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -313,17 +543,20 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: android-runner
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.3
|
||||
ANDROID_BUILD_PLATFORM: android-36
|
||||
QT_VERSION: 6.10.1
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
@@ -396,15 +629,22 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
# cache: 'gradle'
|
||||
|
||||
- name: 'Setup Android NDK'
|
||||
id: setup-ndk
|
||||
@@ -429,35 +669,44 @@ jobs:
|
||||
shell: bash
|
||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||
|
||||
- name: 'Rename Android APKs'
|
||||
run: |
|
||||
cd deploy/build
|
||||
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
||||
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
|
||||
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
|
||||
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
||||
cd ../..
|
||||
|
||||
- name: 'Upload x86_64 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86_64
|
||||
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86
|
||||
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-arm64-v8a
|
||||
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-armeabi-v7a
|
||||
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
|
||||
3
.github/workflows/tag-deploy.yml
vendored
3
.github/workflows/tag-deploy.yml
vendored
@@ -17,9 +17,12 @@ jobs:
|
||||
QIF_VERSION: 4.5
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
||||
63
.github/workflows/tag-upload.yml
vendored
63
.github/workflows/tag-upload.yml
vendored
@@ -1,64 +1,41 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
RELEASE_VERSION:
|
||||
description: 'Release version (e.g. 1.2.3.4)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
Upload-S3:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
ref: ${{ inputs.RELEASE_VERSION }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
deploy/deploy_s3.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
GIT_TAG=${{ github.ref_name }}
|
||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||
else
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
||||
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
- name: Send dist to S3
|
||||
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -9,6 +9,7 @@ deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
|
||||
# Qt-es
|
||||
@@ -133,4 +134,12 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
||||
out/
|
||||
|
||||
# CMake files
|
||||
CMakeFiles/
|
||||
CMakeFiles/
|
||||
|
||||
ios-ne-build.sh
|
||||
macos-ne-build.sh
|
||||
macos-signed-build.sh
|
||||
macos-with-sign-build.sh
|
||||
DeveloperIdApplicationCertificate.p12
|
||||
DeveloperIdInstallerCertificate.p12
|
||||
|
||||
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "client/3rd/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
||||
[submodule "client/3rd/qtkeychain"]
|
||||
path = client/3rd/qtkeychain
|
||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||
@@ -10,9 +7,14 @@
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
branch = feature/special-handshake
|
||||
[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
|
||||
branch = 6.6
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.15.4)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.3.0
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2072)
|
||||
set(APP_ANDROID_VERSION_CODE 2120)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -31,14 +32,56 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
elseif(MACOS_NE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||
else()
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
endif()
|
||||
|
||||
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
|
||||
|
||||
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
|
||||
|
||||
set(CPACK_GENERATOR "WIX")
|
||||
set(CPACK_WIX_VERSION 4)
|
||||
set(CPACK_PACKAGE_NAME "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
||||
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
|
||||
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
||||
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
|
||||
|
||||
# WiX patches
|
||||
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
|
||||
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
|
||||
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
|
||||
|
||||
# WiX v4 Util extension for CloseApplication + namespace for util
|
||||
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
|
||||
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
|
||||
|
||||
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
14
README.md
14
README.md
@@ -9,17 +9,17 @@
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
|
||||
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
@@ -179,13 +179,13 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
|
||||
|
||||
## License
|
||||
|
||||
GPL v3.0
|
||||
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
|
||||
|
||||
## Donate
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
|
||||
26
README_RU.md
26
README_RU.md
@@ -6,16 +6,16 @@
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
|
||||
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
|
||||
|
||||
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
@@ -30,7 +30,7 @@
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): 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.
|
||||
- Поддерживает платформы: 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).
|
||||
|
||||
## Ссылки
|
||||
@@ -38,10 +38,10 @@
|
||||
- [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://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://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\)
|
||||
|
||||
## Технологии
|
||||
@@ -80,8 +80,8 @@ git submodule update --init --recursive
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
|
||||
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с Qt 5
|
||||
@@ -117,7 +117,7 @@ $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
```
|
||||
|
||||
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
@@ -169,7 +169,7 @@ GPL v3.0
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
|
||||
149
THIRD_PARTY_LICENSES.md
Normal file
149
THIRD_PARTY_LICENSES.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Third-Party Licenses
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0.
|
||||
This file lists third-party software components used by this repository.
|
||||
Each component is distributed under its own license as linked below.
|
||||
|
||||
---
|
||||
|
||||
## QtKeychain
|
||||
|
||||
- Source: https://github.com/frankosterfeld/qtkeychain
|
||||
- License: BSD License
|
||||
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
|
||||
|
||||
---
|
||||
|
||||
## QSimpleCrypto
|
||||
|
||||
- Source: https://github.com/n1flh31mur/QSimpleCrypto
|
||||
- License: Apache License 2.0
|
||||
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## SortFilterProxyModel
|
||||
|
||||
- Source: https://github.com/oKcerG/SortFilterProxyModel
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## QJsonStruct
|
||||
|
||||
- Source: https://github.com/Qv2ray/QJsonStruct
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## QR Code Generator (qrcodegen)
|
||||
|
||||
- Source: https://github.com/nayuki/QR-Code-generator
|
||||
- License: MIT License
|
||||
- License Text: https://www.nayuki.io/page/qr-code-generator-library
|
||||
|
||||
---
|
||||
|
||||
## Qt Gamepad
|
||||
|
||||
- Source: https://github.com/qt/qtgamepad
|
||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
---
|
||||
|
||||
## AmneziaWG Apple (WireGuard)
|
||||
|
||||
- Source: https://github.com/amnezia-vpn/amneziawg-apple
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
|
||||
|
||||
---
|
||||
|
||||
## AmneziaWG Android
|
||||
|
||||
- Source: https://github.com/amnezia-vpn/amneziawg-go
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Xray Core
|
||||
|
||||
- Source: https://github.com/XTLS/Xray-core
|
||||
- License: Mozilla Public License 2.0 (MPL-2.0)
|
||||
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Cloak
|
||||
|
||||
- Source: https://github.com/cbeuw/Cloak
|
||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Shadowsocks
|
||||
|
||||
- Source: https://github.com/shadowsocks/shadowsocks-libev
|
||||
- License: GPL-3.0-or-later
|
||||
- License Text: http://www.gnu.org/licenses/
|
||||
|
||||
---
|
||||
|
||||
## OpenSSL
|
||||
|
||||
- Source: https://github.com/openssl/openssl
|
||||
- License: Apache License 2.0
|
||||
- License Text: https://www.openssl.org/source/license.html
|
||||
|
||||
---
|
||||
|
||||
## libssh
|
||||
|
||||
- Source: https://www.libssh.org/
|
||||
- License: GNU Lesser General Public License (LGPL)
|
||||
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
||||
|
||||
---
|
||||
|
||||
## OpenVPNAdapter
|
||||
|
||||
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
|
||||
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
|
||||
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Wintun
|
||||
|
||||
- Source: https://www.wintun.net/
|
||||
- License: Prebuilt Binaries License
|
||||
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
|
||||
|
||||
---
|
||||
|
||||
## Mullvad Split Tunnel Driver
|
||||
|
||||
- Source: https://github.com/mullvad/win-split-tunnel
|
||||
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
|
||||
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
|
||||
|
||||
---
|
||||
|
||||
## tun2socks
|
||||
|
||||
- Source: https://github.com/eycorsican/go-tun2socks
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## TAP-Windows Driver
|
||||
|
||||
- Source: https://github.com/OpenVPN/tap-windows6
|
||||
- License: tap-windows6 license
|
||||
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
|
||||
Submodule client/3rd-prebuilt updated: ba580dc5bd...51bb4703a4
1
client/3rd/OpenVPNAdapter
vendored
1
client/3rd/OpenVPNAdapter
vendored
Submodule client/3rd/OpenVPNAdapter deleted from 7c821a8d5c
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
Submodule client/3rd/amneziawg-apple updated: 76e7db556a...cf63135331
1
client/3rd/qtgamepad
vendored
Submodule
1
client/3rd/qtgamepad
vendored
Submodule
Submodule client/3rd/qtgamepad added at f72b3e0c62
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
set(PROJECT AmneziaVPN)
|
||||
project(${PROJECT})
|
||||
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
||||
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
||||
@@ -26,14 +25,14 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||
|
||||
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
||||
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
|
||||
|
||||
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
@@ -48,24 +47,27 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||
target_include_directories(${PROJECT} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
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)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||
endif()
|
||||
|
||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||
qt6_add_resources(QRC ${QRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
|
||||
)
|
||||
|
||||
# -- i18n begin
|
||||
set(CMAKE_AUTORCC ON)
|
||||
@@ -82,6 +84,7 @@ set(AMNEZIAVPN_TS_FILES
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
||||
|
||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||
|
||||
@@ -96,11 +99,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
|
||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
# -- i18n end
|
||||
|
||||
if(IOS)
|
||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
|
||||
set(IS_CI ${CI})
|
||||
if(IS_CI)
|
||||
message("Detected CI env")
|
||||
@@ -110,8 +108,8 @@ if(IS_CI)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
@@ -120,167 +118,31 @@ include_directories(
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
add_definitions(-DQ_OS_MAC)
|
||||
add_definitions(-DMACOS_NE)
|
||||
message("Add macros for MacOS Network Extension")
|
||||
else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
|
||||
include_directories(mozilla)
|
||||
include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
|
||||
if(NOT IOS)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||
endif()
|
||||
|
||||
if(NOT IOS)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
|
||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
||||
|
||||
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
|
||||
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
|
||||
|
||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
|
||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
|
||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${COMMON_FILES_H}
|
||||
${PAGE_LOGIC_H}
|
||||
${CONFIGURATORS_H}
|
||||
${UI_MODELS_H}
|
||||
${UI_CONTROLLERS_H}
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
||||
)
|
||||
|
||||
set(RESOURCES ${RESOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
user32
|
||||
rasapi32
|
||||
@@ -294,7 +156,7 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
cmake_policy(SET CMP0099 OLD)
|
||||
cmake_policy(SET CMP0099 NEW)
|
||||
cmake_policy(SET CMP0114 NEW)
|
||||
|
||||
if(NOT BUILD_OSX_APP_IDENTIFIER)
|
||||
@@ -313,7 +175,6 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -321,33 +182,8 @@ if(LINUX AND NOT ANDROID)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
@@ -357,7 +193,9 @@ endif()
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
elseif(APPLE AND MACOS_NE)
|
||||
include(cmake/macos_ne.cmake)
|
||||
elseif(APPLE)
|
||||
include(cmake/osxtools.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
@@ -378,7 +216,7 @@ elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
@@ -393,8 +231,22 @@ if(NOT IOS AND NOT ANDROID)
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
qt_finalize_target(${PROJECT})
|
||||
|
||||
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
||||
if(COMMAND qt_import_qml_plugins)
|
||||
qt_import_qml_plugins(${PROJECT})
|
||||
endif()
|
||||
if(COMMAND qt_finalize_executable)
|
||||
qt_finalize_executable(${PROJECT})
|
||||
else()
|
||||
qt_finalize_target(${PROJECT})
|
||||
endif()
|
||||
|
||||
309
client/amneziaApplication.cpp
Normal file
309
client/amneziaApplication.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
#include "amneziaApplication.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QMimeData>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickStyle>
|
||||
#include <QResource>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QEvent>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QWindow>
|
||||
|
||||
#include "core/protocols/qmlRegisterProtocols.h"
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
|
||||
|
||||
bool AmneziaApplication::m_forceQuit = false;
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
|
||||
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
|
||||
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
|
||||
{
|
||||
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Fix config file permissions
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
{
|
||||
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
|
||||
s.setValue("permFixed", true);
|
||||
}
|
||||
|
||||
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||
+ APPLICATION_NAME + ".conf";
|
||||
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
|
||||
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
||||
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
#endif
|
||||
|
||||
m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this);
|
||||
m_nam = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
|
||||
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_vpnConnectionThread.requestInterruption();
|
||||
m_vpnConnectionThread.quit();
|
||||
|
||||
if (!m_vpnConnectionThread.wait(3000)) {
|
||||
m_vpnConnectionThread.terminate();
|
||||
m_vpnConnectionThread.wait(500);
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
delete m_engine;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
namespace {
|
||||
static void clearQtCaches()
|
||||
{
|
||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
if (!cacheRoot.isEmpty()) {
|
||||
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
|
||||
QDir(cacheRoot + "/qmlcache").removeRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void AmneziaApplication::init()
|
||||
{
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[this, url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl) {
|
||||
QCoreApplication::exit(-1);
|
||||
return;
|
||||
}
|
||||
// install filter on main window
|
||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||
win->installEventFilter(this);
|
||||
#ifdef Q_OS_ANDROID
|
||||
QObject::connect(win, &QQuickWindow::sceneGraphError,
|
||||
[](QQuickWindow::SceneGraphError, const QString &msg) {
|
||||
qWarning() << "Scene graph error (suppressed):" << msg;
|
||||
});
|
||||
// Keep graphics context alive across hide/show cycles to avoid
|
||||
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
|
||||
win->setPersistentSceneGraph(true);
|
||||
win->setPersistentGraphics(true);
|
||||
#endif
|
||||
win->show();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||
|
||||
#ifdef MACOS_NE
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
|
||||
#else
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||
#endif
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
|
||||
if (m_parser.isSet(m_optImport)) {
|
||||
const QString data = m_parser.value(m_optImport);
|
||||
if (!data.isEmpty()) {
|
||||
if (m_coreController) {
|
||||
m_coreController->importConfigFromData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_engine->load(url);
|
||||
|
||||
m_coreController->setQmlRoot();
|
||||
|
||||
#ifdef Q_OS_WIN //TODO
|
||||
if (m_parser.isSet(m_optAutostart))
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
else
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
#else
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
#endif
|
||||
|
||||
// Android TextArea clipboard workaround
|
||||
// Text from TextArea always has "text/html" mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||
// Next, html is created for this mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
|
||||
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
|
||||
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
|
||||
// So we catch all the copies to the clipboard and clear them from "text/html"
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
|
||||
auto clipboard = QGuiApplication::clipboard();
|
||||
if (clipboard->mimeData()->hasHtml()) {
|
||||
clipboard->setText(clipboard->text());
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (m_parser.isSet(m_optConnect)) {
|
||||
bool ok = false;
|
||||
int idx = m_parser.value(m_optConnect).toInt(&ok);
|
||||
if (ok) {
|
||||
QTimer::singleShot(0, this, [this, idx]() {
|
||||
if (m_coreController) {
|
||||
m_coreController->openConnectionByIndex(idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaApplication::registerTypes()
|
||||
{
|
||||
qRegisterMetaType<ServerCredentials>("ServerCredentials");
|
||||
|
||||
qRegisterMetaType<DockerContainer>("DockerContainer");
|
||||
using namespace amnezia::ProtocolEnumNS;
|
||||
qRegisterMetaType<TransportProto>("TransportProto");
|
||||
qRegisterMetaType<Proto>("Proto");
|
||||
qRegisterMetaType<ServiceType>("ServiceType");
|
||||
|
||||
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
|
||||
|
||||
m_containerProps.reset(new ContainerProps());
|
||||
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
|
||||
|
||||
m_protocolProps.reset(new ProtocolProps());
|
||||
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
|
||||
|
||||
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
||||
"ContainersModelFilters");
|
||||
|
||||
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||
|
||||
amnezia::declareQmlProtocolEnum();
|
||||
Vpn::declareQmlVpnConnectionStateEnum();
|
||||
PageLoader::declareQmlPageEnum();
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadFonts()
|
||||
{
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||
}
|
||||
|
||||
bool AmneziaApplication::parseCommands()
|
||||
{
|
||||
m_parser.setApplicationDescription(APPLICATION_NAME);
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
m_parser.addOption(m_optAutostart);
|
||||
m_parser.addOption(m_optCleanup);
|
||||
m_parser.addOption(m_optConnect);
|
||||
m_parser.addOption(m_optImport);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(m_optCleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer *server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
}
|
||||
emit m_coreController->pageController()->raiseMainWindow(); //TODO
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Close) {
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
quit();
|
||||
#else
|
||||
if (m_forceQuit) {
|
||||
quit();
|
||||
} else {
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
m_coreController->pageController()->hideMainWindow();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true; // eat the close
|
||||
}
|
||||
// call base QObject::eventFilter
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void AmneziaApplication::forceQuit()
|
||||
{
|
||||
m_forceQuit = true;
|
||||
quit();
|
||||
}
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
QNetworkAccessManager *AmneziaApplication::networkManager()
|
||||
{
|
||||
return m_nam;
|
||||
}
|
||||
|
||||
QClipboard *AmneziaApplication::getClipboard()
|
||||
{
|
||||
return this->clipboard();
|
||||
}
|
||||
78
client/amneziaApplication.h
Normal file
78
client/amneziaApplication.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef AMNEZIA_APPLICATION_H
|
||||
#define AMNEZIA_APPLICATION_H
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <QClipboard>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "secureQSettings.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/models/containerProps.h"
|
||||
#include "ui/models/protocolProps.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AmneziaApplication(int &argc, char *argv[]);
|
||||
virtual ~AmneziaApplication();
|
||||
|
||||
void init();
|
||||
void registerTypes();
|
||||
void loadFonts();
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *networkManager();
|
||||
QClipboard *getClipboard();
|
||||
|
||||
public slots:
|
||||
void forceQuit();
|
||||
|
||||
private:
|
||||
static bool m_forceQuit;
|
||||
QQmlApplicationEngine *m_engine {};
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QScopedPointer<CoreController> m_coreController;
|
||||
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QCommandLineOption m_optAutostart;
|
||||
QCommandLineOption m_optCleanup;
|
||||
QCommandLineOption m_optConnect;
|
||||
QCommandLineOption m_optImport;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
@@ -1,466 +0,0 @@
|
||||
#include "amnezia_application.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QMimeData>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickStyle>
|
||||
#include <QResource>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/installedAppsImageProvider.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#include "protocols/qml_register_protocols.h"
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Fix config file permissions
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
{
|
||||
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
|
||||
s.setValue("permFixed", true);
|
||||
}
|
||||
|
||||
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||
+ APPLICATION_NAME + ".conf";
|
||||
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
|
||||
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
||||
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
#endif
|
||||
|
||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||
m_nam = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
m_vpnConnectionThread.quit();
|
||||
m_vpnConnectionThread.wait(3000);
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
delete m_engine;
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaApplication::init()
|
||||
{
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(m_settings));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
initModels();
|
||||
loadTranslator();
|
||||
initControllers();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!AndroidController::initLogging()) {
|
||||
qFatal("Android logging initialization failed");
|
||||
}
|
||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
|
||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
|
||||
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
|
||||
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||
m_connectionController->onConnectionStateChanged(state);
|
||||
if (m_vpnConnection)
|
||||
m_vpnConnection->restoreConnection();
|
||||
});
|
||||
if (!AndroidController::instance()->initialize()) {
|
||||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
data.clear();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
emit m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
&NotificationHandler::setConnectionState);
|
||||
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
|
||||
bool enabled = m_settings->isSaveLogs();
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (enabled) {
|
||||
if (!Logger::init(false)) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_parser.isSet("a"))
|
||||
m_pageController->showOnStartup();
|
||||
else
|
||||
emit m_pageController->raiseMainWindow();
|
||||
#else
|
||||
m_pageController->showOnStartup();
|
||||
#endif
|
||||
|
||||
// Android TextArea clipboard workaround
|
||||
// Text from TextArea always has "text/html" mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||
// Next, html is created for this mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
|
||||
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
|
||||
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
|
||||
// So we catch all the copies to the clipboard and clear them from "text/html"
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
|
||||
auto clipboard = QGuiApplication::clipboard();
|
||||
if (clipboard->mimeData()->hasHtml()) {
|
||||
clipboard->setText(clipboard->text());
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void AmneziaApplication::registerTypes()
|
||||
{
|
||||
qRegisterMetaType<ServerCredentials>("ServerCredentials");
|
||||
|
||||
qRegisterMetaType<DockerContainer>("DockerContainer");
|
||||
qRegisterMetaType<TransportProto>("TransportProto");
|
||||
qRegisterMetaType<Proto>("Proto");
|
||||
qRegisterMetaType<ServiceType>("ServiceType");
|
||||
|
||||
declareQmlProtocolEnum();
|
||||
declareQmlContainerEnum();
|
||||
|
||||
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
|
||||
|
||||
m_containerProps.reset(new ContainerProps());
|
||||
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
|
||||
|
||||
m_protocolProps.reset(new ProtocolProps());
|
||||
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
|
||||
|
||||
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
||||
"ContainersModelFilters");
|
||||
|
||||
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||
|
||||
Vpn::declareQmlVpnConnectionStateEnum();
|
||||
PageLoader::declareQmlPageEnum();
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadFonts()
|
||||
{
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadTranslator()
|
||||
{
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
void AmneziaApplication::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
}
|
||||
} else {
|
||||
m_settings->setAppLanguage(QLocale::English);
|
||||
}
|
||||
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
}
|
||||
|
||||
bool AmneziaApplication::parseCommands()
|
||||
{
|
||||
m_parser.setApplicationDescription(APPLICATION_NAME);
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
||||
m_parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
||||
m_parser.addOption(c_cleanup);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(c_cleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer* server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket* clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
}
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
void AmneziaApplication::initModels()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||
|
||||
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||
|
||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||
&ContainersModel::updateModel);
|
||||
m_serversModel->resetModel();
|
||||
|
||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||
|
||||
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||
|
||||
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||
|
||||
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||
|
||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||
|
||||
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||
#endif
|
||||
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||
&ServersModel::clearCachedProfile);
|
||||
|
||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||
|
||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
|
||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||
});
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||
}
|
||||
|
||||
void AmneziaApplication::initControllers()
|
||||
{
|
||||
m_connectionController.reset(
|
||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](const QString &errorMessage) {
|
||||
emit m_pageController->showErrorMessage(errorMessage);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](ErrorCode errorCode) {
|
||||
emit m_pageController->showErrorMessage(errorCode);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_focusController.reset(new FocusController(m_engine, this));
|
||||
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
|
||||
m_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||
&InstallController::setEncryptedPassphrase);
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated);
|
||||
|
||||
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||
emit m_connectionController->configFromApiUpdated();
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||
});
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
|
||||
m_settingsController.reset(
|
||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||
}
|
||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
#ifndef AMNEZIA_APPLICATION_H
|
||||
#define AMNEZIA_APPLICATION_H
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "settings.h"
|
||||
#include "vpnconnection.h"
|
||||
|
||||
#include "ui/controllers/connectionController.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "ui/controllers/installController.h"
|
||||
#include "ui/controllers/focusController.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/appSplitTunnelingController.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.h"
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/apiServicesModel.h"
|
||||
#include "ui/models/apiCountryModel.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AmneziaApplication(int &argc, char *argv[]);
|
||||
virtual ~AmneziaApplication();
|
||||
|
||||
void init();
|
||||
void registerTypes();
|
||||
void loadFonts();
|
||||
void loadTranslator();
|
||||
void updateTranslator(const QLocale &locale);
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *manager() { return m_nam; }
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
void initControllers();
|
||||
|
||||
QQmlApplicationEngine *m_engine {};
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
||||
#endif
|
||||
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<FocusController> m_focusController;
|
||||
QScopedPointer<PageController> m_pageController;
|
||||
QScopedPointer<InstallController> m_installController;
|
||||
QScopedPointer<ImportController> m_importController;
|
||||
QScopedPointer<ExportController> m_exportController;
|
||||
QScopedPointer<SettingsController> m_settingsController;
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
@@ -11,7 +11,7 @@
|
||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
<!-- for TV -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="true" />
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
||||
@@ -45,7 +45,8 @@
|
||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -214,4 +215,4 @@
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -111,7 +111,6 @@ dependencies {
|
||||
implementation(project(":wireguard"))
|
||||
implementation(project(":awg"))
|
||||
implementation(project(":openvpn"))
|
||||
implementation(project(":cloak"))
|
||||
implementation(project(":xray"))
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.activity)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
id(libs.plugins.kotlin.android.get().pluginId)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.amnezia.vpn.protocol.cloak"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":utils"))
|
||||
compileOnly(project(":protocolApi"))
|
||||
implementation(project(":openvpn"))
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.amnezia.vpn.protocol.cloak
|
||||
|
||||
import android.util.Base64
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.json.JSONObject
|
||||
|
||||
class Cloak : OpenVpn() {
|
||||
|
||||
override fun internalInit() {
|
||||
super.internalInit()
|
||||
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
|
||||
}
|
||||
|
||||
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
||||
val openVpnConfig = ClientAPI_Config()
|
||||
|
||||
val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config")
|
||||
val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data"))
|
||||
val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT)
|
||||
|
||||
val configStr = "$openVpnConfigStr\n<cloak>\n$cloakConfigStr\n</cloak>\n"
|
||||
|
||||
openVpnConfig.usePluggableTransports = true
|
||||
openVpnConfig.content = configStr
|
||||
return openVpnConfig
|
||||
}
|
||||
|
||||
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
|
||||
cloakConfigJson.put("NumConn", 1)
|
||||
cloakConfigJson.put("ProxyMethod", "openvpn")
|
||||
if (cloakConfigJson.has("port")) {
|
||||
val port = cloakConfigJson["port"]
|
||||
cloakConfigJson.remove("port")
|
||||
cloakConfigJson.put("RemotePort", port)
|
||||
}
|
||||
if (cloakConfigJson.has("remote")) {
|
||||
val remote = cloakConfigJson["remote"]
|
||||
cloakConfigJson.remove("remote")
|
||||
cloakConfigJson.put("RemoteHost", remote)
|
||||
}
|
||||
return cloakConfigJson
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
|
||||
openVpnClient = null
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
openVpnClient?.let {
|
||||
it.establish = makeEstablish(vpnBuilder)
|
||||
it.reconnect(0)
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class Protocol {
|
||||
|
||||
abstract fun stopVpn()
|
||||
|
||||
abstract fun reconnectVpn(vpnBuilder: Builder)
|
||||
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||
|
||||
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
||||
if (!allowSplitTunneling) {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:enforceNavigationBarContrast">false</item>
|
||||
<item name="android:enforceStatusBarContrast">false</item>
|
||||
</style>
|
||||
<style name="Translucent" parent="NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
@@ -35,7 +35,6 @@ include(":protocolApi")
|
||||
include(":wireguard")
|
||||
include(":awg")
|
||||
include(":openvpn")
|
||||
include(":cloak")
|
||||
include(":xray")
|
||||
include(":xray:libXray")
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.os.ParcelFileDescriptor
|
||||
import android.os.SystemClock
|
||||
import android.provider.OpenableColumns
|
||||
import android.provider.Settings
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -35,6 +37,11 @@ import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -68,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
|
||||
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
||||
|
||||
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
||||
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
|
||||
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
|
||||
|
||||
class AmneziaActivity : QtActivity() {
|
||||
|
||||
@@ -84,6 +93,12 @@ class AmneziaActivity : QtActivity() {
|
||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||
|
||||
private var isActivityResumed = false
|
||||
private var hasWindowFocus = false
|
||||
private val resumeHandler = Handler(Looper.getMainLooper())
|
||||
private var pendingOpenFileUri: String? = null
|
||||
private var openFileDeliveryScheduled = false
|
||||
|
||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||
object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
@@ -170,10 +185,9 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
// Configure window for edge-to-edge display
|
||||
configureWindowForEdgeToEdge()
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
@@ -186,11 +200,18 @@ class AmneziaActivity : QtActivity() {
|
||||
doBindService()
|
||||
}
|
||||
)
|
||||
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
|
||||
openFileDeliveryScheduled = false
|
||||
registerBroadcastReceivers()
|
||||
intent?.let(::processIntent)
|
||||
runBlocking { vpnProto = proto.await() }
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
|
||||
}
|
||||
|
||||
private fun loadLibs() {
|
||||
listOf(
|
||||
"rsapss",
|
||||
@@ -256,6 +277,11 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
isActivityResumed = false
|
||||
hasWindowFocus = false
|
||||
// Cancel all pending operations when activity stops
|
||||
resumeHandler.removeCallbacksAndMessages(null)
|
||||
openFileDeliveryScheduled = false
|
||||
Log.d(TAG, "Stop Amnezia activity")
|
||||
doUnbindService()
|
||||
mainScope.launch {
|
||||
@@ -265,7 +291,197 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
hasWindowFocus = hasFocus
|
||||
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
||||
|
||||
if (!hasFocus) {
|
||||
// Cancel pending operations if window loses focus
|
||||
resumeHandler.removeCallbacksAndMessages(null)
|
||||
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.decorView.apply {
|
||||
invalidate()
|
||||
resumeHandler.postDelayed({
|
||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||
sendTouch(1f, 1f)
|
||||
}
|
||||
}, 50)
|
||||
resumeHandler.postDelayed({
|
||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||
sendTouch(2f, 2f)
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
}, 150)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
val keyCode = event.keyCode
|
||||
val pressed = event.action == KeyEvent.ACTION_DOWN
|
||||
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
||||
nativeGamepadKeyEvent(0, keyCode, pressed)
|
||||
return true
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
||||
val synthetic = KeyEvent(
|
||||
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
||||
event.repeatCount, event.metaState, -1, event.scanCode,
|
||||
event.flags, InputDevice.SOURCE_KEYBOARD
|
||||
)
|
||||
return super.dispatchKeyEvent(synthetic)
|
||||
}
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
|
||||
|
||||
override fun onPause() {
|
||||
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
|
||||
// Using a coroutine here would be too late — the surface is gone by the time
|
||||
// the coroutine runs. A direct synchronous call gives Qt's render thread the
|
||||
// best chance to process visible=false before surface destruction.
|
||||
if (qtInitialized.isCompleted) {
|
||||
QtAndroidController.onActivityPaused()
|
||||
}
|
||||
super.onPause()
|
||||
isActivityResumed = false
|
||||
// Cancel all pending operations when activity pauses
|
||||
resumeHandler.removeCallbacksAndMessages(null)
|
||||
openFileDeliveryScheduled = false
|
||||
Log.d(TAG, "Pause Amnezia activity")
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
isActivityResumed = true
|
||||
Log.d(TAG, "Resume Amnezia activity")
|
||||
if (qtInitialized.isCompleted) {
|
||||
QtAndroidController.onActivityResumed()
|
||||
}
|
||||
|
||||
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
|
||||
val uri = pendingOpenFileUri!!
|
||||
openFileDeliveryScheduled = true
|
||||
resumeHandler.postDelayed({
|
||||
if (!isFinishing && !isDestroyed) {
|
||||
pendingOpenFileUri = null
|
||||
openFileDeliveryScheduled = false
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
}
|
||||
}
|
||||
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.decorView.apply {
|
||||
invalidate()
|
||||
|
||||
resumeHandler.postDelayed({
|
||||
// Check if activity is still resumed and has focus before executing
|
||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||
sendTouch(1f, 1f)
|
||||
}
|
||||
}, 100)
|
||||
|
||||
resumeHandler.postDelayed({
|
||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||
sendTouch(2f, 2f)
|
||||
}
|
||||
}, 200)
|
||||
|
||||
resumeHandler.postDelayed({
|
||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureWindowForEdgeToEdge() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
|
||||
// Workaround for Android 14 (API 34+) IME adjustResize bug
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
setupImeInsetsListener()
|
||||
}
|
||||
} else {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupImeInsetsListener() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
||||
|
||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val imeHeightDp = (imeHeight / density).toInt()
|
||||
|
||||
// Also track system bars (navigation bar, status bar) changes
|
||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val navBarHeight = systemBarsInsets.bottom
|
||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
||||
val statusBarHeight = systemBarsInsets.top
|
||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
||||
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
||||
}
|
||||
|
||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
isActivityResumed = false
|
||||
hasWindowFocus = false
|
||||
// Cancel all pending operations when activity is destroyed
|
||||
resumeHandler.removeCallbacksAndMessages(null)
|
||||
Log.d(TAG, "Destroy Amnezia activity")
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
notificationStateReceiver = null
|
||||
@@ -591,9 +807,13 @@ class AmneziaActivity : QtActivity() {
|
||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}?.toString() ?: ""
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
if (uri.isNotEmpty()) {
|
||||
pendingOpenFileUri = uri
|
||||
} else {
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
@@ -622,7 +842,7 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun getFd(fileName: String): Int {
|
||||
Log.v(TAG, "Get fd for $fileName")
|
||||
return blockingCall {
|
||||
return blockingCall(Dispatchers.IO) {
|
||||
try {
|
||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||
pfd?.fd ?: -1
|
||||
@@ -666,6 +886,43 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
@Suppress("unused")
|
||||
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
|
||||
@Suppress("unused")
|
||||
fun getStatusBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getNavigationBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
|
||||
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
protocolState.value = RECONNECTING
|
||||
|
||||
connectionJob = connectionScope.launch {
|
||||
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ object AppListProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
|
||||
val name: String?
|
||||
val packageName: String = pi.packageName
|
||||
val icon: Boolean = ai.icon != 0
|
||||
val icon: Boolean = (ai?.icon ?: 0) != 0
|
||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||
|
||||
init {
|
||||
val name = ai.loadLabel(pm).toString()
|
||||
this.name = if (name != packageName) name else null
|
||||
val name = ai?.loadLabel(pm)?.toString()
|
||||
this.name = name?.takeIf { it != packageName }
|
||||
}
|
||||
|
||||
override fun compareTo(other: App): Int {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -11,8 +14,29 @@ private const val TAG = "TvFilePicker"
|
||||
|
||||
class TvFilePicker : ComponentActivity() {
|
||||
|
||||
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
setResult(RESULT_OK, Intent().apply { data = it })
|
||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
|
||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
}
|
||||
if (activitiesToResolveIntent.all {
|
||||
val name = it.activityInfo.packageName
|
||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||
}) {
|
||||
throw ActivityNotFoundException()
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}) {
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = it
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -31,7 +55,7 @@ class TvFilePicker : ComponentActivity() {
|
||||
private fun getFile() {
|
||||
try {
|
||||
Log.v(TAG, "getFile")
|
||||
fileChooseResultLauncher.launch("*/*")
|
||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Log.w(TAG, "Activity not found")
|
||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.amnezia.vpn
|
||||
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.protocol.xray.Xray
|
||||
@@ -36,14 +35,6 @@ enum class VpnProto(
|
||||
override fun createProtocol(): Protocol = OpenVpn()
|
||||
},
|
||||
|
||||
CLOAK(
|
||||
"Cloak",
|
||||
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||
OpenVpnService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Cloak()
|
||||
},
|
||||
|
||||
XRAY(
|
||||
"XRay",
|
||||
"org.amnezia.vpn:amneziaXrayService",
|
||||
@@ -72,4 +63,4 @@ enum class VpnProto(
|
||||
companion object {
|
||||
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,10 @@ object QtAndroidController {
|
||||
external fun onAuthResult(result: Boolean)
|
||||
|
||||
external fun decodeQrCode(data: String): Boolean
|
||||
|
||||
external fun onImeInsetsChanged(heightDp: Int)
|
||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
||||
|
||||
external fun onActivityPaused()
|
||||
external fun onActivityResumed()
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileLock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.amnezia.vpn.util.Log.Priority.D
|
||||
import org.amnezia.vpn.util.Log.Priority.E
|
||||
@@ -135,8 +137,8 @@ object Log {
|
||||
}
|
||||
|
||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
|
||||
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
"$tag: $msg\n"
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnException
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.amnezia.vpn.util.Log
|
||||
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
|
||||
open class Wireguard : Protocol() {
|
||||
|
||||
private var tunnelHandle: Int = -1
|
||||
private var config: WireguardConfig? = null // save config for reconnect
|
||||
protected open val ifName: String = "amn0"
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var statusJob: Job? = null
|
||||
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val wireguardConfig = parseConfig(config)
|
||||
start(wireguardConfig, vpnBuilder, protect)
|
||||
this.config = wireguardConfig
|
||||
}
|
||||
|
||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
@@ -120,14 +123,26 @@ open class Wireguard : Protocol() {
|
||||
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
|
||||
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
|
||||
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
|
||||
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
|
||||
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||
}
|
||||
|
||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
if (tunnelHandle != -1) {
|
||||
private fun start(
|
||||
config: WireguardConfig,
|
||||
vpnBuilder: Builder,
|
||||
protect: (Int) -> Boolean,
|
||||
stopExistingVpn: Boolean = false
|
||||
) {
|
||||
if (!stopExistingVpn && tunnelHandle != -1) {
|
||||
Log.w(TAG, "Tunnel already up")
|
||||
return
|
||||
}
|
||||
@@ -135,6 +150,9 @@ open class Wireguard : Protocol() {
|
||||
buildVpnInterface(config, vpnBuilder)
|
||||
|
||||
vpnBuilder.establish().use { tunFd ->
|
||||
if (stopExistingVpn && tunnelHandle != -1) {
|
||||
turnOffVpn()
|
||||
}
|
||||
if (tunFd == null) {
|
||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||
}
|
||||
@@ -191,20 +209,25 @@ open class Wireguard : Protocol() {
|
||||
return lastHandshake
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
private fun turnOffVpn() {
|
||||
statusJob?.cancel()
|
||||
statusJob = null
|
||||
val handleToClose = tunnelHandle
|
||||
tunnelHandle = -1
|
||||
GoBackend.awgTurnOff(handleToClose)
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
turnOffVpn()
|
||||
state.value = DISCONNECTED
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
state.value = CONNECTED
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val config = this.config ?: throw VpnException("Reconnect config is empty")
|
||||
start(config, vpnBuilder, protect, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,17 @@ open class WireguardConfig protected constructor(
|
||||
val jmax: Int?,
|
||||
val s1: Int?,
|
||||
val s2: Int?,
|
||||
val h1: Long?,
|
||||
val h2: Long?,
|
||||
val h3: Long?,
|
||||
val h4: Long?
|
||||
val s3: Int?,
|
||||
val s4: Int?,
|
||||
val h1: String?,
|
||||
val h2: String?,
|
||||
val h3: String?,
|
||||
val h4: String?,
|
||||
var i1: String?,
|
||||
var i2: String?,
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -39,10 +46,17 @@ open class WireguardConfig protected constructor(
|
||||
builder.jmax,
|
||||
builder.s1,
|
||||
builder.s2,
|
||||
builder.s3,
|
||||
builder.s4,
|
||||
builder.h1,
|
||||
builder.h2,
|
||||
builder.h3,
|
||||
builder.h4
|
||||
builder.h4,
|
||||
builder.i1,
|
||||
builder.i2,
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -61,10 +75,17 @@ open class WireguardConfig protected constructor(
|
||||
appendLine("jmax=$jmax")
|
||||
appendLine("s1=$s1")
|
||||
appendLine("s2=$s2")
|
||||
s3?.let { appendLine("s3=$it") }
|
||||
s4?.let { appendLine("s4=$it") }
|
||||
appendLine("h1=$h1")
|
||||
appendLine("h2=$h2")
|
||||
appendLine("h3=$h3")
|
||||
appendLine("h4=$h4")
|
||||
i1?.let { appendLine("i1=$it") }
|
||||
i2?.let { appendLine("i2=$it") }
|
||||
i3?.let { appendLine("i3=$it") }
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +138,17 @@ open class WireguardConfig protected constructor(
|
||||
internal var jmax: Int? = null
|
||||
internal var s1: Int? = null
|
||||
internal var s2: Int? = null
|
||||
internal var h1: Long? = null
|
||||
internal var h2: Long? = null
|
||||
internal var h3: Long? = null
|
||||
internal var h4: Long? = null
|
||||
internal var s3: Int? = null
|
||||
internal var s4: Int? = null
|
||||
internal var h1: String? = null
|
||||
internal var h2: String? = null
|
||||
internal var h3: String? = null
|
||||
internal var h4: String? = null
|
||||
internal var i1: String? = null
|
||||
internal var i2: String? = null
|
||||
internal var i3: String? = null
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
@@ -139,10 +167,17 @@ open class WireguardConfig protected constructor(
|
||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||
fun setH1(h1: String) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: String) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: String) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: String) = apply { this.h4 = h4 }
|
||||
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.ServerSocket
|
||||
import java.util.UUID
|
||||
import go.Seq
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
@@ -19,11 +22,32 @@ import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.ip
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
private const val TAG = "Xray"
|
||||
private const val LIBXRAY_TAG = "libXray"
|
||||
|
||||
private fun findSocksInboundIndex(inbounds: JSONArray): Int {
|
||||
for (i in 0 until inbounds.length()) {
|
||||
val o = inbounds.optJSONObject(i) ?: continue
|
||||
if (o.optString("protocol").equals("socks", ignoreCase = true)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun acquireFreeLocalPort(): Int {
|
||||
try {
|
||||
ServerSocket(0, 1, InetAddress.getByName("127.0.0.1")).use { return it.localPort }
|
||||
} catch (e: Exception) {
|
||||
throw VpnStartException(
|
||||
"Failed to acquire free TCP port on 127.0.0.1 for SOCKS inbound: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Xray : Protocol() {
|
||||
|
||||
private var isRunning: Boolean = false
|
||||
@@ -53,9 +77,13 @@ class Xray : Protocol() {
|
||||
return
|
||||
}
|
||||
|
||||
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
||||
val xrayConfigData = config.optJSONObject("xray_config_data")
|
||||
?: config.optJSONObject("ssxray_config_data")
|
||||
?: throw BadConfigException("config_data not found")
|
||||
val xrayJsonConfig = JSONObject(xrayConfigData.optString("config"))
|
||||
|
||||
// Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present.
|
||||
ensureInboundAuth(xrayJsonConfig)
|
||||
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
||||
|
||||
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
||||
@@ -97,9 +125,22 @@ class Xray : Protocol() {
|
||||
if (it.isNotBlank()) setMtu(it.toInt())
|
||||
}
|
||||
|
||||
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject
|
||||
val inbounds = xrayJsonConfig.getJSONArray("inbounds")
|
||||
val socksIdx = findSocksInboundIndex(inbounds)
|
||||
if (socksIdx < 0) {
|
||||
throw BadConfigException("socks inbound not found")
|
||||
}
|
||||
val socksConfig = inbounds.getJSONObject(socksIdx)
|
||||
socksConfig.getInt("port").let { setSocksPort(it) }
|
||||
|
||||
val socksSettings = socksConfig.optJSONObject("settings")
|
||||
val accounts = socksSettings?.optJSONArray("accounts")
|
||||
if (accounts != null && accounts.length() > 0) {
|
||||
val account = accounts.getJSONObject(0)
|
||||
setSocksUser(account.optString("user"))
|
||||
setSocksPass(account.optString("pass"))
|
||||
}
|
||||
|
||||
configSplitTunneling(config)
|
||||
configAppSplitTunneling(config)
|
||||
}
|
||||
@@ -157,22 +198,54 @@ class Xray : Protocol() {
|
||||
state.value = DISCONNECTED
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
||||
val proxyUrl = "socks5://${config.socksUser}:${config.socksPass}@127.0.0.1:${config.socksPort}"
|
||||
val tun2SocksConfig = Tun2SocksConfig().apply {
|
||||
mtu = config.mtu.toLong()
|
||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||
proxy = proxyUrl
|
||||
device = "fd://$fd"
|
||||
logLevel = "warning"
|
||||
logLevel = "warn"
|
||||
}
|
||||
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||
throw VpnStartException("Failed to start tun2socks: $err")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures SOCKS5 auth is present on the socks inbound settings.
|
||||
// Re-uses existing credentials if already configured; otherwise generates random ones.
|
||||
private fun ensureInboundAuth(xrayConfig: JSONObject) {
|
||||
val inbounds = xrayConfig.optJSONArray("inbounds") ?: return
|
||||
val socksIdx = findSocksInboundIndex(inbounds)
|
||||
if (socksIdx < 0) return
|
||||
|
||||
val inbound = inbounds.getJSONObject(socksIdx)
|
||||
inbound.put("port", acquireFreeLocalPort())
|
||||
val settings = inbound.optJSONObject("settings") ?: JSONObject().also { inbound.put("settings", it) }
|
||||
val accounts = settings.optJSONArray("accounts")
|
||||
if (accounts != null && accounts.length() > 0) {
|
||||
val account = accounts.getJSONObject(0)
|
||||
if (account.optString("user").isNotEmpty() && account.optString("pass").isNotEmpty()) {
|
||||
// Ensure auth mode is enforced even for imported configs that had accounts
|
||||
// but auth: "noauth" (or no auth field).
|
||||
settings.put("auth", "password")
|
||||
inbound.put("settings", settings)
|
||||
inbounds.put(socksIdx, inbound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val user = UUID.randomUUID().toString().replace("-", "").substring(0, 16)
|
||||
val pass = UUID.randomUUID().toString().replace("-", "")
|
||||
settings.put("auth", "password")
|
||||
settings.put("accounts", JSONArray().put(JSONObject().put("user", user).put("pass", pass)))
|
||||
inbound.put("settings", settings)
|
||||
inbounds.put(socksIdx, inbound)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val instance: Xray by lazy { Xray() }
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
|
||||
class XrayConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val socksPort: Int,
|
||||
val socksUser: String,
|
||||
val socksPass: String,
|
||||
val maxMemory: Long,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
builder,
|
||||
builder.socksPort,
|
||||
builder.socksUser,
|
||||
builder.socksPass,
|
||||
builder.maxMemory
|
||||
)
|
||||
|
||||
@@ -22,6 +26,12 @@ class XrayConfig protected constructor(
|
||||
internal var socksPort: Int = 0
|
||||
private set
|
||||
|
||||
internal var socksUser: String = ""
|
||||
private set
|
||||
|
||||
internal var socksPass: String = ""
|
||||
private set
|
||||
|
||||
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
||||
private set
|
||||
|
||||
@@ -29,6 +39,10 @@ class XrayConfig protected constructor(
|
||||
|
||||
fun setSocksPort(port: Int) = apply { socksPort = port }
|
||||
|
||||
fun setSocksUser(user: String) = apply { socksUser = user }
|
||||
|
||||
fun setSocksPass(pass: String) = apply { socksPass = pass }
|
||||
|
||||
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
|
||||
|
||||
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
||||
|
||||
@@ -27,12 +27,18 @@ if(WIN32)
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
endif()
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(IOS)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||
@@ -56,7 +62,7 @@ elseif(LINUX)
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
||||
endif()
|
||||
|
||||
|
||||
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
||||
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
||||
|
||||
@@ -77,6 +83,26 @@ add_compile_definitions(_WINSOCKAPI_)
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||
|
||||
if(ANDROID)
|
||||
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
||||
# Only if Qt6CorePrivate is available (required by qtgamepad)
|
||||
find_package(Qt6CorePrivate CONFIG QUIET)
|
||||
if(Qt6CorePrivate_FOUND)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
|
||||
# Link both the C++ module and QML plugin
|
||||
if(TARGET GamepadLegacy)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
||||
endif()
|
||||
if(TARGET GamepadLegacyQuickPrivate)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
||||
endif()
|
||||
message(STATUS "Gamepad support enabled for Android")
|
||||
else()
|
||||
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
include_directories(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||
|
||||
set(APP_ANDROID_MIN_SDK 26)
|
||||
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)
|
||||
|
||||
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
|
||||
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||
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
|
||||
)
|
||||
|
||||
@@ -20,22 +20,26 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
|
||||
|
||||
# We need to include qtprivate api's
|
||||
# As QAndroidBinder is not yet implemented with a public api
|
||||
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
||||
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
|
||||
if(TARGET Qt6::CorePrivate)
|
||||
set(LIBS ${LIBS} Qt6::CorePrivate)
|
||||
endif()
|
||||
set(LIBS ${LIBS} -ljnigraphics)
|
||||
|
||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.cpp
|
||||
)
|
||||
|
||||
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||
|
||||
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
@@ -46,6 +47,8 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
|
||||
@@ -76,12 +79,22 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
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 "match AppStore org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
|
||||
)
|
||||
|
||||
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 "distr ios.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
@@ -108,6 +121,7 @@ target_sources(${PROJECT} PRIVATE
|
||||
${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
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
@@ -126,9 +140,9 @@ add_subdirectory(ios/networkextension)
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||
|
||||
|
||||
@@ -14,23 +14,29 @@ set(LIBS ${LIBS}
|
||||
${FW_SECURITY}
|
||||
${FW_COREWLAN}
|
||||
${FW_NETWORK}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_USER_NOTIFICATIONS}
|
||||
${FW_NETWORK_EXTENSION}
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
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)
|
||||
@@ -49,4 +55,3 @@ execute_process(
|
||||
)
|
||||
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
|
||||
|
||||
|
||||
|
||||
171
client/cmake/macos_ne.cmake
Normal file
171
client/cmake/macos_ne.cmake
Normal file
@@ -0,0 +1,171 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||
# Link Qt Widgets for QWidget, QMenu, QAction etc.
|
||||
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
|
||||
|
||||
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||
find_library(FW_AVFOUNDATION AVFoundation)
|
||||
find_library(FW_FOUNDATION Foundation)
|
||||
find_library(FW_STOREKIT StoreKit)
|
||||
find_library(FW_SERVICEMGMT ServiceManagement)
|
||||
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||
find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${FW_AUTHENTICATIONSERVICES}
|
||||
${FW_AVFOUNDATION}
|
||||
${FW_FOUNDATION}
|
||||
${FW_STOREKIT}
|
||||
${FW_SERVICEMGMT}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_NETWORKEXTENSION}
|
||||
)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE
|
||||
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
||||
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||
)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(macos/networkextension)
|
||||
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Signing OpenVPNAdapter framework"
|
||||
)
|
||||
289
client/cmake/sources.cmake
Normal file
289
client/cmake/sources.cmake
Normal file
@@ -0,0 +1,289 @@
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/migrations.h
|
||||
${CLIENT_ROOT_DIR}/../ipc/ipc.h
|
||||
${CLIENT_ROOT_DIR}/amneziaApplication.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/errorCodes.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/routeModes.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/commonStructs.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/containerEnum.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/protocolEnum.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/configKeys.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/errorStrings.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/installerBase.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/connectionController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/settingsController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.h
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.h
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/pages.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||
)
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/migrations.cpp
|
||||
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/errorStrings.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/installerBase.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
|
||||
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/connectionController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/settingsController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/ssd.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vless.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/trojan.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Include native macOS platform helpers (dock/status-item)
|
||||
if(APPLE AND NOT IOS)
|
||||
list(APPEND HEADERS
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.h
|
||||
)
|
||||
list(APPEND SOURCES
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
|
||||
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(COMMON_FILES_H
|
||||
${CLIENT_ROOT_DIR}/amneziaApplication.h
|
||||
${CLIENT_ROOT_DIR}/secureQSettings.h
|
||||
${CLIENT_ROOT_DIR}/vpnConnection.h
|
||||
)
|
||||
|
||||
set(COMMON_FILES_CPP
|
||||
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
|
||||
${CLIENT_ROOT_DIR}/secureQSettings.cpp
|
||||
${CLIENT_ROOT_DIR}/vpnConnection.cpp
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
|
||||
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
|
||||
|
||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.h)
|
||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.cpp)
|
||||
|
||||
file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h)
|
||||
file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp)
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.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/api/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.h
|
||||
)
|
||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.cpp
|
||||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${COMMON_FILES_H}
|
||||
${PAGE_LOGIC_H}
|
||||
${CONFIGURATORS_H}
|
||||
${CORE_MODELS_H}
|
||||
${UI_MODELS_H}
|
||||
${UI_CONTROLLERS_H}
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${CORE_MODELS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.cpp
|
||||
)
|
||||
|
||||
set(RESOURCES ${RESOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/ipcClient.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/ipcClient.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE AND MACOS_NE)
|
||||
# Include only the tray notification handler in NE builds
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
|
||||
)
|
||||
endif()
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "awg_configurator.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: WireguardConfigurator(settings, serverController, true, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||
|
||||
QMap<QString, QString> configMap;
|
||||
auto configLines = awgConfig.split("\n");
|
||||
for (auto &line : configLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount);
|
||||
jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize);
|
||||
jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize);
|
||||
jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize);
|
||||
jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize);
|
||||
jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader);
|
||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
jsonConfig[config_key::mtu] =
|
||||
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||
|
||||
return QJsonDocument(jsonConfig).toJson();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef AWGCONFIGURATOR_H
|
||||
#define AWGCONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "wireguard_configurator.h"
|
||||
|
||||
class AwgConfigurator : public WireguardConfigurator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // AWGCONFIGURATOR_H
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "cloak_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
|
||||
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
QString cloakPublicKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
|
||||
cloakPublicKey.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QString cloakBypassUid =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
|
||||
cloakBypassUid.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config.insert("Transport", "direct");
|
||||
config.insert("ProxyMethod", "openvpn");
|
||||
config.insert("EncryptionMethod", "aes-gcm");
|
||||
config.insert("UID", cloakBypassUid);
|
||||
config.insert("PublicKey", cloakPublicKey);
|
||||
config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS");
|
||||
config.insert("NumConn", 1);
|
||||
config.insert("BrowserSig", "chrome");
|
||||
config.insert("StreamTimeout", 300);
|
||||
config.insert("RemoteHost", credentials.hostName);
|
||||
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
|
||||
|
||||
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
return textCfg;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef CLOAK_CONFIGURATOR_H
|
||||
#define CLOAK_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurator_base.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class CloakConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // CLOAK_CONFIGURATOR_H
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "configurator_base.h"
|
||||
|
||||
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: QObject { parent }, m_settings(settings), m_serverController(serverController)
|
||||
{
|
||||
}
|
||||
|
||||
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
return protocolConfigString;
|
||||
}
|
||||
|
||||
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
return protocolConfigString;
|
||||
}
|
||||
|
||||
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
|
||||
{
|
||||
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
|
||||
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef CONFIGURATORBASE_H
|
||||
#define CONFIGURATORBASE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "settings.h"
|
||||
|
||||
class ConfiguratorBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode) = 0;
|
||||
|
||||
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
|
||||
protected:
|
||||
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<ServerController> m_serverController;
|
||||
|
||||
};
|
||||
|
||||
#endif // CONFIGURATORBASE_H
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef IKEV2_CONFIGURATOR_H
|
||||
#define IKEV2_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class Ikev2Configurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData {
|
||||
QByteArray clientCert; // p12 client cert
|
||||
QByteArray caCert; // p12 server cert
|
||||
QString clientId;
|
||||
QString password; // certificate password
|
||||
QString host; // host ip
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
QString genIkev2Config(const ConnectionData &connData);
|
||||
QString genMobileConfig(const ConnectionData &connData);
|
||||
QString genStrongSwanConfig(const ConnectionData &connData);
|
||||
|
||||
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // IKEV2_CONFIGURATOR_H
|
||||
@@ -1,43 +0,0 @@
|
||||
#ifndef OPENVPN_CONFIGURATOR_H
|
||||
#define OPENVPN_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class OpenVpnConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
QString clientId;
|
||||
QString request; // certificate request
|
||||
QString privKey; // client private key
|
||||
QString clientCert; // client signed certificate
|
||||
QString caCert; // server certificate
|
||||
QString taKey; // tls-auth key
|
||||
QString host; // host ip
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
|
||||
static ConnectionData createCertRequest();
|
||||
|
||||
private:
|
||||
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
ErrorCode &errorCode);
|
||||
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
|
||||
};
|
||||
|
||||
#endif // OPENVPN_CONFIGURATOR_H
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "shadowsocks_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
|
||||
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
QString ssKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
|
||||
ssKey.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config.insert("server", credentials.hostName);
|
||||
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
|
||||
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
|
||||
config.insert("password", ssKey);
|
||||
config.insert("timeout", 60);
|
||||
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
||||
|
||||
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
// qDebug().noquote() << textCfg;
|
||||
return textCfg;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef SHADOWSOCKS_CONFIGURATOR_H
|
||||
#define SHADOWSOCKS_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class ShadowSocksConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "ssh_configurator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTemporaryFile>
|
||||
#include <QThread>
|
||||
#include <qtimer.h>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "core/server_defs.h"
|
||||
#include "utilities.h"
|
||||
|
||||
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
QTemporaryFile tmp;
|
||||
#ifdef QT_DEBUG
|
||||
tmp.setAutoRemove(false);
|
||||
#endif
|
||||
tmp.open();
|
||||
tmp.write(key.toUtf8());
|
||||
tmp.close();
|
||||
|
||||
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProcessEnvironment(prepareEnv());
|
||||
p.setProgram("cmd.exe");
|
||||
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
|
||||
#else
|
||||
p.setProgram("ssh-keygen");
|
||||
p.setArguments(QStringList() << "-p"
|
||||
<< "-P"
|
||||
<< ""
|
||||
<< "-N"
|
||||
<< ""
|
||||
<< "-m"
|
||||
<< "pem"
|
||||
<< "-f" << tmp.fileName());
|
||||
#endif
|
||||
|
||||
p.start();
|
||||
p.waitForFinished();
|
||||
|
||||
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
|
||||
|
||||
tmp.open();
|
||||
|
||||
return tmp.readAll();
|
||||
#else
|
||||
return key;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
QProcess *p = new QProcess();
|
||||
p->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
p->setProcessEnvironment(prepareEnv());
|
||||
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe");
|
||||
|
||||
if (credentials.secretData.contains("PRIVATE KEY")) {
|
||||
// todo: connect by key
|
||||
// p->setNativeArguments(QString("%1@%2")
|
||||
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||
} else {
|
||||
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||
}
|
||||
#else
|
||||
p->setProgram("/bin/bash");
|
||||
#endif
|
||||
|
||||
p->startDetached();
|
||||
#endif
|
||||
}
|
||||
|
||||
QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
{
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
QString pathEnvVar = env.value("PATH");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#elif defined(Q_OS_MACX)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
env.insert("PATH", pathEnvVar);
|
||||
// qDebug().noquote() << "ENV PATH" << pathEnvVar;
|
||||
return env;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef SSH_CONFIGURATOR_H
|
||||
#define SSH_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class SshConfigurator : ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QProcessEnvironment prepareEnv();
|
||||
QString convertOpenSShKey(const QString &key);
|
||||
void openSshTerminal(const ServerCredentials &credentials);
|
||||
|
||||
};
|
||||
|
||||
#endif // SSH_CONFIGURATOR_H
|
||||
@@ -1,225 +0,0 @@
|
||||
#include "wireguard_configurator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
bool isAwg, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||
{
|
||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||
|
||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
{
|
||||
// TODO review
|
||||
constexpr size_t EDDSA_KEY_LENGTH = 32;
|
||||
|
||||
ConnectionData connData;
|
||||
|
||||
unsigned char buff[EDDSA_KEY_LENGTH];
|
||||
int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH);
|
||||
if (ret <= 0)
|
||||
return connData;
|
||||
|
||||
EVP_PKEY *pKey = EVP_PKEY_new();
|
||||
q_check_ptr(pKey);
|
||||
pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH);
|
||||
|
||||
size_t keySize = EDDSA_KEY_LENGTH;
|
||||
|
||||
// save private key
|
||||
unsigned char priv[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_private_key(pKey, priv, &keySize);
|
||||
connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64();
|
||||
|
||||
// save public key
|
||||
unsigned char pub[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_public_key(pKey, pub, &keySize);
|
||||
connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64();
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
||||
|
||||
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Get list of already created clients (only IP addresses)
|
||||
QString nextIpNumber;
|
||||
{
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
stdOut.replace("AllowedIPs = ", "");
|
||||
stdOut.replace("/32", "");
|
||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||
|
||||
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
||||
// and added there more IPs for route his itnernal networks, like:
|
||||
// ...
|
||||
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
||||
// ...
|
||||
// without this code - next IP would be 1 if last item in 'ips' has format above
|
||||
QStringList vpnIps;
|
||||
for (const auto &ip : ips) {
|
||||
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
|
||||
}
|
||||
ips = vpnIps;
|
||||
|
||||
// Calc next IP address
|
||||
if (ips.isEmpty()) {
|
||||
nextIpNumber = "2";
|
||||
} else {
|
||||
int next = ips.last().split(".").last().toInt() + 1;
|
||||
if (next > 254) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
nextIpNumber = QString::number(next);
|
||||
}
|
||||
}
|
||||
|
||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
{
|
||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||
if (l.isEmpty()) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
l.removeLast();
|
||||
l.append(nextIpNumber);
|
||||
|
||||
connData.clientIP = l.join(".");
|
||||
}
|
||||
|
||||
// Get keys
|
||||
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||
connData.serverPubKey.replace("\n", "");
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
|
||||
connData.pskKey.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Add client to config
|
||||
QString configPart = QString("[Peer]\n"
|
||||
"PublicKey = %1\n"
|
||||
"PresharedKey = %2\n"
|
||||
"AllowedIPs = %3/32\n\n")
|
||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||
|
||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
||||
|
||||
errorCode = m_serverController->runScript(
|
||||
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config =
|
||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
}
|
||||
|
||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
|
||||
config.replace("$WIREGUARD_CLIENT_IP", connData.clientIP);
|
||||
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
|
||||
config.replace("$WIREGUARD_PSK", connData.pskKey);
|
||||
|
||||
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
||||
QJsonObject jConfig;
|
||||
jConfig[config_key::config] = config;
|
||||
|
||||
jConfig[config_key::hostName] = connData.host;
|
||||
jConfig[config_key::port] = connData.port.toInt();
|
||||
jConfig[config_key::client_priv_key] = connData.clientPrivKey;
|
||||
jConfig[config_key::client_ip] = connData.clientIP;
|
||||
jConfig[config_key::client_pub_key] = connData.clientPubKey;
|
||||
jConfig[config_key::psk_key] = connData.pskKey;
|
||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
||||
|
||||
jConfig[config_key::persistent_keep_alive] = "25";
|
||||
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
|
||||
jConfig[config_key::allowed_ips] = allowedIps;
|
||||
|
||||
jConfig[config_key::clientId] = connData.clientPubKey;
|
||||
|
||||
return QJsonDocument(jConfig).toJson();
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
return protocolConfigString;
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
return protocolConfigString;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||
#define WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
|
||||
class WireguardConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
QString clientPrivKey; // client private key
|
||||
QString clientPubKey; // client public key
|
||||
QString clientIP; // internal client IP address
|
||||
QString serverPubKey; // tls-auth key
|
||||
QString pskKey; // preshared key
|
||||
QString host; // host ip
|
||||
QString port;
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
|
||||
static ConnectionData genClientKeys();
|
||||
|
||||
private:
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
bool m_isAwg;
|
||||
QString m_serverConfigPath;
|
||||
QString m_serverPublicKeyPath;
|
||||
QString m_serverPskKeyPath;
|
||||
amnezia::ProtocolScriptType m_configTemplate;
|
||||
QString m_protocolName;
|
||||
QString m_defaultPort;
|
||||
};
|
||||
|
||||
#endif // WIREGUARD_CONFIGURATOR_H
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef XRAY_CONFIGURATOR_H
|
||||
#define XRAY_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class XrayConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
@@ -1,407 +0,0 @@
|
||||
#include "containers_defs.h"
|
||||
|
||||
#include "QJsonObject"
|
||||
#include "QJsonDocument"
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << ContainerProps::containerToString(c);
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
amnezia::DockerContainer ContainerProps::containerFromString(const QString &container)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
for (int i = 0; i < metaEnum.keyCount(); ++i) {
|
||||
DockerContainer c = static_cast<DockerContainer>(i);
|
||||
if (container == containerToString(c))
|
||||
return c;
|
||||
}
|
||||
return DockerContainer::None;
|
||||
}
|
||||
|
||||
QString ContainerProps::containerToString(amnezia::DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
|
||||
return "amnezia-" + containerKey.toLower();
|
||||
}
|
||||
|
||||
QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Ipsec)
|
||||
return "ikev2";
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
|
||||
return containerKey.toLower();
|
||||
}
|
||||
|
||||
QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::None: return {};
|
||||
|
||||
case DockerContainer::OpenVpn: return { Proto::OpenVpn };
|
||||
|
||||
case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks };
|
||||
|
||||
case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak };
|
||||
|
||||
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
||||
|
||||
case DockerContainer::Xray: return { Proto::Xray };
|
||||
|
||||
case DockerContainer::SSXray: return { Proto::SSXray };
|
||||
|
||||
case DockerContainer::Dns: return { Proto::Dns };
|
||||
|
||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||
|
||||
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||
|
||||
default: return { defaultProtocol(container) };
|
||||
}
|
||||
}
|
||||
|
||||
QList<DockerContainer> ContainerProps::allContainers()
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QList<DockerContainer> all;
|
||||
for (int i = 0; i < metaEnum.keyCount(); ++i) {
|
||||
all.append(static_cast<DockerContainer>(i));
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Xray, "XRay" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
{
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
|
||||
"may be recognized by analysis systems in some highly censored regions.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
|
||||
"of censorship.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption. Recommended for regions with low levels of censorship.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||
"but very resistant to blockages. "
|
||||
"Recommended for regions with high levels of censorship.") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
|
||||
{ DockerContainer::Dns,
|
||||
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
|
||||
{ DockerContainer::Socks5Proxy,
|
||||
QObject::tr("") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
{
|
||||
return {
|
||||
{ DockerContainer::OpenVpn,
|
||||
QObject::tr(
|
||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||
"It employs its unique security protocol, "
|
||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||
"catering to a wide range of devices and operating systems. "
|
||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||
"which continually reinforces its security. "
|
||||
"With a strong balance of performance, security, and compatibility, "
|
||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Normal power consumption on mobile devices\n"
|
||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
|
||||
"* Can operate over both TCP and UDP network protocols.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||
"protecting against blocking.\n\n"
|
||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||
"and the server.\n\n"
|
||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||
"being detected\n\n"
|
||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||
"invisible to analysis systems.\n\n"
|
||||
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
|
||||
"OpenVPN over Cloak from the first connection\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* High power consumption on mobile devices\n"
|
||||
"* Flexible settings\n"
|
||||
"* Not recognised by DPI analysis systems\n"
|
||||
"* Works over TCP network protocol, 443 port.\n") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
||||
"While WireGuard is known for its efficiency, "
|
||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
||||
"making its traffic blend in with regular internet traffic.\n"
|
||||
"This means that AmneziaWG keeps the fast performance of the original "
|
||||
"while adding an extra layer of stealth, "
|
||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
||||
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
||||
"thus presenting an authentic TLS certificate and data. \n"
|
||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||
"legitimate sites without the need for specific configurations. \n"
|
||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
||||
},
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
"making it particularly adaptive in dynamic network environments. \n"
|
||||
"While it offers a blend of security, stability, and speed, "
|
||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
||||
"* Available in the AmneziaVPN only on Windows\n"
|
||||
"* Low power consumption, on mobile devices\n"
|
||||
"* Minimal configuration\n"
|
||||
"* Recognised by DPI analysis systems\n"
|
||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
|
||||
};
|
||||
}
|
||||
|
||||
amnezia::ServiceType ContainerProps::containerService(DockerContainer c)
|
||||
{
|
||||
return ProtocolProps::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
{
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Any;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak: return Proto::Cloak;
|
||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
case DockerContainer::Xray: return Proto::Xray;
|
||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||
case DockerContainer::SSXray: return Proto::SSXray;
|
||||
|
||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||
case DockerContainer::Dns: return Proto::Dns;
|
||||
case DockerContainer::Sftp: return Proto::Sftp;
|
||||
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
|
||||
default: return Proto::Any;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
return true;
|
||||
|
||||
#elif defined(Q_OS_IOS)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
default: return false;
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Ipsec: return false;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_ANDROID)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_LINUX)
|
||||
switch (c) {
|
||||
case DockerContainer::Ipsec: return false;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
||||
{
|
||||
switch (c) {
|
||||
case DockerContainer::Ipsec: return QStringList { "500", "4500" };
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
// case DockerContainer::Cloak: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("Low");
|
||||
case DockerContainer::Awg: return tr("High");
|
||||
// case DockerContainer::Cloak: return tr("Extreme");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
||||
// case DockerContainer::Cloak:
|
||||
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return 3;
|
||||
case DockerContainer::Awg: return 2;
|
||||
// case DockerContainer::Cloak: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isShareable(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
case DockerContainer::Sftp: return false;
|
||||
case DockerContainer::Socks5Proxy: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||
.toObject()
|
||||
.value(config_key::last_config)
|
||||
.toString();
|
||||
|
||||
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
}
|
||||
|
||||
int ContainerProps::installPageOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::OpenVpn: return 4;
|
||||
case DockerContainer::Cloak: return 5;
|
||||
case DockerContainer::ShadowSocks: return 6;
|
||||
case DockerContainer::WireGuard: return 2;
|
||||
case DockerContainer::Awg: return 1;
|
||||
case DockerContainer::Xray: return 3;
|
||||
case DockerContainer::Ipsec: return 7;
|
||||
case DockerContainer::SSXray: return 8;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
#ifndef CONTAINERS_DEFS_H
|
||||
#define CONTAINERS_DEFS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "../protocols/protocols_defs.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
namespace ContainerEnumNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
enum DockerContainer {
|
||||
None = 0,
|
||||
Awg,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp,
|
||||
Socks5Proxy
|
||||
};
|
||||
Q_ENUM_NS(DockerContainer)
|
||||
} // namespace ContainerEnumNS
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
using namespace ProtocolEnumNS;
|
||||
|
||||
class ContainerProps : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||
|
||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerHumanNames();
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDescriptions();
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDetailedDescriptions();
|
||||
|
||||
// these protocols will be displayed in container settings
|
||||
Q_INVOKABLE static QVector<amnezia::Proto> protocolsForContainer(amnezia::DockerContainer container);
|
||||
|
||||
Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c);
|
||||
|
||||
// binding between Docker container and main protocol of given container
|
||||
// it may be changed fot future containers :)
|
||||
Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c);
|
||||
|
||||
Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c);
|
||||
Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c);
|
||||
|
||||
static bool isEasySetupContainer(amnezia::DockerContainer container);
|
||||
static QString easySetupHeader(amnezia::DockerContainer container);
|
||||
static QString easySetupDescription(amnezia::DockerContainer container);
|
||||
static int easySetupOrder(amnezia::DockerContainer container);
|
||||
|
||||
static bool isShareable(amnezia::DockerContainer container);
|
||||
|
||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
static int installPageOrder(amnezia::DockerContainer container);
|
||||
};
|
||||
|
||||
static void declareQmlContainerEnum()
|
||||
{
|
||||
qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum",
|
||||
"Error: only enums");
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
|
||||
|
||||
#endif // CONTAINERS_DEFS_H
|
||||
109
client/core/configurators/awgConfigurator.cpp
Normal file
109
client/core/configurators/awgConfigurator.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "awgConfigurator.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
AwgConfigurator::AwgConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: WireguardConfigurator(sshSession, true, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
const AwgServerConfig* serverConfig = nullptr;
|
||||
const AwgClientConfig* clientConfig = nullptr;
|
||||
|
||||
if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
|
||||
serverConfig = &awgProtocolConfig->serverConfig;
|
||||
if (awgProtocolConfig->clientConfig.has_value()) {
|
||||
clientConfig = &awgProtocolConfig->clientConfig.value();
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolConfig wireguardConfig = WireguardConfigurator::createConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return AwgProtocolConfig{};
|
||||
}
|
||||
|
||||
WireGuardProtocolConfig* wgConfig = wireguardConfig.as<WireGuardProtocolConfig>();
|
||||
if (!wgConfig || !wgConfig->clientConfig.has_value()) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return AwgProtocolConfig{};
|
||||
}
|
||||
|
||||
QString awgConfig = wgConfig->clientConfig->nativeConfig;
|
||||
|
||||
QMap<QString, QString> configMap;
|
||||
auto configLines = awgConfig.split("\n");
|
||||
for (auto &line : configLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgProtocolConfig protocolConfig;
|
||||
if (serverConfig) {
|
||||
protocolConfig.serverConfig = *serverConfig;
|
||||
}
|
||||
|
||||
AwgClientConfig newClientConfig;
|
||||
newClientConfig.nativeConfig = awgConfig;
|
||||
newClientConfig.hostName = wgConfig->clientConfig->hostName;
|
||||
newClientConfig.port = wgConfig->clientConfig->port;
|
||||
newClientConfig.clientIp = wgConfig->clientConfig->clientIp;
|
||||
newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey;
|
||||
newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey;
|
||||
newClientConfig.serverPublicKey = wgConfig->clientConfig->serverPublicKey;
|
||||
newClientConfig.presharedKey = wgConfig->clientConfig->presharedKey;
|
||||
newClientConfig.clientId = wgConfig->clientConfig->clientId;
|
||||
newClientConfig.allowedIps = wgConfig->clientConfig->allowedIps;
|
||||
newClientConfig.persistentKeepAlive = wgConfig->clientConfig->persistentKeepAlive;
|
||||
|
||||
QString mtu = protocols::awg::defaultMtu;
|
||||
if (clientConfig && !clientConfig->mtu.isEmpty()) {
|
||||
mtu = clientConfig->mtu;
|
||||
}
|
||||
newClientConfig.mtu = mtu;
|
||||
|
||||
newClientConfig.junkPacketCount = configMap.value(configKey::junkPacketCount);
|
||||
newClientConfig.junkPacketMinSize = configMap.value(configKey::junkPacketMinSize);
|
||||
newClientConfig.junkPacketMaxSize = configMap.value(configKey::junkPacketMaxSize);
|
||||
newClientConfig.initPacketJunkSize = configMap.value(configKey::initPacketJunkSize);
|
||||
newClientConfig.responsePacketJunkSize = configMap.value(configKey::responsePacketJunkSize);
|
||||
newClientConfig.initPacketMagicHeader = configMap.value(configKey::initPacketMagicHeader);
|
||||
newClientConfig.responsePacketMagicHeader = configMap.value(configKey::responsePacketMagicHeader);
|
||||
newClientConfig.underloadPacketMagicHeader = configMap.value(configKey::underloadPacketMagicHeader);
|
||||
newClientConfig.transportPacketMagicHeader = configMap.value(configKey::transportPacketMagicHeader);
|
||||
newClientConfig.specialJunk1 = configMap.value(configKey::specialJunk1);
|
||||
newClientConfig.specialJunk2 = configMap.value(configKey::specialJunk2);
|
||||
newClientConfig.specialJunk3 = configMap.value(configKey::specialJunk3);
|
||||
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
|
||||
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
|
||||
}
|
||||
|
||||
newClientConfig.isObfuscationEnabled = false;
|
||||
|
||||
protocolConfig.setClientConfig(newClientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
20
client/core/configurators/awgConfigurator.h
Normal file
20
client/core/configurators/awgConfigurator.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef AWGCONFIGURATOR_H
|
||||
#define AWGCONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "wireguardConfigurator.h"
|
||||
|
||||
class AwgConfigurator : public WireguardConfigurator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AwgConfigurator(SshSession* sshSession, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
};
|
||||
|
||||
#endif // AWGCONFIGURATOR_H
|
||||
50
client/core/configurators/configuratorBase.cpp
Normal file
50
client/core/configurators/configuratorBase.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "configuratorBase.h"
|
||||
|
||||
#include "core/configurators/awgConfigurator.h"
|
||||
#include "core/configurators/ikev2Configurator.h"
|
||||
#include "core/configurators/openVpnConfigurator.h"
|
||||
#include "core/configurators/wireguardConfigurator.h"
|
||||
#include "core/configurators/xrayConfigurator.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
ConfiguratorBase::ConfiguratorBase(SshSession* sshSession, QObject *parent)
|
||||
: QObject { parent }, m_sshSession(sshSession)
|
||||
{
|
||||
}
|
||||
|
||||
QScopedPointer<ConfiguratorBase> ConfiguratorBase::create(Proto protocol,
|
||||
SshSession* sshSession)
|
||||
{
|
||||
switch (protocol) {
|
||||
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(sshSession));
|
||||
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(sshSession, false));
|
||||
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(sshSession));
|
||||
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(sshSession));
|
||||
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
|
||||
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
|
||||
default: return QScopedPointer<ConfiguratorBase>();
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolConfig ConfiguratorBase::processConfigWithLocalSettings(const ConnectionSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
ProtocolConfig ConfiguratorBase::processConfigWithExportSettings(const ExportSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
void ConfiguratorBase::applyDnsToNativeConfig(const DnsSettings &dns, ProtocolConfig &protocolConfig)
|
||||
{
|
||||
QString config = protocolConfig.nativeConfig();
|
||||
config.replace("$PRIMARY_DNS", dns.primaryDns);
|
||||
config.replace("$SECONDARY_DNS", dns.secondaryDns);
|
||||
protocolConfig.setNativeConfig(config);
|
||||
}
|
||||
43
client/core/configurators/configuratorBase.h
Normal file
43
client/core/configurators/configuratorBase.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef CONFIGURATORBASE_H
|
||||
#define CONFIGURATORBASE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
class SshSession;
|
||||
|
||||
class ConfiguratorBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfiguratorBase(SshSession* sshSession, QObject *parent = nullptr);
|
||||
|
||||
static QScopedPointer<ConfiguratorBase> create(amnezia::Proto protocol,
|
||||
SshSession* sshSession);
|
||||
|
||||
virtual amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) = 0;
|
||||
|
||||
virtual amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig);
|
||||
virtual amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig);
|
||||
|
||||
protected:
|
||||
void applyDnsToNativeConfig(const amnezia::DnsSettings &dns, amnezia::ProtocolConfig &protocolConfig);
|
||||
|
||||
SshSession* m_sshSession;
|
||||
};
|
||||
|
||||
#endif // CONFIGURATORBASE_H
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "ikev2_configurator.h"
|
||||
#include "ikev2Configurator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
@@ -8,14 +8,16 @@
|
||||
#include <QTemporaryFile>
|
||||
#include <QUuid>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "utilities.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/models/protocols/ikev2ProtocolConfig.h"
|
||||
|
||||
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
Ikev2Configurator::Ikev2Configurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,7 +27,6 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
Ikev2Configurator::ConnectionData connData;
|
||||
connData.host = credentials.hostName;
|
||||
connData.clientId = Utils::getRandomString(16);
|
||||
connData.password = Utils::getRandomString(16);
|
||||
connData.password = "";
|
||||
|
||||
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
|
||||
@@ -39,14 +40,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
||||
.arg(connData.clientId);
|
||||
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
|
||||
errorCode = m_sshSession->runContainerScript(credentials, container, scriptCreateCert);
|
||||
|
||||
QString scriptExportCert =
|
||||
QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName);
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
|
||||
errorCode = m_sshSession->runContainerScript(credentials, container, scriptExportCert);
|
||||
|
||||
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
|
||||
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
|
||||
connData.clientCert = m_sshSession->getTextFileFromContainer(container, credentials, certFileName, errorCode);
|
||||
connData.caCert = m_sshSession->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
|
||||
|
||||
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
||||
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
||||
@@ -54,26 +55,51 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
ProtocolConfig Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
Q_UNUSED(containerConfig)
|
||||
const Ikev2ServerConfig* serverConfig = nullptr;
|
||||
if (auto* ikev2Config = containerConfig.protocolConfig.as<Ikev2ProtocolConfig>()) {
|
||||
serverConfig = &ikev2Config->serverConfig;
|
||||
}
|
||||
|
||||
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
return Ikev2ProtocolConfig{};
|
||||
}
|
||||
|
||||
return genIkev2Config(connData);
|
||||
QString configJson = genIkev2Config(connData);
|
||||
QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8());
|
||||
QJsonObject configObj = doc.object();
|
||||
|
||||
Ikev2ProtocolConfig protocolConfig;
|
||||
if (serverConfig) {
|
||||
protocolConfig.serverConfig = *serverConfig;
|
||||
} else {
|
||||
protocolConfig.serverConfig.hostName = connData.host;
|
||||
}
|
||||
|
||||
Ikev2ClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = configJson;
|
||||
clientConfig.hostName = connData.host;
|
||||
clientConfig.userName = connData.clientId;
|
||||
clientConfig.cert = QString(connData.clientCert.toBase64());
|
||||
clientConfig.password = connData.password;
|
||||
clientConfig.clientId = connData.clientId;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
|
||||
{
|
||||
QJsonObject config;
|
||||
config[config_key::hostName] = connData.host;
|
||||
config[config_key::userName] = connData.clientId;
|
||||
config[config_key::cert] = QString(connData.clientCert.toBase64());
|
||||
config[config_key::password] = connData.password;
|
||||
config[configKey::hostName] = connData.host;
|
||||
config[configKey::userName] = connData.clientId;
|
||||
config[configKey::cert] = QString(connData.clientCert.toBase64());
|
||||
config[configKey::password] = connData.password;
|
||||
|
||||
return QJsonDocument(config).toJson();
|
||||
}
|
||||
39
client/core/configurators/ikev2Configurator.h
Normal file
39
client/core/configurators/ikev2Configurator.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef IKEV2_CONFIGURATOR_H
|
||||
#define IKEV2_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
class Ikev2Configurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Ikev2Configurator(SshSession* sshSession, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData {
|
||||
QByteArray clientCert; // p12 client cert
|
||||
QByteArray caCert; // p12 server cert
|
||||
QString clientId;
|
||||
QString password; // certificate password
|
||||
QString host; // host ip
|
||||
};
|
||||
|
||||
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
QString genIkev2Config(const ConnectionData &connData);
|
||||
QString genMobileConfig(const ConnectionData &connData);
|
||||
QString genStrongSwanConfig(const ConnectionData &connData);
|
||||
|
||||
ConnectionData prepareIkev2Config(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container, amnezia::ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // IKEV2_CONFIGURATOR_H
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "openvpn_configurator.h"
|
||||
#include "openVpnConfigurator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
@@ -13,25 +14,34 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/models/protocols/openVpnProtocolConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode &errorCode)
|
||||
DockerContainer container,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||
connData.host = credentials.hostName;
|
||||
@@ -43,26 +53,26 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
|
||||
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
||||
|
||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
errorCode = signCert(container, credentials, connData.clientId);
|
||||
errorCode = signCert(container, credentials, dnsSettings, connData.clientId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.caCert =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
|
||||
connData.clientCert = m_serverController->getTextFileFromContainer(
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
|
||||
connData.clientCert = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
|
||||
connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
errorCode = ErrorCode::SshScpFailureError;
|
||||
@@ -71,23 +81,49 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return "";
|
||||
const OpenVpnServerConfig* serverConfig = nullptr;
|
||||
if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) {
|
||||
serverConfig = &openVpnProtocolConfig->serverConfig;
|
||||
}
|
||||
|
||||
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
|
||||
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
|
||||
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), vars);
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return OpenVpnProtocolConfig{};
|
||||
}
|
||||
|
||||
auto sanitizeStaticKey = [](const QString &key) {
|
||||
QStringList lines = key.split('\n');
|
||||
QStringList filtered;
|
||||
filtered.reserve(lines.size());
|
||||
for (const QString &line : lines) {
|
||||
const QString trimmed = line.trimmed();
|
||||
if (trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
filtered.append(line);
|
||||
}
|
||||
QString result = filtered.join('\n');
|
||||
if (!result.endsWith('\n')) {
|
||||
result.append('\n');
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||
|
||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
||||
} else {
|
||||
config.replace("<tls-auth>", "");
|
||||
config.replace("</tls-auth>", "");
|
||||
@@ -97,42 +133,45 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
||||
config.replace("block-outside-dns", "");
|
||||
#endif
|
||||
|
||||
QJsonObject jConfig;
|
||||
jConfig[config_key::config] = config;
|
||||
|
||||
jConfig[config_key::clientId] = connData.clientId;
|
||||
|
||||
return QJsonDocument(jConfig).toJson();
|
||||
OpenVpnProtocolConfig protocolConfig;
|
||||
if (serverConfig) {
|
||||
protocolConfig.serverConfig = *serverConfig;
|
||||
}
|
||||
|
||||
OpenVpnClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
clientConfig.clientId = connData.clientId;
|
||||
clientConfig.blockOutsideDns = false;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
|
||||
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
QString config = json[config_key::config].toString();
|
||||
QString config = protocolConfig.nativeConfig();
|
||||
|
||||
if (!isApiConfig) {
|
||||
if (!settings.isApiConfig) {
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||
|
||||
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
if (!settings.splitTunneling.isSitesSplitTunnelingEnabled) {
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
} else if (settings.splitTunneling.routeMode == RouteMode::VpnOnlyForwardSites) {
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
} else if (settings.splitTunneling.routeMode == RouteMode::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
@@ -143,59 +182,57 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
#endif
|
||||
|
||||
#if (defined(MZ_MACOS) || defined(MZ_LINUX))
|
||||
QString dnsConf = QString("\nscript-security 2\n"
|
||||
"up %1/update-resolv-conf.sh\n"
|
||||
"down %1/update-resolv-conf.sh\n")
|
||||
.arg(qApp->applicationDirPath());
|
||||
|
||||
config.append(dnsConf);
|
||||
config.append(QString("\nscript-security 2\n"
|
||||
"up %1/update-resolv-conf.sh\n"
|
||||
"down %1/update-resolv-conf.sh\n")
|
||||
.arg(qApp->applicationDirPath()));
|
||||
#endif
|
||||
|
||||
json[config_key::config] = config;
|
||||
return QJsonDocument(json).toJson();
|
||||
protocolConfig.setNativeConfig(config);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
ProtocolConfig OpenVpnConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
|
||||
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
QString config = json[config_key::config].toString();
|
||||
QString config = protocolConfig.nativeConfig();
|
||||
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
config.append("block-ipv6\n");
|
||||
|
||||
// remove block-outside-dns for all exported configs
|
||||
config.replace("block-outside-dns", "");
|
||||
|
||||
json[config_key::config] = config;
|
||||
return QJsonDocument(json).toJson();
|
||||
protocolConfig.setNativeConfig(config);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
|
||||
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials,
|
||||
const DnsSettings &dnsSettings, QString clientId)
|
||||
{
|
||||
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
|
||||
"easyrsa import-req %2/%3.req %3\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(ContainerUtils::containerToString(container))
|
||||
.arg(amnezia::protocols::openvpn::clientsDirPath)
|
||||
.arg(clientId);
|
||||
|
||||
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
|
||||
"easyrsa sign-req client %2\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(ContainerUtils::containerToString(container))
|
||||
.arg(clientId);
|
||||
|
||||
QStringList scriptList { script_import, script_sign };
|
||||
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
|
||||
QString script = m_sshSession->replaceVars(scriptList.join("\n"), amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns));
|
||||
|
||||
return m_serverController->runScript(credentials, script);
|
||||
return m_sshSession->runScript(credentials, script);
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
49
client/core/configurators/openVpnConfigurator.h
Normal file
49
client/core/configurators/openVpnConfigurator.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef OPENVPN_CONFIGURATOR_H
|
||||
#define OPENVPN_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
class OpenVpnConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OpenVpnConfigurator(SshSession* sshSession, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
QString clientId;
|
||||
QString request; // certificate request
|
||||
QString privKey; // client private key
|
||||
QString clientCert; // client signed certificate
|
||||
QString caCert; // server certificate
|
||||
QString taKey; // tls-auth key
|
||||
QString host; // host ip
|
||||
};
|
||||
|
||||
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
static ConnectionData createCertRequest();
|
||||
|
||||
private:
|
||||
ConnectionData prepareOpenVpnConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
amnezia::ErrorCode signCert(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
|
||||
const amnezia::DnsSettings &dnsSettings, QString clientId);
|
||||
};
|
||||
|
||||
#endif // OPENVPN_CONFIGURATOR_H
|
||||
288
client/core/configurators/wireguardConfigurator.cpp
Normal file
288
client/core/configurators/wireguardConfigurator.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "wireguardConfigurator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/wireGuardProtocolConfig.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
#include <QJsonArray>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
WireguardConfigurator::WireguardConfigurator(SshSession* sshSession, bool isAwg,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent), m_isAwg(isAwg)
|
||||
{
|
||||
m_serverConfigPath =
|
||||
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
m_serverPublicKeyPath =
|
||||
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||
m_serverPskKeyPath =
|
||||
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||
|
||||
m_protocolName = m_isAwg ? configKey::awg : configKey::wireguard;
|
||||
m_defaultPort = m_isAwg ? protocols::awg::defaultPort : protocols::wireguard::defaultPort;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
{
|
||||
// TODO review
|
||||
constexpr size_t EDDSA_KEY_LENGTH = 32;
|
||||
|
||||
ConnectionData connData;
|
||||
|
||||
unsigned char buff[EDDSA_KEY_LENGTH];
|
||||
int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH);
|
||||
if (ret <= 0)
|
||||
return connData;
|
||||
|
||||
EVP_PKEY *pKey = EVP_PKEY_new();
|
||||
q_check_ptr(pKey);
|
||||
pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH);
|
||||
|
||||
size_t keySize = EDDSA_KEY_LENGTH;
|
||||
|
||||
// save private key
|
||||
unsigned char priv[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_private_key(pKey, priv, &keySize);
|
||||
connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64();
|
||||
|
||||
// save public key
|
||||
unsigned char pub[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_public_key(pKey, pub, &keySize);
|
||||
connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64();
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
|
||||
{
|
||||
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
||||
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
|
||||
|
||||
QList<QHostAddress> ips;
|
||||
|
||||
while (matchIterator.hasNext()) {
|
||||
QRegularExpressionMatch match = matchIterator.next();
|
||||
const QString address_string { match.captured(1) };
|
||||
const QHostAddress address { address_string };
|
||||
if (address.isNull()) {
|
||||
qWarning() << "Couldn't recognize the ip address: " << address_string;
|
||||
} else {
|
||||
ips << address;
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const WireGuardServerConfig* serverConfig,
|
||||
const AwgServerConfig* awgServerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
|
||||
QString portStr = m_defaultPort;
|
||||
if (serverConfig && !serverConfig->port.isEmpty()) {
|
||||
portStr = serverConfig->port;
|
||||
} else if (awgServerConfig && !awgServerConfig->port.isEmpty()) {
|
||||
portStr = awgServerConfig->port;
|
||||
}
|
||||
connData.port = portStr;
|
||||
|
||||
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString configPath = m_serverConfigPath;
|
||||
if (container == DockerContainer::Awg) {
|
||||
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
|
||||
}
|
||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode = m_sshSession->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
auto ips = getIpsFromConf(stdOut);
|
||||
|
||||
QHostAddress nextIp = [&] {
|
||||
QHostAddress result;
|
||||
QHostAddress lastIp;
|
||||
QString subnetAddress = protocols::wireguard::defaultSubnetAddress;
|
||||
if (serverConfig && !serverConfig->subnetAddress.isEmpty()) {
|
||||
subnetAddress = serverConfig->subnetAddress;
|
||||
} else if (awgServerConfig && !awgServerConfig->subnetAddress.isEmpty()) {
|
||||
subnetAddress = awgServerConfig->subnetAddress;
|
||||
}
|
||||
if (ips.empty()) {
|
||||
lastIp.setAddress(subnetAddress);
|
||||
} else {
|
||||
lastIp = ips.last();
|
||||
}
|
||||
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||
switch (lastOctet) {
|
||||
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
connData.clientIP = nextIp.toString();
|
||||
|
||||
// Get keys
|
||||
connData.serverPubKey =
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||
connData.serverPubKey.replace("\n", "");
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
|
||||
connData.pskKey.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Add client to config
|
||||
QString configPart = QString("[Peer]\n"
|
||||
"PublicKey = %1\n"
|
||||
"PresharedKey = %2\n"
|
||||
"AllowedIPs = %3/32\n\n")
|
||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, configPart, configPath,
|
||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
bool isAwg = (container == DockerContainer::Awg2);
|
||||
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
|
||||
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
|
||||
QString script = QString(
|
||||
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
|
||||
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns)));
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
const WireGuardServerConfig* wireguardServerConfig = nullptr;
|
||||
const WireGuardClientConfig* wireguardClientConfig = nullptr;
|
||||
const AwgServerConfig* awgServerConfig = nullptr;
|
||||
const AwgClientConfig* awgClientConfig = nullptr;
|
||||
|
||||
if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) {
|
||||
wireguardServerConfig = &wireGuardProtocolConfig->serverConfig;
|
||||
if (wireGuardProtocolConfig->clientConfig.has_value()) {
|
||||
wireguardClientConfig = &wireGuardProtocolConfig->clientConfig.value();
|
||||
}
|
||||
} else if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
|
||||
awgServerConfig = &awgProtocolConfig->serverConfig;
|
||||
if (awgProtocolConfig->clientConfig.has_value()) {
|
||||
awgClientConfig = &awgProtocolConfig->clientConfig.value();
|
||||
}
|
||||
}
|
||||
|
||||
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
|
||||
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config = m_sshSession->replaceVars(scriptData, vars);
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return WireGuardProtocolConfig{};
|
||||
}
|
||||
|
||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
|
||||
config.replace("$WIREGUARD_CLIENT_IP", connData.clientIP);
|
||||
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
|
||||
config.replace("$WIREGUARD_PSK", connData.pskKey);
|
||||
|
||||
QString mtu = protocols::wireguard::defaultMtu;
|
||||
if (wireguardClientConfig && !wireguardClientConfig->mtu.isEmpty()) {
|
||||
mtu = wireguardClientConfig->mtu;
|
||||
} else if (awgClientConfig && !awgClientConfig->mtu.isEmpty()) {
|
||||
mtu = awgClientConfig->mtu;
|
||||
}
|
||||
|
||||
WireGuardProtocolConfig protocolConfig;
|
||||
if (wireguardServerConfig) {
|
||||
protocolConfig.serverConfig = *wireguardServerConfig;
|
||||
}
|
||||
|
||||
WireGuardClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
clientConfig.hostName = connData.host;
|
||||
clientConfig.port = connData.port.toInt();
|
||||
clientConfig.clientIp = connData.clientIP;
|
||||
clientConfig.clientPrivateKey = connData.clientPrivKey;
|
||||
clientConfig.clientPublicKey = connData.clientPubKey;
|
||||
clientConfig.serverPublicKey = connData.serverPubKey;
|
||||
clientConfig.presharedKey = connData.pskKey;
|
||||
clientConfig.clientId = connData.clientPubKey;
|
||||
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
|
||||
clientConfig.persistentKeepAlive = "25";
|
||||
clientConfig.mtu = mtu;
|
||||
clientConfig.isObfuscationEnabled = false;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
ProtocolConfig WireguardConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
return ConfiguratorBase::processConfigWithLocalSettings(settings, protocolConfig);
|
||||
}
|
||||
|
||||
ProtocolConfig WireguardConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
|
||||
ProtocolConfig protocolConfig)
|
||||
{
|
||||
return ConfiguratorBase::processConfigWithExportSettings(settings, protocolConfig);
|
||||
}
|
||||
61
client/core/configurators/wireguardConfigurator.h
Normal file
61
client/core/configurators/wireguardConfigurator.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||
#define WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
class WireguardConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WireguardConfigurator(SshSession* sshSession,
|
||||
bool isAwg, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
QString clientPrivKey; // client private key
|
||||
QString clientPubKey; // client public key
|
||||
QString clientIP; // internal client IP address
|
||||
QString serverPubKey; // tls-auth key
|
||||
QString pskKey; // preshared key
|
||||
QString host; // host ip
|
||||
QString port;
|
||||
};
|
||||
|
||||
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
static ConnectionData genClientKeys();
|
||||
|
||||
private:
|
||||
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||
ConnectionData prepareWireguardConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::WireGuardServerConfig* serverConfig,
|
||||
const amnezia::AwgServerConfig* awgServerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
|
||||
bool m_isAwg;
|
||||
QString m_serverConfigPath;
|
||||
QString m_serverPublicKeyPath;
|
||||
QString m_serverPskKeyPath;
|
||||
amnezia::ProtocolScriptType m_configTemplate;
|
||||
QString m_protocolName;
|
||||
QString m_defaultPort;
|
||||
};
|
||||
|
||||
#endif // WIREGUARD_CONFIGURATOR_H
|
||||
@@ -1,32 +1,43 @@
|
||||
#include "xray_configurator.h"
|
||||
#include "xrayConfigurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUuid>
|
||||
#include "logger.h"
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
}
|
||||
|
||||
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_serverController->getTextFileFromContainer(
|
||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
@@ -45,13 +56,13 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains("inbounds")) {
|
||||
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig["inbounds"].toArray();
|
||||
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
@@ -59,38 +70,38 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains("settings")) {
|
||||
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject settings = inbound["settings"].toObject();
|
||||
if (!settings.contains("clients")) {
|
||||
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
|
||||
if (!settings.contains(amnezia::protocols::xray::clients)) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray clients = settings["clients"].toArray();
|
||||
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{"id", clientId},
|
||||
{"flow", "xtls-rprx-vision"}
|
||||
{amnezia::protocols::xray::id, clientId},
|
||||
{amnezia::protocols::xray::flow, "xtls-rprx-vision"}
|
||||
};
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings["clients"] = clients;
|
||||
inbound["settings"] = settings;
|
||||
settings[amnezia::protocols::xray::clients] = clients;
|
||||
inbound[amnezia::protocols::xray::settings] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig["inbounds"] = inbounds;
|
||||
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_serverController->uploadTextFileToContainer(
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
@@ -104,9 +115,9 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_serverController->runScript(
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
|
||||
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
|
||||
);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
@@ -117,57 +128,75 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
|
||||
return clientId;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
// Get client ID from prepareServerConfig
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
|
||||
const XrayServerConfig* serverConfig = nullptr;
|
||||
if (auto* xrayConfig = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
serverConfig = &xrayConfig->serverConfig;
|
||||
}
|
||||
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
|
||||
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
|
||||
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars);
|
||||
|
||||
if (config.isEmpty()) {
|
||||
logger.error() << "Failed to get config template";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
QString xrayPublicKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||
logger.error() << "Failed to get public key";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayShortId =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||
logger.error() << "Failed to get short ID";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayShortId.replace("\n", "");
|
||||
|
||||
// Validate all required variables are present
|
||||
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
|
||||
logger.error() << "Config template missing required variables:"
|
||||
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
|
||||
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
|
||||
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayClientId);
|
||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||
|
||||
return config;
|
||||
XrayProtocolConfig protocolConfig;
|
||||
if (serverConfig) {
|
||||
protocolConfig.serverConfig = *serverConfig;
|
||||
}
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
clientConfig.localPort = "";
|
||||
clientConfig.id = xrayClientId;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
27
client/core/configurators/xrayConfigurator.h
Normal file
27
client/core/configurators/xrayConfigurator.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef XRAY_CONFIGURATOR_H
|
||||
#define XRAY_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configuratorBase.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
class XrayConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
XrayConfigurator(SshSession* sshSession, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
54
client/core/controllers/allowedDnsController.cpp
Normal file
54
client/core/controllers/allowedDnsController.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "allowedDnsController.h"
|
||||
|
||||
AllowedDnsController::AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository)
|
||||
: m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
fillDnsServers();
|
||||
}
|
||||
|
||||
bool AllowedDnsController::addDns(const QString &ip)
|
||||
{
|
||||
if (m_dnsServers.contains(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dnsServers.append(ip);
|
||||
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AllowedDnsController::addDnsList(const QStringList &dnsServers, bool replaceExisting)
|
||||
{
|
||||
if (replaceExisting) {
|
||||
m_dnsServers.clear();
|
||||
}
|
||||
|
||||
for (const QString &ip : dnsServers) {
|
||||
if (!m_dnsServers.contains(ip)) {
|
||||
m_dnsServers.append(ip);
|
||||
}
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
|
||||
}
|
||||
|
||||
void AllowedDnsController::removeDns(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_dnsServers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_dnsServers.removeAt(index);
|
||||
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
|
||||
}
|
||||
|
||||
QStringList AllowedDnsController::getCurrentDnsServers() const
|
||||
{
|
||||
return m_dnsServers;
|
||||
}
|
||||
|
||||
void AllowedDnsController::fillDnsServers()
|
||||
{
|
||||
m_dnsServers = m_appSettingsRepository->getAllowedDnsServers();
|
||||
}
|
||||
|
||||
26
client/core/controllers/allowedDnsController.h
Normal file
26
client/core/controllers/allowedDnsController.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef ALLOWEDDNSCONTROLLER_H
|
||||
#define ALLOWEDDNSCONTROLLER_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class AllowedDnsController
|
||||
{
|
||||
public:
|
||||
explicit AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
bool addDns(const QString &ip);
|
||||
void addDnsList(const QStringList &dnsServers, bool replaceExisting);
|
||||
void removeDns(int index);
|
||||
QStringList getCurrentDnsServers() const;
|
||||
|
||||
private:
|
||||
void fillDnsServers();
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
QStringList m_dnsServers;
|
||||
};
|
||||
|
||||
#endif // ALLOWEDDNSCONTROLLER_H
|
||||
|
||||
72
client/core/controllers/api/newsController.cpp
Normal file
72
client/core/controllers/api/newsController.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "newsController.h"
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository,
|
||||
ServersController* serversController)
|
||||
: m_appSettingsRepository(appSettingsRepository), m_serversController(serversController)
|
||||
{
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
{
|
||||
if (!m_serversController) {
|
||||
qWarning() << "ServersController is null, skip fetchNews";
|
||||
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
|
||||
}
|
||||
|
||||
const auto stacks = m_serversController->gatewayStacks();
|
||||
if (stacks.isEmpty()) {
|
||||
qDebug() << "No Gateway stacks, skip fetchNews";
|
||||
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray()));
|
||||
}
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
|
||||
|
||||
const QJsonObject stacksJson = stacks.toJson();
|
||||
if (stacksJson.contains(apiDefs::key::userCountryCode)) {
|
||||
payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode));
|
||||
}
|
||||
if (stacksJson.contains(apiDefs::key::serviceType)) {
|
||||
payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType));
|
||||
}
|
||||
|
||||
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
|
||||
return future.then([gatewayController](QPair<ErrorCode, QByteArray> result) -> QPair<ErrorCode, QJsonArray> {
|
||||
auto [errorCode, responseBody] = result;
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return qMakePair(errorCode, QJsonArray());
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||
QJsonArray newsArray;
|
||||
if (doc.isArray()) {
|
||||
newsArray = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.value("news").isArray()) {
|
||||
newsArray = obj.value("news").toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return qMakePair(ErrorCode::NoError, newsArray);
|
||||
});
|
||||
}
|
||||
|
||||
28
client/core/controllers/api/newsController.h
Normal file
28
client/core/controllers/api/newsController.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef NEWSCONTROLLER_H
|
||||
#define NEWSCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
|
||||
class NewsController
|
||||
{
|
||||
public:
|
||||
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
|
||||
ServersController* serversController);
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
|
||||
|
||||
private:
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
ServersController* m_serversController;
|
||||
};
|
||||
|
||||
#endif // NEWSCONTROLLER_H
|
||||
|
||||
248
client/core/controllers/api/servicesCatalogController.cpp
Normal file
248
client/core/controllers/api/servicesCatalogController.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include "servicesCatalogController.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QSysInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QEventLoop>
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <limits>
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "version.h"
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
{
|
||||
constexpr char serviceDescription[] = "service_description";
|
||||
constexpr char subscriptionPlans[] = "subscription_plans";
|
||||
constexpr char storeProductId[] = "store_product_id";
|
||||
constexpr char priceLabel[] = "price_label";
|
||||
constexpr char subtitle[] = "subtitle";
|
||||
constexpr char isTrial[] = "is_trial";
|
||||
constexpr char minPriceLabel[] = "min_price_label";
|
||||
}
|
||||
|
||||
namespace serviceType
|
||||
{
|
||||
constexpr char amneziaPremium[] = "amnezia-premium";
|
||||
}
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
struct StoreKitPlanQuote {
|
||||
QString displayPrice;
|
||||
double priceAmount = 0.0;
|
||||
double subscriptionBillingMonths = 0.0;
|
||||
QString displayPricePerMonth;
|
||||
};
|
||||
|
||||
constexpr double oneMonthThreshold = 1.0 + 1e-6;
|
||||
constexpr double monthsFallbackThreshold = 1e-6;
|
||||
constexpr double monthlyPriceEpsilon = 1e-9;
|
||||
|
||||
QStringList collectPremiumStoreProductIds(const QJsonArray &services)
|
||||
{
|
||||
QStringList productIds;
|
||||
QSet<QString> seenProductIds;
|
||||
for (const QJsonValue &serviceValue : services) {
|
||||
const QJsonObject serviceObject = serviceValue.toObject();
|
||||
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
|
||||
continue;
|
||||
}
|
||||
const QJsonArray subscriptionPlans =
|
||||
serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray();
|
||||
for (const QJsonValue &planValue : subscriptionPlans) {
|
||||
if (!planValue.isObject()) {
|
||||
continue;
|
||||
}
|
||||
const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString();
|
||||
if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) {
|
||||
continue;
|
||||
}
|
||||
seenProductIds.insert(storeProductId);
|
||||
productIds.append(storeProductId);
|
||||
}
|
||||
}
|
||||
return productIds;
|
||||
}
|
||||
|
||||
QHash<QString, StoreKitPlanQuote> buildStoreKitQuoteMap(const QList<QVariantMap> &fetchedProducts)
|
||||
{
|
||||
QHash<QString, StoreKitPlanQuote> quotesByProductId;
|
||||
quotesByProductId.reserve(fetchedProducts.size());
|
||||
|
||||
for (const QVariantMap &productInfo : fetchedProducts) {
|
||||
const QString productId = productInfo.value(QStringLiteral("productId")).toString();
|
||||
if (productId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString();
|
||||
if (displayPrice.isEmpty()) {
|
||||
const QString price = productInfo.value(QStringLiteral("price")).toString();
|
||||
const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString();
|
||||
displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode);
|
||||
}
|
||||
|
||||
StoreKitPlanQuote quote;
|
||||
quote.displayPrice = displayPrice;
|
||||
quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble();
|
||||
quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble();
|
||||
quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString();
|
||||
quotesByProductId.insert(productId, quote);
|
||||
}
|
||||
|
||||
return quotesByProductId;
|
||||
}
|
||||
|
||||
void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data)
|
||||
{
|
||||
QJsonArray services = data.value(apiDefs::key::services).toArray();
|
||||
if (services.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QStringList productIds = collectPremiumStoreProductIds(services);
|
||||
if (productIds.isEmpty()) {
|
||||
qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload";
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QVariantMap> fetchedProducts;
|
||||
QEventLoop loop;
|
||||
IosController::Instance()->fetchProducts(productIds,
|
||||
[&](const QList<QVariantMap> &products, const QStringList &invalidIds,
|
||||
const QString &errorString) {
|
||||
if (!errorString.isEmpty()) {
|
||||
qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString;
|
||||
}
|
||||
if (!invalidIds.isEmpty()) {
|
||||
qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds;
|
||||
}
|
||||
fetchedProducts = products;
|
||||
loop.quit();
|
||||
});
|
||||
loop.exec();
|
||||
|
||||
const QHash<QString, StoreKitPlanQuote> quotesByProductId = buildStoreKitQuoteMap(fetchedProducts);
|
||||
|
||||
for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) {
|
||||
QJsonObject serviceObject = services.at(serviceIndex).toObject();
|
||||
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject();
|
||||
const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray();
|
||||
|
||||
QJsonArray mergedPlans;
|
||||
double minMonthlyAmount = std::numeric_limits<double>::infinity();
|
||||
QString minMonthlyDisplay;
|
||||
|
||||
for (const QJsonValue &planValue : sourcePlans) {
|
||||
if (!planValue.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject planObject = planValue.toObject();
|
||||
const QString storeProductId = planObject.value(configKey::storeProductId).toString();
|
||||
if (storeProductId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto quoteIterator = quotesByProductId.constFind(storeProductId);
|
||||
if (quoteIterator == quotesByProductId.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool isTrialPlan = planObject.value(configKey::isTrial).toBool();
|
||||
const StoreKitPlanQuote "e = *quoteIterator;
|
||||
planObject.insert(configKey::priceLabel, quote.displayPrice);
|
||||
|
||||
const double months = quote.subscriptionBillingMonths;
|
||||
if (!isTrialPlan && months > oneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) {
|
||||
planObject.insert(
|
||||
configKey::subtitle,
|
||||
QCoreApplication::translate("ServicesCatalogController", "%1/mo",
|
||||
"IAP: price per month in plan subtitle")
|
||||
.arg(quote.displayPricePerMonth));
|
||||
}
|
||||
|
||||
if (!isTrialPlan && quote.priceAmount > 0.0) {
|
||||
const double monthsForMin = months > monthsFallbackThreshold ? months : 1.0;
|
||||
const double monthly = quote.priceAmount / monthsForMin;
|
||||
if (monthly < minMonthlyAmount - monthlyPriceEpsilon) {
|
||||
minMonthlyAmount = monthly;
|
||||
minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice;
|
||||
}
|
||||
}
|
||||
|
||||
mergedPlans.append(planObject);
|
||||
}
|
||||
|
||||
descriptionObject.insert(configKey::subscriptionPlans, mergedPlans);
|
||||
if (minMonthlyAmount < std::numeric_limits<double>::infinity() && !minMonthlyDisplay.isEmpty()) {
|
||||
descriptionObject.insert(configKey::minPriceLabel,
|
||||
QCoreApplication::translate("ServicesCatalogController", "from %1 per month",
|
||||
"IAP: card footer minimum monthly price from StoreKit")
|
||||
.arg(minMonthlyDisplay));
|
||||
}
|
||||
serviceObject.insert(configKey::serviceDescription, descriptionObject);
|
||||
services.replace(serviceIndex, serviceObject);
|
||||
}
|
||||
data.insert(apiDefs::key::services, services);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository)
|
||||
: m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData)
|
||||
{
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
|
||||
apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!responseBody.contains(apiDefs::key::services.data())) {
|
||||
errorCode = ErrorCode::ApiServicesMissingError;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
servicesData = QJsonDocument::fromJson(responseBody).object();
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
mergeStoreKitPricesIntoPremiumPlans(servicesData);
|
||||
#endif
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
26
client/core/controllers/api/servicesCatalogController.h
Normal file
26
client/core/controllers/api/servicesCatalogController.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef SERVICESCATALOGCONTROLLER_H
|
||||
#define SERVICESCATALOGCONTROLLER_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class ServicesCatalogController
|
||||
{
|
||||
public:
|
||||
explicit ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
ErrorCode fillAvailableServices(QJsonObject &servicesData);
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody);
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
};
|
||||
|
||||
#endif // SERVICESCATALOGCONTROLLER_H
|
||||
|
||||
1094
client/core/controllers/api/subscriptionController.cpp
Normal file
1094
client/core/controllers/api/subscriptionController.cpp
Normal file
File diff suppressed because it is too large
Load Diff
122
client/core/controllers/api/subscriptionController.h
Normal file
122
client/core/controllers/api/subscriptionController.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef SUBSCRIPTIONCONTROLLER_H
|
||||
#define SUBSCRIPTIONCONTROLLER_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QByteArray>
|
||||
#include <QFuture>
|
||||
#include <QList>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
|
||||
class ServersController;
|
||||
|
||||
class SubscriptionController
|
||||
{
|
||||
public:
|
||||
struct ProtocolData
|
||||
{
|
||||
QString certRequest;
|
||||
QString certPrivKey;
|
||||
QString wireGuardClientPrivKey;
|
||||
QString wireGuardClientPubKey;
|
||||
QString xrayUuid;
|
||||
};
|
||||
|
||||
struct GatewayRequestData
|
||||
{
|
||||
QString osVersion;
|
||||
QString appVersion;
|
||||
QString appLanguage;
|
||||
QString installationUuid;
|
||||
QString userCountryCode;
|
||||
QString serverCountryCode;
|
||||
QString serviceType;
|
||||
QString serviceProtocol;
|
||||
QJsonObject authData;
|
||||
|
||||
QJsonObject toJsonObject() const;
|
||||
};
|
||||
|
||||
explicit SubscriptionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
ProtocolData generateProtocolData(const QString &protocol);
|
||||
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
|
||||
ErrorCode fillServerConfig(const QJsonObject &serverConfigJson, ServerConfig &serverConfig);
|
||||
|
||||
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
ServerConfig &serverConfig);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email,
|
||||
ServerConfig &serverConfig);
|
||||
|
||||
ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
ErrorCode updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
ErrorCode deactivateDevice(int serverIndex, bool isRemoveEvent);
|
||||
|
||||
ErrorCode deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig);
|
||||
|
||||
ErrorCode revokeNativeConfig(int serverIndex, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode updateServiceFromTelegram(int serverIndex);
|
||||
|
||||
ErrorCode prepareVpnKeyExport(int serverIndex, QString &vpnKey);
|
||||
|
||||
ErrorCode validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers);
|
||||
|
||||
void removeApiConfig(int serverIndex);
|
||||
|
||||
void setCurrentProtocol(int serverIndex, const QString &protocolName);
|
||||
bool isVlessProtocol(int serverIndex) const;
|
||||
|
||||
ErrorCode getAccountInfo(int serverIndex, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(int serverIndex);
|
||||
|
||||
struct AppStoreRestoreResult
|
||||
{
|
||||
bool hasInstalledConfig = false;
|
||||
bool duplicateConfigAlreadyPresent = false;
|
||||
int duplicateCount = 0;
|
||||
int duplicateServerIndex = -1;
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
};
|
||||
|
||||
ErrorCode processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol);
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(int serverIndex) const;
|
||||
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
|
||||
void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &userCountryCode,
|
||||
const QByteArray &apiResponseBody);
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
};
|
||||
|
||||
#endif // SUBSCRIPTIONCONTROLLER_H
|
||||
|
||||
@@ -1,509 +0,0 @@
|
||||
#include "apiController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
#include "core/enums/apiEnums.h"
|
||||
#include "utilities.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
{
|
||||
constexpr char cloak[] = "cloak";
|
||||
constexpr char awg[] = "awg";
|
||||
|
||||
constexpr char apiEdnpoint[] = "api_endpoint";
|
||||
constexpr char accessToken[] = "api_key";
|
||||
constexpr char certificate[] = "certificate";
|
||||
constexpr char publicKey[] = "public_key";
|
||||
constexpr char protocol[] = "protocol";
|
||||
|
||||
constexpr char uuid[] = "installation_uuid";
|
||||
constexpr char osVersion[] = "os_version";
|
||||
constexpr char appVersion[] = "app_version";
|
||||
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serverCountryCode[] = "server_country_code";
|
||||
constexpr char serviceType[] = "service_type";
|
||||
constexpr char serviceInfo[] = "service_info";
|
||||
|
||||
constexpr char aesKey[] = "aes_key";
|
||||
constexpr char aesIv[] = "aes_iv";
|
||||
constexpr char aesSalt[] = "aes_salt";
|
||||
|
||||
constexpr char apiPayload[] = "api_payload";
|
||||
constexpr char keyPayload[] = "key_payload";
|
||||
|
||||
constexpr char apiConfig[] = "api_config";
|
||||
constexpr char authData[] = "auth_data";
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
{
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return ErrorCode::ApiConfigSslError;
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
return ErrorCode::NoError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
return ErrorCode::ApiConfigTimeoutError;
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "")
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "Timeout occurred";
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "The response contains an html tag";
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||
} catch (...) {
|
||||
qDebug() << "Failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
|
||||
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
||||
const QByteArray &apiResponseBody, QJsonObject &serverConfig)
|
||||
{
|
||||
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(ba);
|
||||
if (!ba_uncompressed.isEmpty()) {
|
||||
ba = ba_uncompressed;
|
||||
}
|
||||
|
||||
QString configStr = ba;
|
||||
if (protocol == configKey::cloak) {
|
||||
configStr.replace("<key>", "<key>\n");
|
||||
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||
} else if (protocol == configKey::awg) {
|
||||
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
auto containers = newServerConfig.value(config_key::containers).toArray();
|
||||
if (containers.isEmpty()) {
|
||||
return; // todo process error
|
||||
}
|
||||
auto container = containers.at(0).toObject();
|
||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||
auto containerConfig = container.value(containerName).toObject();
|
||||
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
|
||||
containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
|
||||
containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
|
||||
containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
|
||||
containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
|
||||
containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||
container[containerName] = containerConfig;
|
||||
containers.replace(0, container);
|
||||
newServerConfig[config_key::containers] = containers;
|
||||
configStr = QString(QJsonDocument(newServerConfig).toJson());
|
||||
}
|
||||
|
||||
QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
|
||||
serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
|
||||
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
|
||||
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
|
||||
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
|
||||
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
|
||||
serverConfig[config_key::name] = newServerConfig.value(config_key::name);
|
||||
}
|
||||
|
||||
auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
|
||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||
|
||||
QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
|
||||
map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
|
||||
auto apiConfig = QJsonObject::fromVariantMap(map);
|
||||
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject());
|
||||
}
|
||||
|
||||
serverConfig[configKey::apiConfig] = apiConfig;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList ApiController::getProxyUrls()
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrl;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
||||
} else {
|
||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
break;
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
||||
{
|
||||
ApiController::ApiPayloadData apiPayload;
|
||||
if (protocol == configKey::cloak) {
|
||||
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
|
||||
} else if (protocol == configKey::awg) {
|
||||
auto connData = WireguardConfigurator::genClientKeys();
|
||||
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
|
||||
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
|
||||
}
|
||||
return apiPayload;
|
||||
}
|
||||
|
||||
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
|
||||
{
|
||||
QJsonObject obj;
|
||||
if (protocol == configKey::cloak) {
|
||||
obj[configKey::certificate] = apiPayloadData.certRequest.request;
|
||||
} else if (protocol == configKey::awg) {
|
||||
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
||||
}
|
||||
|
||||
obj[configKey::osVersion] = QSysInfo::productType();
|
||||
obj[configKey::appVersion] = QString(APP_VERSION);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||
request.setUrl(endpoint);
|
||||
|
||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||
|
||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||
|
||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
|
||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
auto apiResponseBody = reply->readAll();
|
||||
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
||||
emit finished(serverConfig, serverIndex);
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
emit errorOccurred(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
||||
qDebug().noquote() << errors;
|
||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/services").arg(proxyUrl));
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!responseBody.contains("services")) {
|
||||
return ErrorCode::ApiServicesMissingError;
|
||||
}
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData,
|
||||
QJsonObject &serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
|
||||
|
||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||
|
||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||
if (!serverCountryCode.isEmpty()) {
|
||||
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||
}
|
||||
apiPayload[configKey::serviceType] = serviceType;
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
if (!authData.isEmpty()) {
|
||||
apiPayload[configKey::authData] = authData;
|
||||
}
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/config").arg(proxyUrl));
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
encryptedResponseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
try {
|
||||
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef APICONTROLLER_H
|
||||
#define APICONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurators/openvpn_configurator.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class ApiController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||
|
||||
ErrorCode getServicesList(QByteArray &responseBody);
|
||||
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig);
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
void finished(const QJsonObject &config, const int serverIndex);
|
||||
|
||||
private:
|
||||
struct ApiPayloadData
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData certRequest;
|
||||
|
||||
QString wireGuardClientPrivKey;
|
||||
QString wireGuardClientPubKey;
|
||||
};
|
||||
|
||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||
void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
|
||||
QJsonObject &serverConfig);
|
||||
QStringList getProxyUrls();
|
||||
|
||||
QString m_gatewayEndpoint;
|
||||
QStringList m_proxyUrls;
|
||||
bool m_isDevEnvironment = false;
|
||||
};
|
||||
|
||||
#endif // APICONTROLLER_H
|
||||
70
client/core/controllers/appSplitTunnelingController.cpp
Normal file
70
client/core/controllers/appSplitTunnelingController.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "appSplitTunnelingController.h"
|
||||
|
||||
AppSplitTunnelingController::AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository)
|
||||
: m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
m_currentRouteMode = m_appSettingsRepository->appsRouteMode();
|
||||
if (m_currentRouteMode == AppsRouteMode::VpnAllApps) { // for old split tunneling configs
|
||||
m_currentRouteMode = AppsRouteMode::VpnAllExceptApps;
|
||||
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
|
||||
m_appSettingsRepository->setAppsRouteMode(AppsRouteMode::VpnAllExceptApps);
|
||||
} else {
|
||||
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppSplitTunnelingController::addApp(const amnezia::InstalledAppInfo &appInfo)
|
||||
{
|
||||
if (m_apps.contains(appInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_apps.append(appInfo);
|
||||
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::removeApp(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_apps.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_apps.removeAt(index);
|
||||
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::clearAppsList()
|
||||
{
|
||||
m_apps.clear();
|
||||
m_appSettingsRepository->setVpnApps(m_currentRouteMode, m_apps);
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::setRouteMode(AppsRouteMode routeMode)
|
||||
{
|
||||
m_currentRouteMode = routeMode;
|
||||
m_apps = m_appSettingsRepository->vpnApps(m_currentRouteMode);
|
||||
m_appSettingsRepository->setAppsRouteMode(routeMode);
|
||||
}
|
||||
|
||||
void AppSplitTunnelingController::toggleSplitTunneling(bool enabled)
|
||||
{
|
||||
m_appSettingsRepository->setAppsSplitTunnelingEnabled(enabled);
|
||||
}
|
||||
|
||||
AppsRouteMode AppSplitTunnelingController::getRouteMode() const
|
||||
{
|
||||
return m_currentRouteMode;
|
||||
}
|
||||
|
||||
bool AppSplitTunnelingController::isSplitTunnelingEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isAppsSplitTunnelingEnabled();
|
||||
}
|
||||
|
||||
QVector<amnezia::InstalledAppInfo> AppSplitTunnelingController::getApps() const
|
||||
{
|
||||
return m_apps;
|
||||
}
|
||||
|
||||
32
client/core/controllers/appSplitTunnelingController.h
Normal file
32
client/core/controllers/appSplitTunnelingController.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef APPSPLITTUNNELINGCONTROLLER_H
|
||||
#define APPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class AppSplitTunnelingController
|
||||
{
|
||||
public:
|
||||
explicit AppSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
bool addApp(const amnezia::InstalledAppInfo &appInfo);
|
||||
void removeApp(int index);
|
||||
void clearAppsList();
|
||||
void setRouteMode(AppsRouteMode routeMode);
|
||||
void toggleSplitTunneling(bool enabled);
|
||||
|
||||
AppsRouteMode getRouteMode() const;
|
||||
bool isSplitTunnelingEnabled() const;
|
||||
QVector<amnezia::InstalledAppInfo> getApps() const;
|
||||
|
||||
private:
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
AppsRouteMode m_currentRouteMode;
|
||||
QVector<amnezia::InstalledAppInfo> m_apps;
|
||||
};
|
||||
|
||||
#endif // APPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
183
client/core/controllers/connectionController.cpp
Normal file
183
client/core/controllers/connectionController.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "connectionController.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "core/configurators/configuratorBase.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
|
||||
ConnectionController::ConnectionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
VpnConnection* vpnConnection,
|
||||
QObject* parent)
|
||||
: QObject(parent),
|
||||
m_serversRepository(serversRepository),
|
||||
m_appSettingsRepository(appSettingsRepository),
|
||||
m_vpnConnection(vpnConnection)
|
||||
{
|
||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ConnectionController::isConnected() const
|
||||
{
|
||||
return m_vpnConnection && m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected;
|
||||
}
|
||||
|
||||
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
emit setConnectionStateRequested(state);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(int serverIndex,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
container = serverConfigModel.defaultContainer();
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
auto dns = serverConfigModel.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, serverConfigModel, containerConfigModel, container);
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::openConnection(int serverIndex)
|
||||
{
|
||||
QJsonObject vpnConfiguration;
|
||||
DockerContainer container;
|
||||
|
||||
ErrorCode errorCode = prepareConnection(serverIndex, vpnConfiguration, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
emit openConnectionRequested(serverIndex, container, vpnConfiguration);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void ConnectionController::closeConnection()
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
emit closeConnectionRequested();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void ConnectionController::restoreConnection()
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
emit restoreConnectionRequested();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ConnectionController::onKillSwitchModeChanged(bool enabled)
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
emit killSwitchModeChangedRequested(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::lastConnectionError() const
|
||||
{
|
||||
return m_vpnConnection->lastError();
|
||||
}
|
||||
|
||||
QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QString, QString> &dns,
|
||||
const ServerConfig &serverConfig,
|
||||
const ContainerConfig &containerConfig,
|
||||
DockerContainer container)
|
||||
{
|
||||
QJsonObject vpnConfiguration {};
|
||||
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
return vpnConfiguration;
|
||||
}
|
||||
|
||||
Proto proto = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
ConnectionSettings connectionSettings = {
|
||||
{ dns.first, dns.second },
|
||||
serverConfig.isApiConfig(),
|
||||
{
|
||||
m_appSettingsRepository->isSitesSplitTunnelingEnabled(),
|
||||
m_appSettingsRepository->routeMode()
|
||||
}
|
||||
};
|
||||
|
||||
auto configurator = ConfiguratorBase::create(proto, nullptr);
|
||||
ProtocolConfig processedConfig = configurator->processConfigWithLocalSettings(connectionSettings,
|
||||
containerConfig.protocolConfig);
|
||||
|
||||
QJsonObject vpnConfigData = processedConfig.getClientConfigJson();
|
||||
if (ContainerUtils::isAwgContainer(container) || container == DockerContainer::WireGuard) {
|
||||
if (vpnConfigData[configKey::mtu].toString().isEmpty()) {
|
||||
vpnConfigData[configKey::mtu] =
|
||||
ContainerUtils::isAwgContainer(container) ? protocols::awg::defaultMtu :
|
||||
protocols::wireguard::defaultMtu;
|
||||
}
|
||||
}
|
||||
|
||||
vpnConfiguration.insert(ProtocolUtils::key_proto_config_data(proto), vpnConfigData);
|
||||
vpnConfiguration[configKey::vpnProto] = ProtocolUtils::protoToString(proto);
|
||||
|
||||
vpnConfiguration[configKey::dns1] = dns.first;
|
||||
vpnConfiguration[configKey::dns2] = dns.second;
|
||||
|
||||
vpnConfiguration[configKey::hostName] = serverConfig.hostName();
|
||||
vpnConfiguration[configKey::description] = serverConfig.description();
|
||||
|
||||
vpnConfiguration[configKey::configVersion] = serverConfig.configVersion();
|
||||
|
||||
return vpnConfiguration;
|
||||
}
|
||||
|
||||
bool ConnectionController::isServiceReady() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
return Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ConnectionController::isContainerSupported(DockerContainer container) const
|
||||
{
|
||||
return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
}
|
||||
78
client/core/controllers/connectionController.h
Normal file
78
client/core/controllers/connectionController.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef CONNECTIONCONTROLLER_H
|
||||
#define CONNECTIONCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QPair>
|
||||
#include <memory>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/protocols/vpnProtocol.h"
|
||||
#include "vpnConnection.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class ConnectionController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConnectionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
VpnConnection* vpnConnection,
|
||||
QObject* parent = nullptr);
|
||||
~ConnectionController() = default;
|
||||
|
||||
ErrorCode prepareConnection(int serverIndex,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode openConnection(int serverIndex);
|
||||
|
||||
void closeConnection();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnection();
|
||||
#endif
|
||||
|
||||
void onKillSwitchModeChanged(bool enabled);
|
||||
|
||||
ErrorCode lastConnectionError() const;
|
||||
|
||||
bool isConnected() const;
|
||||
void setConnectionState(Vpn::ConnectionState state);
|
||||
|
||||
QJsonObject createConnectionConfiguration(const QPair<QString, QString> &dns,
|
||||
const ServerConfig &serverConfig,
|
||||
const ContainerConfig &containerConfig,
|
||||
DockerContainer container);
|
||||
|
||||
bool isServiceReady() const;
|
||||
|
||||
bool isContainerSupported(DockerContainer container) const;
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
#endif
|
||||
|
||||
private:
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
};
|
||||
|
||||
#endif
|
||||
338
client/core/controllers/coreController.cpp
Normal file
338
client/core/controllers/coreController.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "coreController.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTranslator>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/selfhosted/importController.h"
|
||||
#include "core/controllers/coreSignalHandlers.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "logger.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/utils/installedAppsImageProvider.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent)
|
||||
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||
{
|
||||
initRepositories();
|
||||
initCoreControllers();
|
||||
initModels();
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
initLogging();
|
||||
|
||||
m_translator = new QTranslator(this);
|
||||
if (m_appSettingsRepository) {
|
||||
updateTranslator(m_appSettingsRepository->getAppLanguage());
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::setQmlContextProperty(const QString &name, QObject *value)
|
||||
{
|
||||
if (m_engine) {
|
||||
m_engine->rootContext()->setContextProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
{
|
||||
m_containersModel = new ContainersModel(this);
|
||||
setQmlContextProperty("ContainersModel", m_containersModel);
|
||||
|
||||
m_defaultServerContainersModel = new ContainersModel(this);
|
||||
setQmlContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel);
|
||||
|
||||
m_serversModel = new ServersModel(this);
|
||||
setQmlContextProperty("ServersModel", m_serversModel);
|
||||
|
||||
m_languageModel = new LanguageModel(this);
|
||||
setQmlContextProperty("LanguageModel", m_languageModel);
|
||||
|
||||
m_ipSplitTunnelingModel = new IpSplitTunnelingModel(this);
|
||||
setQmlContextProperty("IpSplitTunnelingModel", m_ipSplitTunnelingModel);
|
||||
|
||||
m_allowedDnsModel = new AllowedDnsModel(this);
|
||||
setQmlContextProperty("AllowedDnsModel", m_allowedDnsModel);
|
||||
|
||||
m_appSplitTunnelingModel = new AppSplitTunnelingModel(this);
|
||||
setQmlContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel);
|
||||
|
||||
m_protocolsModel = new ProtocolsModel(this);
|
||||
setQmlContextProperty("ProtocolsModel", m_protocolsModel);
|
||||
|
||||
m_openVpnConfigModel = new OpenVpnConfigModel(this);
|
||||
setQmlContextProperty("OpenVpnConfigModel", m_openVpnConfigModel);
|
||||
|
||||
m_wireGuardConfigModel = new WireGuardConfigModel(this);
|
||||
setQmlContextProperty("WireGuardConfigModel", m_wireGuardConfigModel);
|
||||
|
||||
m_awgConfigModel = new AwgConfigModel(this);
|
||||
setQmlContextProperty("AwgConfigModel", m_awgConfigModel);
|
||||
|
||||
m_xrayConfigModel = new XrayConfigModel(this);
|
||||
setQmlContextProperty("XrayConfigModel", m_xrayConfigModel);
|
||||
|
||||
m_torConfigModel = new TorConfigModel(this);
|
||||
setQmlContextProperty("TorConfigModel", m_torConfigModel);
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel = new Ikev2ConfigModel(this);
|
||||
setQmlContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel);
|
||||
#endif
|
||||
|
||||
m_sftpConfigModel = new SftpConfigModel(this);
|
||||
setQmlContextProperty("SftpConfigModel", m_sftpConfigModel);
|
||||
|
||||
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
|
||||
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
|
||||
|
||||
m_clientManagementModel = new ClientManagementModel(this);
|
||||
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
|
||||
|
||||
m_apiServicesModel = new ApiServicesModel(this);
|
||||
setQmlContextProperty("ApiServicesModel", m_apiServicesModel);
|
||||
|
||||
m_apiCountryModel = new ApiCountryModel(this);
|
||||
setQmlContextProperty("ApiCountryModel", m_apiCountryModel);
|
||||
|
||||
m_apiSubscriptionPlansModel = new ApiSubscriptionPlansModel(this);
|
||||
setQmlContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel);
|
||||
|
||||
m_apiBenefitsModel = new ApiBenefitsModel(this);
|
||||
setQmlContextProperty("ApiBenefitsModel", m_apiBenefitsModel);
|
||||
|
||||
m_apiAccountInfoModel = new ApiAccountInfoModel(this);
|
||||
setQmlContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel);
|
||||
|
||||
m_apiDevicesModel = new ApiDevicesModel(this);
|
||||
setQmlContextProperty("ApiDevicesModel", m_apiDevicesModel);
|
||||
|
||||
m_newsModel = new NewsModel(m_appSettingsRepository, this);
|
||||
setQmlContextProperty("NewsModel", m_newsModel);
|
||||
}
|
||||
|
||||
void CoreController::initRepositories()
|
||||
{
|
||||
m_serversRepository = new SecureServersRepository(m_settings, this);
|
||||
m_appSettingsRepository = new SecureAppSettingsRepository(m_settings, this);
|
||||
|
||||
if (m_vpnConnection) {
|
||||
m_vpnConnection->setRepositories(m_serversRepository, m_appSettingsRepository);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::initCoreControllers()
|
||||
{
|
||||
m_serversController = new ServersController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_appSplitTunnelingController = new AppSplitTunnelingController(m_appSettingsRepository);
|
||||
m_usersController = new UsersController(m_serversRepository, this);
|
||||
m_ipSplitTunnelingController = new IpSplitTunnelingController(m_appSettingsRepository, this);
|
||||
m_allowedDnsController = new AllowedDnsController(m_appSettingsRepository);
|
||||
m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository);
|
||||
m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository);
|
||||
m_newsController = new NewsController(m_appSettingsRepository, m_serversController);
|
||||
|
||||
m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_exportController = new ExportController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_importCoreController = new ImportController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_connectionController = new ConnectionController(m_serversRepository, m_appSettingsRepository, m_vpnConnection.get(), this);
|
||||
m_settingsController = new SettingsController(m_serversRepository, m_appSettingsRepository, this);
|
||||
}
|
||||
|
||||
void CoreController::initControllers()
|
||||
{
|
||||
m_connectionUiController = new ConnectionUiController(m_connectionController, m_serversController, this);
|
||||
setQmlContextProperty("ConnectionController", m_connectionUiController);
|
||||
|
||||
if (m_engine) {
|
||||
m_focusController = new FocusController(m_engine, this);
|
||||
setQmlContextProperty("FocusController", m_focusController);
|
||||
}
|
||||
|
||||
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
|
||||
m_awgConfigModel, m_wireGuardConfigModel, m_openVpnConfigModel, m_xrayConfigModel, m_torConfigModel,
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel,
|
||||
#endif
|
||||
m_sftpConfigModel, m_socks5ConfigModel, this);
|
||||
setQmlContextProperty("InstallController", m_installUiController);
|
||||
|
||||
m_importController = new ImportUiController(m_importCoreController, this);
|
||||
setQmlContextProperty("ImportController", m_importController);
|
||||
|
||||
m_exportUiController = new ExportUiController(m_exportController, this);
|
||||
setQmlContextProperty("ExportController", m_exportUiController);
|
||||
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
setQmlContextProperty("PageController", m_pageController);
|
||||
|
||||
m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this);
|
||||
setQmlContextProperty("ServersUiController", m_serversUiController);
|
||||
|
||||
m_ipSplitTunnelingUiController = new IpSplitTunnelingUiController(m_ipSplitTunnelingController, m_ipSplitTunnelingModel, this);
|
||||
setQmlContextProperty("IpSplitTunnelingController", m_ipSplitTunnelingUiController);
|
||||
|
||||
m_allowedDnsUiController = new AllowedDnsUiController(m_allowedDnsController, m_allowedDnsModel, this);
|
||||
setQmlContextProperty("AllowedDnsController", m_allowedDnsUiController);
|
||||
|
||||
m_appSplitTunnelingUiController = new AppSplitTunnelingUiController(m_appSplitTunnelingController, m_appSplitTunnelingModel, this);
|
||||
setQmlContextProperty("AppSplitTunnelingController", m_appSplitTunnelingUiController);
|
||||
|
||||
m_systemController = new SystemController(this);
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
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, this);
|
||||
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!AndroidController::initLogging()) {
|
||||
qFatal("Android logging initialization failed");
|
||||
}
|
||||
AndroidController::instance()->setSaveLogs(m_appSettingsRepository->isSaveLogs());
|
||||
AndroidController::instance()->setScreenshotsEnabled(m_appSettingsRepository->isScreenshotsEnabled());
|
||||
|
||||
if (!AndroidController::instance()->initialize()) {
|
||||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initAppleController()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initLogging()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
bool enabled = m_appSettingsRepository->isSaveLogs();
|
||||
if (enabled) {
|
||||
if (!Logger::init(false)) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
}
|
||||
}
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initSignalHandlers()
|
||||
{
|
||||
m_signalHandlers = new CoreSignalHandlers(this, this);
|
||||
m_signalHandlers->initAllHandlers();
|
||||
|
||||
// Trigger initial update after handlers are connected
|
||||
m_serversUiController->updateModel();
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator);
|
||||
}
|
||||
|
||||
QStringList availableTranslations;
|
||||
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/amneziavpn_") + lang;
|
||||
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||
for (const QString &translation : availableTranslations) {
|
||||
if (translation.contains(translationFilePrefix)) {
|
||||
strFileName = translation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_translator->load(strFileName)) {
|
||||
QCoreApplication::installTranslator(m_translator);
|
||||
} else {
|
||||
if (m_translator->load(QString(":/translations/amneziavpn_en.qm"))) {
|
||||
QCoreApplication::installTranslator(m_translator);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
m_engine->retranslate();
|
||||
}
|
||||
|
||||
emit translationsUpdated();
|
||||
if (m_languageUiController) {
|
||||
emit websiteUrlChanged(m_languageUiController->getCurrentSiteUrl());
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::setQmlRoot()
|
||||
{
|
||||
if (m_engine && m_systemController) {
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
}
|
||||
}
|
||||
|
||||
PageController* CoreController::pageController() const
|
||||
{
|
||||
return m_pageController;
|
||||
}
|
||||
|
||||
void CoreController::openConnectionByIndex(int serverIndex)
|
||||
{
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
}
|
||||
if (m_serversController) {
|
||||
m_serversController->setDefaultServerIndex(serverIndex);
|
||||
}
|
||||
m_connectionUiController->toggleConnection();
|
||||
}
|
||||
|
||||
void CoreController::importConfigFromData(const QString &data)
|
||||
{
|
||||
if (!m_importController)
|
||||
return;
|
||||
|
||||
if (m_importController->extractConfigFromData(data)) {
|
||||
m_importController->importConfig();
|
||||
}
|
||||
}
|
||||
213
client/core/controllers/coreController.h
Normal file
213
client/core/controllers/coreController.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#ifndef CORECONTROLLER_H
|
||||
#define CORECONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/utils/systemTrayNotificationHandler.h"
|
||||
#endif
|
||||
|
||||
#include "ui/controllers/api/subscriptionUiController.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/controllers/appSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/allowedDnsUiController.h"
|
||||
#include "ui/controllers/connectionUiController.h"
|
||||
#include "ui/controllers/selfhosted/exportUiController.h"
|
||||
#include "core/controllers/selfhosted/exportController.h"
|
||||
#include "ui/controllers/qml/focusController.h"
|
||||
#include "ui/controllers/importUiController.h"
|
||||
#include "core/controllers/selfhosted/importController.h"
|
||||
#include "ui/controllers/selfhosted/installUiController.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
#include "ui/controllers/serversUiController.h"
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/selfhosted/usersController.h"
|
||||
#include "core/controllers/appSplitTunnelingController.h"
|
||||
#include "core/controllers/ipSplitTunnelingController.h"
|
||||
#include "core/controllers/allowedDnsController.h"
|
||||
#include "core/controllers/api/servicesCatalogController.h"
|
||||
#include "core/controllers/api/subscriptionController.h"
|
||||
#include "core/controllers/api/newsController.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
#include "ui/models/allowedDnsModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/api/apiAccountInfoModel.h"
|
||||
#include "ui/models/api/apiBenefitsModel.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/api/apiDevicesModel.h"
|
||||
#include "ui/models/api/apiServicesModel.h"
|
||||
#include "ui/models/api/apiSubscriptionPlansModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.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/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestGatewayStacks;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestGatewayStacks;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||
|
||||
PageController* pageController() const;
|
||||
void setQmlRoot();
|
||||
|
||||
void openConnectionByIndex(int serverIndex);
|
||||
void importConfigFromData(const QString &data);
|
||||
void updateTranslator(const QLocale &locale);
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
private:
|
||||
void initRepositories();
|
||||
void initCoreControllers();
|
||||
void initModels();
|
||||
void initControllers();
|
||||
void initAndroidController();
|
||||
void initAppleController();
|
||||
void initLogging();
|
||||
void initSignalHandlers();
|
||||
void setQmlContextProperty(const QString &name, QObject *value);
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
SecureQSettings* m_settings;
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QTranslator* m_translator;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
NotificationHandler* m_notificationHandler;
|
||||
#endif
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
|
||||
ConnectionUiController* m_connectionUiController;
|
||||
FocusController* m_focusController;
|
||||
PageController* m_pageController;
|
||||
InstallUiController* m_installUiController;
|
||||
ImportUiController* m_importController;
|
||||
ImportController* m_importCoreController;
|
||||
ExportUiController* m_exportUiController;
|
||||
SettingsUiController* m_settingsUiController;
|
||||
ServersUiController* m_serversUiController;
|
||||
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
|
||||
SystemController* m_systemController;
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
|
||||
SubscriptionUiController* m_subscriptionUiController;
|
||||
ApiNewsUiController* m_apiNewsUiController;
|
||||
|
||||
ServicesCatalogUiController* m_servicesCatalogUiController;
|
||||
|
||||
ServersController* m_serversController;
|
||||
UsersController* m_usersController;
|
||||
AppSplitTunnelingController* m_appSplitTunnelingController;
|
||||
IpSplitTunnelingController* m_ipSplitTunnelingController;
|
||||
AllowedDnsController* m_allowedDnsController;
|
||||
ServicesCatalogController* m_servicesCatalogController;
|
||||
SubscriptionController* m_subscriptionController;
|
||||
NewsController* m_newsController;
|
||||
InstallController* m_installController;
|
||||
ExportController* m_exportController;
|
||||
ConnectionController* m_connectionController;
|
||||
SettingsController* m_settingsController;
|
||||
|
||||
ContainersModel* m_containersModel;
|
||||
ContainersModel* m_defaultServerContainersModel;
|
||||
ServersModel* m_serversModel;
|
||||
LanguageModel* m_languageModel;
|
||||
ProtocolsModel* m_protocolsModel;
|
||||
IpSplitTunnelingModel* m_ipSplitTunnelingModel;
|
||||
NewsModel* m_newsModel;
|
||||
AllowedDnsModel* m_allowedDnsModel;
|
||||
AppSplitTunnelingModel* m_appSplitTunnelingModel;
|
||||
ClientManagementModel* m_clientManagementModel;
|
||||
|
||||
ApiServicesModel* m_apiServicesModel;
|
||||
ApiSubscriptionPlansModel* m_apiSubscriptionPlansModel;
|
||||
ApiBenefitsModel* m_apiBenefitsModel;
|
||||
ApiCountryModel* m_apiCountryModel;
|
||||
ApiAccountInfoModel* m_apiAccountInfoModel;
|
||||
ApiDevicesModel* m_apiDevicesModel;
|
||||
|
||||
OpenVpnConfigModel* m_openVpnConfigModel;
|
||||
XrayConfigModel* m_xrayConfigModel;
|
||||
TorConfigModel* m_torConfigModel;
|
||||
WireGuardConfigModel* m_wireGuardConfigModel;
|
||||
AwgConfigModel* m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
Ikev2ConfigModel* m_ikev2ConfigModel;
|
||||
#endif
|
||||
SftpConfigModel* m_sftpConfigModel;
|
||||
Socks5ProxyConfigModel* m_socks5ConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
412
client/core/controllers/coreSignalHandlers.cpp
Normal file
412
client/core/controllers/coreSignalHandlers.cpp
Normal file
@@ -0,0 +1,412 @@
|
||||
#include "coreSignalHandlers.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/controllers/connectionUiController.h"
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
#include "ui/controllers/serversUiController.h"
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/allowedDnsUiController.h"
|
||||
#include "ui/controllers/appSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/selfhosted/installUiController.h"
|
||||
#include "ui/controllers/importUiController.h"
|
||||
#include "ui/controllers/api/subscriptionUiController.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/ipSplitTunnelingController.h"
|
||||
#include "core/controllers/appSplitTunnelingController.h"
|
||||
#include "core/controllers/selfhosted/usersController.h"
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/selfhosted/exportController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#include "ui/utils/systemTrayNotificationHandler.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
CoreSignalHandlers::CoreSignalHandlers(CoreController* coreController, QObject* parent)
|
||||
: QObject(parent),
|
||||
m_coreController(coreController)
|
||||
{
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAllHandlers()
|
||||
{
|
||||
initErrorMessagesHandler();
|
||||
initSettingsSplitTunnelingHandler();
|
||||
initInstallControllerHandler();
|
||||
initExportControllerHandler();
|
||||
initImportControllerHandler();
|
||||
initApiCountryModelUpdateHandler();
|
||||
initSubscriptionRefreshHandler();
|
||||
initContainerModelUpdateHandler();
|
||||
initAdminConfigRevokedHandler();
|
||||
initPassphraseRequestHandler();
|
||||
initTranslationsUpdatedHandler();
|
||||
initLanguageHandler();
|
||||
initAutoConnectHandler();
|
||||
initAmneziaDnsToggledHandler();
|
||||
initServersModelUpdateHandler();
|
||||
initClientManagementModelUpdateHandler();
|
||||
initSitesModelUpdateHandler();
|
||||
initAllowedDnsModelUpdateHandler();
|
||||
initAppSplitTunnelingModelUpdateHandler();
|
||||
initPrepareConfigHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
initAndroidSettingsHandler();
|
||||
initAndroidConnectionHandler();
|
||||
initIosImportHandler();
|
||||
initIosSettingsHandler();
|
||||
initNotificationHandler();
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
{
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
emit m_coreController->m_pageController->showErrorMessage(errorCode);
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
|
||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::errorOccurred, m_coreController->m_pageController,
|
||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initSettingsSplitTunnelingHandler()
|
||||
{
|
||||
connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingRouteModeChanged, this, [this](RouteMode mode) {
|
||||
m_coreController->m_ipSplitTunnelingController->setRouteMode(mode);
|
||||
});
|
||||
connect(m_coreController->m_settingsController, &SettingsController::siteSplitTunnelingToggled, this, [this](bool enabled) {
|
||||
m_coreController->m_ipSplitTunnelingController->toggleSplitTunneling(enabled);
|
||||
});
|
||||
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingRouteModeChanged, this, [this](AppsRouteMode mode) {
|
||||
m_coreController->m_appSplitTunnelingController->setRouteMode(mode);
|
||||
});
|
||||
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingToggled, this, [this](bool enabled) {
|
||||
m_coreController->m_appSplitTunnelingController->toggleSplitTunneling(enabled);
|
||||
});
|
||||
connect(m_coreController->m_settingsController, &SettingsController::appSplitTunnelingClearAppsList, this, [this]() {
|
||||
m_coreController->m_appSplitTunnelingController->clearAppsList();
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initInstallControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::currentContainerUpdated, m_coreController->m_connectionUiController,
|
||||
&ConnectionUiController::onCurrentContainerUpdated);
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
|
||||
m_coreController->m_installUiController, [this](int index) {
|
||||
if (index >= 0) {
|
||||
m_coreController->m_installUiController->clearProcessedServerCredentials();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initExportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this,
|
||||
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this,
|
||||
[this](int serverIndex, DockerContainer container) {
|
||||
m_coreController->m_usersController->updateClients(serverIndex, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](int serverIndex, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverIndex, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](int serverIndex, int row, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->renameClient(serverIndex, row, clientName, container);
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
m_coreController->m_serversController->setDefaultServerIndex(newServerIndex);
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerIndex(newServerIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() {
|
||||
int processedIndex = m_coreController->m_serversUiController->getProcessedServerIndex();
|
||||
if (processedIndex < 0 || processedIndex >= m_coreController->m_serversRepository->serversCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerConfig server = m_coreController->m_serversRepository->server(processedIndex);
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
if (server.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = server.as<ApiV2ServerConfig>();
|
||||
if (apiV2) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initSubscriptionRefreshHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::subscriptionRefreshNeeded, this, [this]() {
|
||||
const int defaultServerIndex = m_coreController->m_serversController->getDefaultServerIndex();
|
||||
if (defaultServerIndex >= 0) {
|
||||
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerIndex, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initContainerModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded, this, [this]() {
|
||||
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](int serverIndex, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverIndex, containerConfig, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_serversController,
|
||||
&ServersController::clearCachedProfile);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initPassphraseRequestHandler()
|
||||
{
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::passphraseRequestStarted, m_coreController->m_pageController,
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
connect(m_coreController->m_pageController, &PageController::passphraseRequestDrawerClosed, m_coreController->m_installUiController,
|
||||
&InstallUiController::setEncryptedPassphrase);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initTranslationsUpdatedHandler()
|
||||
{
|
||||
connect(m_coreController->m_languageUiController, &LanguageUiController::updateTranslations, m_coreController, &CoreController::updateTranslator);
|
||||
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated);
|
||||
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_connectionUiController, &ConnectionUiController::onTranslationsUpdated);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initLanguageHandler()
|
||||
{
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appLanguageChanged, m_coreController->m_languageUiController, &LanguageUiController::onAppLanguageChanged);
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled() && m_coreController->m_serversController->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAmneziaDnsToggledHandler()
|
||||
{
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::useAmneziaDnsChanged, m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initServersModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged,
|
||||
m_coreController->m_serversUiController, &ServersUiController::onDefaultServerChanged);
|
||||
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished,
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initClientManagementModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_usersController, &UsersController::clientsUpdated,
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
|
||||
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initSitesModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::sitesSplitTunnelingEnabledChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::routeModeChanged, m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::updateModel);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAllowedDnsModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::allowedDnsServersChanged, m_coreController->m_allowedDnsUiController, &AllowedDnsUiController::updateModel);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAppSplitTunnelingModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsSplitTunnelingEnabledChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::appsRouteModeChanged, m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::updateModel);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initPrepareConfigHandler()
|
||||
{
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::prepareConfig, this, [this]() {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
|
||||
m_coreController->m_subscriptionUiController->validateConfig();
|
||||
});
|
||||
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::configValidated, this, [this](bool isValid) {
|
||||
if (!isValid) {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_installUiController->validateConfig();
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::configValidated, this, [this](bool isValid) {
|
||||
if (!isValid) {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_connectionUiController->openConnection();
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
{
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController,
|
||||
&ConnectionController::onKillSwitchModeChanged);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAndroidSettingsHandler()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAndroidConnectionHandler()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||
m_coreController->m_connectionUiController->onConnectionStateChanged(state);
|
||||
m_coreController->m_connectionController->restoreConnection();
|
||||
});
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
m_coreController->m_importController->extractConfigFromData(data);
|
||||
data.clear();
|
||||
emit m_coreController->m_pageController->goToPageViewConfig();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initIosImportHandler()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
m_coreController->m_importController->extractConfigFromData(data);
|
||||
emit m_coreController->m_pageController->goToPageViewConfig();
|
||||
});
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
m_coreController->m_pageController->goToPageSettingsBackup();
|
||||
emit m_coreController->m_settingsUiController->importBackupFromOutside(filePath);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initIosSettingsHandler()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initNotificationHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_coreController->m_notificationHandler = NotificationHandler::create(m_coreController);
|
||||
|
||||
connect(m_coreController->m_connectionController, &ConnectionController::connectionStateChanged, m_coreController->m_notificationHandler,
|
||||
&NotificationHandler::setConnectionState);
|
||||
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::raiseRequested, m_coreController->m_pageController, &PageController::raiseMainWindow);
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::connectRequested, m_coreController->m_connectionUiController,
|
||||
static_cast<void (ConnectionUiController::*)()>(&ConnectionUiController::openConnection));
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::disconnectRequested, m_coreController->m_connectionUiController,
|
||||
&ConnectionUiController::closeConnection);
|
||||
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
|
||||
|
||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
|
||||
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
48
client/core/controllers/coreSignalHandlers.h
Normal file
48
client/core/controllers/coreSignalHandlers.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef CORESIGNALHANDLERS_H
|
||||
#define CORESIGNALHANDLERS_H
|
||||
|
||||
#include <QObject>
|
||||
#include "core/controllers/coreController.h"
|
||||
|
||||
class CoreSignalHandlers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoreSignalHandlers(CoreController* coreController, QObject* parent = nullptr);
|
||||
|
||||
void initAllHandlers();
|
||||
|
||||
private:
|
||||
void initErrorMessagesHandler();
|
||||
void initSettingsSplitTunnelingHandler();
|
||||
void initInstallControllerHandler();
|
||||
void initExportControllerHandler();
|
||||
void initImportControllerHandler();
|
||||
void initApiCountryModelUpdateHandler();
|
||||
void initSubscriptionRefreshHandler();
|
||||
void initContainerModelUpdateHandler();
|
||||
void initAdminConfigRevokedHandler();
|
||||
void initPassphraseRequestHandler();
|
||||
void initTranslationsUpdatedHandler();
|
||||
void initLanguageHandler();
|
||||
void initAutoConnectHandler();
|
||||
void initAmneziaDnsToggledHandler();
|
||||
void initServersModelUpdateHandler();
|
||||
void initClientManagementModelUpdateHandler();
|
||||
void initSitesModelUpdateHandler();
|
||||
void initAllowedDnsModelUpdateHandler();
|
||||
void initAppSplitTunnelingModelUpdateHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
void initAndroidSettingsHandler();
|
||||
void initAndroidConnectionHandler();
|
||||
void initIosImportHandler();
|
||||
void initIosSettingsHandler();
|
||||
void initNotificationHandler();
|
||||
|
||||
CoreController* m_coreController;
|
||||
};
|
||||
|
||||
#endif // CORESIGNALHANDLERS_H
|
||||
|
||||
702
client/core/controllers/gatewayController.cpp
Normal file
702
client/core/controllers/gatewayController.cpp
Normal file
@@ -0,0 +1,702 @@
|
||||
#include "gatewayController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/utils/ipcClient.h"
|
||||
#endif
|
||||
|
||||
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 updateRequestResponsePattern("client version update is required");
|
||||
|
||||
constexpr int httpStatusCodeNotFound = 404;
|
||||
constexpr int httpStatusCodeConflict = 409;
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
constexpr int httpStatusCodePaymentRequired = 402;
|
||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
|
||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isDevEnvironment(isDevEnvironment),
|
||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||
{
|
||||
EncryptedRequestData encRequestData;
|
||||
encRequestData.errorCode = ErrorCode::NoError;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(encRequestData.request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
||||
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
||||
"", encRequestData.salt);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[apiDefs::key::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[apiDefs::key::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
|
||||
QNetworkReply::NetworkError replyError, const QByteArray &key,
|
||||
const QByteArray &iv, const QByteArray &salt)
|
||||
{
|
||||
DecryptionResult result;
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
result.isDecryptionSuccessful = true;
|
||||
} catch (...) {
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||
encRequestData.request.setUrl(url);
|
||||
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = reply->readAll();
|
||||
replyErrorString = reply->errorString();
|
||||
replyError = reply->error();
|
||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
if (!sslErrors.isEmpty()
|
||||
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (!decryptionResult.isDecryptionSuccessful) {
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||
{
|
||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||
promise->start();
|
||||
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
|
||||
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||
const QString &replyErrorString, int httpStatusCode) {
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||
decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decryptionResult.isDecryptionSuccessful) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
|
||||
promise->finish();
|
||||
};
|
||||
|
||||
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
|
||||
QStringList primaryBaseUrls;
|
||||
QStringList fallbackBaseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
} else {
|
||||
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
}
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||
|
||||
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
target.push_back(baseUrl + "endpoints.json");
|
||||
}
|
||||
};
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
||||
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
||||
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
||||
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||
const QString &replyErrorString, int httpStatusCode) {
|
||||
GatewayController::DecryptionResult result;
|
||||
result.decryptedBody = decryptedBody;
|
||||
result.isDecryptionSuccessful = isDecryptionSuccessful;
|
||||
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
}
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList primaryBaseUrls;
|
||||
QStringList fallbackBaseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
} else {
|
||||
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||
|
||||
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) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
target.push_back(baseUrl + "endpoints.json");
|
||||
}
|
||||
};
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
|
||||
if (proxyStorageUrls.empty()) {
|
||||
qDebug() << "empty storage endpoint list";
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << replyError;
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||
bool isDecryptionSuccessful)
|
||||
{
|
||||
const QByteArray &responseBody = decryptedResponseBody;
|
||||
|
||||
int apiHttpStatus = -1;
|
||||
QString apiErrorMessage;
|
||||
if (isDecryptionSuccessful) {
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
apiHttpStatus = jsonObj.value("http_status").toInt(-1);
|
||||
apiErrorMessage = jsonObj.value(QStringLiteral("message")).toString().trimmed();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "timeout occurred";
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
if (responseBody.contains("html")) {
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotFound) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeConflict) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodePaymentRequired) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeUnprocessableEntity) {
|
||||
return apiErrorMessage != unprocessableSubscriptionMessage;
|
||||
}
|
||||
if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||
{
|
||||
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||
|
||||
QByteArray responseBody;
|
||||
|
||||
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
auto result = replyProcessingFunction(reply, sslErrors);
|
||||
reply->deleteLater();
|
||||
return result;
|
||||
};
|
||||
|
||||
if (m_proxyUrl.isEmpty()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
request.setUrl(proxyUrl + "lmbd-health");
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
|
||||
m_proxyUrl = proxyUrl;
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
m_proxyUrl = proxyUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete({});
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
responseBody = encrypted;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error decrypting payload";
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
|
||||
std::function<void(const QString &)> onComplete)
|
||||
{
|
||||
if (currentProxyIndex >= proxyUrls.size()) {
|
||||
onComplete("");
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
|
||||
// *(state->sslErrors) = e;
|
||||
// });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
m_proxyUrl = proxyUrls[currentProxyIndex];
|
||||
onComplete(m_proxyUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
|
||||
{
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
if (proxyUrl.isEmpty()) {
|
||||
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request = encRequestData.request;
|
||||
request.setUrl(endpoint.arg(proxyUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
|
||||
httpStatusCode);
|
||||
});
|
||||
}
|
||||
72
client/core/controllers/gatewayController.h
Normal file
72
client/core/controllers/gatewayController.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef GATEWAYCONTROLLER_H
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPromise>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class GatewayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
|
||||
private:
|
||||
struct EncryptedRequestData
|
||||
{
|
||||
QNetworkRequest request;
|
||||
QByteArray requestBody;
|
||||
QByteArray key;
|
||||
QByteArray iv;
|
||||
QByteArray salt;
|
||||
amnezia::ErrorCode errorCode;
|
||||
};
|
||||
|
||||
struct DecryptionResult
|
||||
{
|
||||
QByteArray decryptedBody;
|
||||
bool isDecryptionSuccessful;
|
||||
};
|
||||
|
||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
|
||||
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
|
||||
|
||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
|
||||
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete);
|
||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||
void bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
|
||||
|
||||
int m_requestTimeoutMsecs;
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
#endif // GATEWAYCONTROLLER_H
|
||||
245
client/core/controllers/ipSplitTunnelingController.cpp
Normal file
245
client/core/controllers/ipSplitTunnelingController.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#include "ipSplitTunnelingController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include <QJsonObject>
|
||||
|
||||
IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent)
|
||||
: QObject(parent),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
m_currentRouteMode = m_appSettingsRepository->routeMode();
|
||||
if (m_currentRouteMode == RouteMode::VpnAllSites) { // for old split tunneling configs
|
||||
m_appSettingsRepository->setRouteMode(RouteMode::VpnOnlyForwardSites);
|
||||
m_currentRouteMode = RouteMode::VpnOnlyForwardSites;
|
||||
}
|
||||
fillSites();
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip)
|
||||
{
|
||||
QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
if (existing.contains(hostname) && ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
|
||||
{
|
||||
if (replaceExisting) {
|
||||
m_sites.clear();
|
||||
}
|
||||
for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) {
|
||||
const QString &hostname = it.key();
|
||||
const QString &ip = it.value();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
if (!ip.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
}
|
||||
}
|
||||
if (replaceExisting) {
|
||||
m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode);
|
||||
}
|
||||
m_appSettingsRepository->addVpnSites(m_currentRouteMode, sites);
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::addSite(const QString &hostname)
|
||||
{
|
||||
QString normalizedHostname = normalizeHostname(hostname);
|
||||
|
||||
if (!validateHostname(normalizedHostname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) {
|
||||
processSite(normalizedHostname, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addSiteInternal(normalizedHostname, "")) {
|
||||
QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo)));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::removeSite(const QString &hostname)
|
||||
{
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
m_sites.removeAt(i);
|
||||
m_appSettingsRepository->removeVpnSite(m_currentRouteMode, hostname);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::removeSites()
|
||||
{
|
||||
m_sites.clear();
|
||||
m_appSettingsRepository->removeAllVpnSites(m_currentRouteMode);
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::setRouteMode(RouteMode routeMode)
|
||||
{
|
||||
m_currentRouteMode = routeMode;
|
||||
fillSites();
|
||||
m_appSettingsRepository->setRouteMode(routeMode);
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::toggleSplitTunneling(bool enabled)
|
||||
{
|
||||
m_appSettingsRepository->setSitesSplitTunnelingEnabled(enabled);
|
||||
}
|
||||
|
||||
RouteMode IpSplitTunnelingController::getRouteMode() const
|
||||
{
|
||||
return m_currentRouteMode;
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::isSplitTunnelingEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isSitesSplitTunnelingEnabled();
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QString>> IpSplitTunnelingController::getCurrentSites() const
|
||||
{
|
||||
return m_sites;
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::fillSites()
|
||||
{
|
||||
QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
m_sites.clear();
|
||||
for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) {
|
||||
m_sites.append(qMakePair(it.key(), it.value().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
QString IpSplitTunnelingController::normalizeHostname(const QString &hostname) const
|
||||
{
|
||||
QString normalized = hostname;
|
||||
normalized.replace("https://", "");
|
||||
normalized.replace("http://", "");
|
||||
normalized.replace("ftp://", "");
|
||||
normalized = normalized.split("/", Qt::SkipEmptyParts).first();
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::validateHostname(const QString &hostname) const
|
||||
{
|
||||
if (hostname.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo)
|
||||
{
|
||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||
QString hostname = hostInfo.hostName();
|
||||
|
||||
for (const QHostAddress &addr : addresses) {
|
||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||
processSiteAfterResolve(hostname, addr.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip)
|
||||
{
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip)
|
||||
{
|
||||
addSiteInternal(hostname, ip);
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage)
|
||||
{
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
errorMessage = tr("Failed to parse JSON data: %1").arg(parseError.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonDocument.isArray()) {
|
||||
errorMessage = tr("The JSON data is not an array");
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray jsonArray = jsonDocument.array();
|
||||
QMap<QString, QString> sites;
|
||||
|
||||
for (auto jsonValue : jsonArray) {
|
||||
QJsonObject jsonObject = jsonValue.toObject();
|
||||
QString hostname = jsonObject.value("hostname").toString("");
|
||||
QString ip = jsonObject.value("ip").toString("");
|
||||
|
||||
QString normalizedHostname = normalizeHostname(hostname);
|
||||
|
||||
if (!validateHostname(normalizedHostname)) {
|
||||
qDebug() << normalizedHostname << " not look like ip adress or domain name";
|
||||
continue;
|
||||
}
|
||||
|
||||
sites.insert(normalizedHostname, ip);
|
||||
}
|
||||
|
||||
addSites(sites, replaceExisting);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray IpSplitTunnelingController::exportSitesToJson() const
|
||||
{
|
||||
QVector<QPair<QString, QString>> sites = getCurrentSites();
|
||||
QJsonArray jsonArray;
|
||||
|
||||
for (const auto &site : sites) {
|
||||
QJsonObject jsonObject;
|
||||
jsonObject["hostname"] = site.first;
|
||||
jsonObject["ip"] = site.second;
|
||||
jsonArray.append(jsonObject);
|
||||
}
|
||||
|
||||
QJsonDocument jsonDocument(jsonArray);
|
||||
return jsonDocument.toJson();
|
||||
}
|
||||
|
||||
58
client/core/controllers/ipSplitTunnelingController.h
Normal file
58
client/core/controllers/ipSplitTunnelingController.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef IPSPLITTUNNELINGCONTROLLER_H
|
||||
#define IPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <QStringList>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QHostInfo>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class IpSplitTunnelingController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr);
|
||||
|
||||
bool addSite(const QString &hostname);
|
||||
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
|
||||
bool removeSite(const QString &hostname);
|
||||
void removeSites();
|
||||
void setRouteMode(RouteMode routeMode);
|
||||
void toggleSplitTunneling(bool enabled);
|
||||
|
||||
RouteMode getRouteMode() const;
|
||||
bool isSplitTunnelingEnabled() const;
|
||||
QVector<QPair<QString, QString>> getCurrentSites() const;
|
||||
|
||||
bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage);
|
||||
QByteArray exportSitesToJson() const;
|
||||
|
||||
private slots:
|
||||
void onHostResolved(const QHostInfo &hostInfo);
|
||||
|
||||
private:
|
||||
void fillSites();
|
||||
bool addSiteInternal(const QString &hostname, const QString &ip);
|
||||
QString normalizeHostname(const QString &hostname) const;
|
||||
bool validateHostname(const QString &hostname) const;
|
||||
void processSiteAfterResolve(const QString &hostname, const QString &ip);
|
||||
void processSite(const QString &hostname, const QString &ip);
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
RouteMode m_currentRouteMode;
|
||||
QVector<QPair<QString, QString>> m_sites;
|
||||
};
|
||||
|
||||
#endif // IPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
337
client/core/controllers/selfhosted/exportController.cpp
Normal file
337
client/core/controllers/selfhosted/exportController.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
#include "exportController.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "core/configurators/configuratorBase.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
ExportController::ExportController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_serversRepository(serversRepository),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateFullAccessConfig(int serverIndex)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
serverConfig.visit([](auto& arg) {
|
||||
for (auto it = arg.containers.begin(); it != arg.containers.end(); ++it) {
|
||||
it.value().protocolConfig.clearClientConfig();
|
||||
}
|
||||
});
|
||||
|
||||
QJsonObject serverJson = serverConfig.toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
if (ContainerUtils::containerService(container) != ServiceType::Other) {
|
||||
SshSession sshSession;
|
||||
Proto protocol = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
DnsSettings dnsSettings = {
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns()
|
||||
};
|
||||
|
||||
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
|
||||
ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, containerConfig, dnsSettings, result.errorCode);
|
||||
if (result.errorCode != ErrorCode::NoError) {
|
||||
return result;
|
||||
}
|
||||
|
||||
containerConfig.protocolConfig = newProtocolConfig;
|
||||
|
||||
QString clientId = newProtocolConfig.clientId();
|
||||
if (!clientId.isEmpty()) {
|
||||
emit appendClientRequested(serverIndex, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
serverConfig.visit([container, containerConfig](auto& arg) {
|
||||
arg.containers.clear();
|
||||
arg.containers[container] = containerConfig;
|
||||
arg.defaultContainer = container;
|
||||
});
|
||||
|
||||
if (serverConfig.isSelfHosted()) {
|
||||
SelfHostedServerConfig* selfHosted = serverConfig.as<SelfHostedServerConfig>();
|
||||
if (selfHosted) {
|
||||
selfHosted->userName.reset();
|
||||
selfHosted->password.reset();
|
||||
selfHosted->port.reset();
|
||||
}
|
||||
}
|
||||
|
||||
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
serverConfig.visit([&dns](auto& arg) {
|
||||
arg.dns1 = dns.first;
|
||||
arg.dns2 = dns.second;
|
||||
});
|
||||
|
||||
QJsonObject serverJson = serverConfig.toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
result.qrCodes = generateQrCodesFromConfig(compressedConfig);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::NativeConfigResult ExportController::generateNativeConfig(int serverIndex, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const QString &clientName)
|
||||
{
|
||||
NativeConfigResult result;
|
||||
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Proto protocol = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
ContainerConfig modifiedContainerConfig = containerConfig;
|
||||
modifiedContainerConfig.container = container;
|
||||
|
||||
DnsSettings dnsSettings = {
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns()
|
||||
};
|
||||
|
||||
SshSession sshSession;
|
||||
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
|
||||
|
||||
ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, modifiedContainerConfig, dnsSettings, result.errorCode);
|
||||
if (result.errorCode != ErrorCode::NoError) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportSettings exportSettings = { { dns.first, dns.second } };
|
||||
ProtocolConfig processedConfig = configurator->processConfigWithExportSettings(exportSettings, newProtocolConfig);
|
||||
|
||||
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) {
|
||||
result.jsonNativeConfig[configKey::config] = processedConfig.nativeConfig();
|
||||
} else {
|
||||
result.jsonNativeConfig = QJsonDocument::fromJson(processedConfig.nativeConfig().toUtf8()).object();
|
||||
}
|
||||
|
||||
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) {
|
||||
QString clientId = newProtocolConfig.clientId();
|
||||
if (!clientId.isEmpty()) {
|
||||
emit appendClientRequested(serverIndex, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateOpenVpnConfig(int serverIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = DockerContainer::OpenVpn;
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : std::as_const(lines)) {
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
result.qrCodes = generateQrCodesFromConfig(result.config.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateWireGuardConfig(int serverIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::WireGuard);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::WireGuard, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : std::as_const(lines)) {
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
if (container != DockerContainer::Awg && container != DockerContainer::Awg2) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList lines = nativeResult.jsonNativeConfig.value(configKey::config).toString().replace("\r", "").split("\n");
|
||||
for (const QString &line : std::as_const(lines)) {
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
result.qrCodes << generateSingleQrCode(result.config.toUtf8());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
ExportController::ExportResult ExportController::generateXrayConfig(int serverIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::Xray);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::Xray, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList lines = QString(QJsonDocument(nativeResult.jsonNativeConfig).toJson()).replace("\r", "").split("\n");
|
||||
for (const QString &line : std::as_const(lines)) {
|
||||
result.config.append(line + "\n");
|
||||
}
|
||||
|
||||
// Parse the Xray data to extract VLESS parameters and generate string
|
||||
QJsonObject xrayConfig = nativeResult.jsonNativeConfig;
|
||||
QJsonArray outbounds = xrayConfig.value(amnezia::protocols::xray::outbounds).toArray();
|
||||
|
||||
if (outbounds.isEmpty()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonObject outbound = outbounds[0].toObject();
|
||||
QJsonObject settings = outbound.value(amnezia::protocols::xray::settings).toObject();
|
||||
QJsonObject streamSettings = outbound.value(amnezia::protocols::xray::streamSettings).toObject();
|
||||
|
||||
QJsonArray vnext = settings.value(amnezia::protocols::xray::vnext).toArray();
|
||||
if (vnext.isEmpty()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonObject server = vnext[0].toObject();
|
||||
QJsonArray users = server.value(amnezia::protocols::xray::users).toArray();
|
||||
if (users.isEmpty()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonObject user = users[0].toObject();
|
||||
|
||||
amnezia::serialization::VlessServerObject vlessServer;
|
||||
vlessServer.address = server.value(amnezia::protocols::xray::address).toString();
|
||||
vlessServer.port = server.value(amnezia::protocols::xray::port).toInt();
|
||||
vlessServer.id = user.value(amnezia::protocols::xray::id).toString();
|
||||
vlessServer.flow = user.value(amnezia::protocols::xray::flow).toString("xtls-rprx-vision");
|
||||
vlessServer.encryption = user.value(amnezia::protocols::xray::encryption).toString("none");
|
||||
|
||||
vlessServer.network = streamSettings.value(amnezia::protocols::xray::network).toString("tcp");
|
||||
vlessServer.security = streamSettings.value(amnezia::protocols::xray::security).toString("reality");
|
||||
|
||||
if (vlessServer.security == "reality") {
|
||||
QJsonObject realitySettings = streamSettings.value(amnezia::protocols::xray::realitySettings).toObject();
|
||||
vlessServer.serverName = realitySettings.value(amnezia::protocols::xray::serverName).toString();
|
||||
vlessServer.publicKey = realitySettings.value(amnezia::protocols::xray::publicKey).toString();
|
||||
vlessServer.shortId = realitySettings.value(amnezia::protocols::xray::shortId).toString();
|
||||
vlessServer.fingerprint = realitySettings.value(amnezia::protocols::xray::fingerprint).toString("chrome");
|
||||
vlessServer.spiderX = realitySettings.value(amnezia::protocols::xray::spiderX).toString("");
|
||||
}
|
||||
|
||||
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExportController::updateClientManagementModel(int serverIndex, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit updateClientsRequested(serverIndex, container);
|
||||
}
|
||||
|
||||
void ExportController::revokeConfig(int row, int serverIndex, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit revokeClientRequested(serverIndex, row, container);
|
||||
}
|
||||
|
||||
void ExportController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit renameClientRequested(serverIndex, row, clientName, container);
|
||||
}
|
||||
|
||||
QString ExportController::generateVpnUrl(const QByteArray &compressedConfig)
|
||||
{
|
||||
return QString("vpn://%1").arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)));
|
||||
}
|
||||
|
||||
QList<QString> ExportController::generateQrCodesFromConfig(const QByteArray &data)
|
||||
{
|
||||
return qrCodeUtils::generateQrCodeImageSeries(data);
|
||||
}
|
||||
|
||||
QString ExportController::generateSingleQrCode(const QByteArray &data)
|
||||
{
|
||||
auto qr = qrCodeUtils::generateQrCode(data);
|
||||
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||
}
|
||||
77
client/core/controllers/selfhosted/exportController.h
Normal file
77
client/core/controllers/selfhosted/exportController.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef EXPORTCONTROLLER_H
|
||||
#define EXPORTCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class SshSession;
|
||||
class VpnConfigurationsController;
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class ExportController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ExportResult
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QString config;
|
||||
QString nativeConfigString;
|
||||
QList<QString> qrCodes;
|
||||
};
|
||||
|
||||
explicit ExportController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
ExportResult generateFullAccessConfig(int serverIndex);
|
||||
ExportResult generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName);
|
||||
ExportResult generateOpenVpnConfig(int serverIndex, const QString &clientName);
|
||||
ExportResult generateWireGuardConfig(int serverIndex, const QString &clientName);
|
||||
ExportResult generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName);
|
||||
ExportResult generateXrayConfig(int serverIndex, const QString &clientName);
|
||||
|
||||
signals:
|
||||
void appendClientRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(int serverIndex, DockerContainer container);
|
||||
void revokeClientRequested(int serverIndex, int row, DockerContainer container);
|
||||
void renameClientRequested(int serverIndex, int row, const QString &clientName, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
void updateClientManagementModel(int serverIndex, int containerIndex);
|
||||
void revokeConfig(int row, int serverIndex, int containerIndex);
|
||||
void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex);
|
||||
|
||||
private:
|
||||
struct NativeConfigResult
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QJsonObject jsonNativeConfig;
|
||||
};
|
||||
|
||||
NativeConfigResult generateNativeConfig(int serverIndex, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const QString &clientName);
|
||||
|
||||
QString generateVpnUrl(const QByteArray &compressedConfig);
|
||||
QList<QString> generateQrCodesFromConfig(const QByteArray &data);
|
||||
QString generateSingleQrCode(const QByteArray &data);
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
};
|
||||
|
||||
#endif // EXPORTCONTROLLER_H
|
||||
762
client/core/controllers/selfhosted/importController.cpp
Normal file
762
client/core/controllers/selfhosted/importController.cpp
Normal file
@@ -0,0 +1,762 @@
|
||||
#include "importController.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QMap>
|
||||
#include <QRandomGenerator>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QRegularExpressionMatchIterator>
|
||||
#include <QUrl>
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
|
||||
namespace
|
||||
{
|
||||
ConfigTypes checkConfigFormat(const QString &config)
|
||||
{
|
||||
const QString openVpnConfigPatternCli = "client";
|
||||
const QString openVpnConfigPatternDriver1 = "dev tun";
|
||||
const QString openVpnConfigPatternDriver2 = "dev tap";
|
||||
|
||||
const QString wireguardConfigPatternSectionInterface = "[Interface]";
|
||||
const QString wireguardConfigPatternSectionPeer = "[Peer]";
|
||||
|
||||
const QString xrayConfigPatternInbound = "inbounds";
|
||||
const QString xrayConfigPatternOutbound = "outbounds";
|
||||
|
||||
const QString amneziaConfigPattern = "containers";
|
||||
const QString amneziaConfigPatternHostName = "hostName";
|
||||
const QString amneziaConfigPatternUserName = "userName";
|
||||
const QString amneziaConfigPatternPassword = "password";
|
||||
const QString amneziaFreeConfigPattern = "api_key";
|
||||
const QString amneziaPremiumConfigPattern = "auth_data";
|
||||
const QString backupPattern = "Servers/serversList";
|
||||
|
||||
if (config.contains(backupPattern)) {
|
||||
return ConfigTypes::Backup;
|
||||
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
|
||||
|| config.contains(amneziaPremiumConfigPattern)
|
||||
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||
&& config.contains(amneziaConfigPatternPassword))) {
|
||||
return ConfigTypes::Amnezia;
|
||||
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
||||
return ConfigTypes::WireGuard;
|
||||
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
||||
return ConfigTypes::Xray;
|
||||
} else if (config.contains(openVpnConfigPatternCli)
|
||||
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
||||
return ConfigTypes::OpenVpn;
|
||||
}
|
||||
return ConfigTypes::Invalid;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ImportController::ImportController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_serversRepository(serversRepository),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
ImportController::ImportResult ImportController::extractConfigFromData(const QString &data, const QString &configFileName)
|
||||
{
|
||||
ImportResult result;
|
||||
result.configFileName = configFileName;
|
||||
result.maliciousWarningText.clear();
|
||||
|
||||
QString config = data;
|
||||
QString prefix;
|
||||
QString errormsg;
|
||||
ConfigTypes configType = ConfigTypes::Invalid;
|
||||
|
||||
if (config.startsWith("vless://")) {
|
||||
configType = ConfigTypes::Xray;
|
||||
result.config = extractXrayConfig(
|
||||
Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
||||
configType, prefix);
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startsWith("vmess://") && config.contains("@")) {
|
||||
configType = ConfigTypes::Xray;
|
||||
result.config = extractXrayConfig(
|
||||
Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
||||
configType, prefix);
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startsWith("vmess://")) {
|
||||
configType = ConfigTypes::Xray;
|
||||
result.config = extractXrayConfig(
|
||||
Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
||||
configType, prefix);
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startsWith("trojan://")) {
|
||||
configType = ConfigTypes::Xray;
|
||||
result.config = extractXrayConfig(
|
||||
Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
||||
configType, prefix);
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startsWith("ss://") && !config.contains("plugin=")) {
|
||||
configType = ConfigTypes::ShadowSocks;
|
||||
result.config = extractXrayConfig(
|
||||
Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
||||
configType, prefix);
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startsWith("ssd://")) {
|
||||
QStringList tmp;
|
||||
QList<std::pair<QString, QJsonObject>> servers = serialization::ssd::Deserialize(config, &prefix, &tmp);
|
||||
configType = ConfigTypes::ShadowSocks;
|
||||
// Took only first config from list
|
||||
if (!servers.isEmpty()) {
|
||||
result.config = extractXrayConfig(servers.first().first, configType);
|
||||
}
|
||||
if (!result.config.empty()) {
|
||||
result.configType = configType;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
configType = checkConfigFormat(config);
|
||||
if (configType == ConfigTypes::Invalid) {
|
||||
config.replace("vpn://", "");
|
||||
QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray baUncompressed = qUncompress(ba);
|
||||
if (!baUncompressed.isEmpty()) {
|
||||
ba = baUncompressed;
|
||||
}
|
||||
|
||||
config = ba;
|
||||
configType = checkConfigFormat(config);
|
||||
}
|
||||
|
||||
result.configType = configType;
|
||||
|
||||
switch (configType) {
|
||||
case ConfigTypes::OpenVpn: {
|
||||
result.config = extractOpenVpnConfig(config);
|
||||
if (!result.config.empty()) {
|
||||
checkForMaliciousStrings(result.config, result.maliciousWarningText);
|
||||
return result;
|
||||
}
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
case ConfigTypes::Awg:
|
||||
case ConfigTypes::WireGuard: {
|
||||
result.config = extractWireGuardConfig(config, result.configType);
|
||||
result.isNativeWireGuardConfig = (result.configType == ConfigTypes::WireGuard);
|
||||
if (!result.config.empty()) {
|
||||
return result;
|
||||
}
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
case ConfigTypes::Xray: {
|
||||
result.config = extractXrayConfig(config, configType);
|
||||
if (!result.config.empty()) {
|
||||
return result;
|
||||
}
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
case ConfigTypes::Amnezia: {
|
||||
result.config = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
|
||||
if (apiUtils::isServerFromApi(result.config)) {
|
||||
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
|
||||
apiConfig[apiDefs::key::vpnKey] = data;
|
||||
result.config[apiDefs::key::apiConfig] = apiConfig;
|
||||
}
|
||||
|
||||
processAmneziaConfig(result.config);
|
||||
if (!result.config.empty()) {
|
||||
checkForMaliciousStrings(result.config, result.maliciousWarningText);
|
||||
return result;
|
||||
}
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
case ConfigTypes::Backup: {
|
||||
result.errorCode = ErrorCode::ImportBackupFileUseRestoreInstead;
|
||||
return result;
|
||||
}
|
||||
case ConfigTypes::Invalid: {
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
result.configFileName.clear();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
|
||||
ImportController::ImportResult ImportController::extractConfigFromQr(const QByteArray &data)
|
||||
{
|
||||
ImportResult result;
|
||||
|
||||
QString dataStr = QString::fromUtf8(data);
|
||||
ConfigTypes configType = checkConfigFormat(dataStr);
|
||||
if (configType != ConfigTypes::Invalid) {
|
||||
return extractConfigFromData(dataStr, "");
|
||||
}
|
||||
|
||||
QJsonObject dataObj = QJsonDocument::fromJson(data).object();
|
||||
if (!dataObj.isEmpty()) {
|
||||
result.config = dataObj;
|
||||
result.configType = ConfigTypes::Amnezia;
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(data);
|
||||
if (!ba_uncompressed.isEmpty()) {
|
||||
result.config = QJsonDocument::fromJson(ba_uncompressed).object();
|
||||
if (result.config.isEmpty()) {
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
result.configType = ConfigTypes::Amnezia;
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray baUncompressed = qUncompress(ba);
|
||||
|
||||
if (!baUncompressed.isEmpty()) {
|
||||
ba = baUncompressed;
|
||||
}
|
||||
|
||||
if (!ba.isEmpty()) {
|
||||
result.config = QJsonDocument::fromJson(ba).object();
|
||||
if (result.config.isEmpty()) {
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
result.configType = ConfigTypes::Amnezia;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ImportController::startDecodingQr()
|
||||
{
|
||||
m_qrCodeChunks.clear();
|
||||
m_totalQrCodeChunksCount = 0;
|
||||
m_receivedQrCodeChunksCount = 0;
|
||||
m_isQrCodeProcessed = true;
|
||||
}
|
||||
|
||||
ImportController::QrParseResult ImportController::parseQrCodeChunk(const QString &code)
|
||||
{
|
||||
QrParseResult parseResult;
|
||||
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
|
||||
parseResult.chunksTotal = m_totalQrCodeChunksCount;
|
||||
|
||||
if (!m_isQrCodeProcessed) {
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QDataStream s(&ba, QIODevice::ReadOnly);
|
||||
qint16 magic;
|
||||
s >> magic;
|
||||
|
||||
if (magic == qrCodeUtils::qrMagicCode) {
|
||||
quint8 chunksCount;
|
||||
s >> chunksCount;
|
||||
if (m_totalQrCodeChunksCount != chunksCount) {
|
||||
m_qrCodeChunks.clear();
|
||||
}
|
||||
|
||||
m_totalQrCodeChunksCount = chunksCount;
|
||||
|
||||
quint8 chunkId;
|
||||
s >> chunkId;
|
||||
s >> m_qrCodeChunks[chunkId];
|
||||
m_receivedQrCodeChunksCount = m_qrCodeChunks.size();
|
||||
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
|
||||
parseResult.chunksTotal = m_totalQrCodeChunksCount;
|
||||
|
||||
if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) {
|
||||
QByteArray data;
|
||||
for (int i = 0; i < m_totalQrCodeChunksCount; ++i) {
|
||||
data.append(m_qrCodeChunks.value(i));
|
||||
}
|
||||
|
||||
ImportResult result = extractConfigFromQr(data);
|
||||
if (result.errorCode == ErrorCode::NoError) {
|
||||
parseResult.success = true;
|
||||
parseResult.importResult = result;
|
||||
m_isQrCodeProcessed = false;
|
||||
} else {
|
||||
m_qrCodeChunks.clear();
|
||||
m_totalQrCodeChunksCount = 0;
|
||||
m_receivedQrCodeChunksCount = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImportResult result = extractConfigFromQr(code.toUtf8());
|
||||
if (result.errorCode != ErrorCode::NoError) {
|
||||
result = extractConfigFromQr(ba);
|
||||
}
|
||||
if (result.errorCode == ErrorCode::NoError) {
|
||||
parseResult.success = true;
|
||||
parseResult.importResult = result;
|
||||
m_isQrCodeProcessed = false;
|
||||
}
|
||||
}
|
||||
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
bool ImportController::isQrDecodingActive() const
|
||||
{
|
||||
return m_isQrCodeProcessed;
|
||||
}
|
||||
|
||||
int ImportController::qrChunksReceived() const
|
||||
{
|
||||
return m_receivedQrCodeChunksCount;
|
||||
}
|
||||
|
||||
int ImportController::qrChunksTotal() const
|
||||
{
|
||||
return m_totalQrCodeChunksCount;
|
||||
}
|
||||
|
||||
void ImportController::importConfig(const QJsonObject &config)
|
||||
{
|
||||
ServerCredentials credentials;
|
||||
credentials.hostName = config.value(configKey::hostName).toString();
|
||||
credentials.port = config.value(configKey::port).toInt();
|
||||
credentials.userName = config.value(configKey::userName).toString();
|
||||
credentials.secretData = config.value(configKey::password).toString();
|
||||
|
||||
if (credentials.isValid() || config.contains(configKey::containers)) {
|
||||
ServerConfig serverConfig = ServerConfig::fromJson(config);
|
||||
m_serversRepository->addServer(serverConfig);
|
||||
emit importFinished();
|
||||
} else if (config.contains(configKey::configVersion)) {
|
||||
quint16 crc = qChecksum(QJsonDocument(config).toJson());
|
||||
if (m_serversRepository->hasServerWithCrc(crc)) {
|
||||
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
||||
} else {
|
||||
QJsonObject configWithCrc = config;
|
||||
configWithCrc.insert(configKey::crc, crc);
|
||||
ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc);
|
||||
m_serversRepository->addServer(serverConfig);
|
||||
emit importFinished();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Failed to import profile";
|
||||
qDebug().noquote() << QJsonDocument(config).toJson();
|
||||
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject ImportController::processNativeWireGuardConfig(const QJsonObject &config)
|
||||
{
|
||||
QJsonObject result = config;
|
||||
auto containers = result.value(configKey::containers).toArray();
|
||||
if (!containers.isEmpty()) {
|
||||
auto container = containers.at(0).toObject();
|
||||
auto serverProtocolConfig = container.value(ContainerUtils::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject();
|
||||
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object();
|
||||
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
clientProtocolConfig[configKey::junkPacketCount] = junkPacketCount;
|
||||
clientProtocolConfig[configKey::junkPacketMinSize] = junkPacketMinSize;
|
||||
clientProtocolConfig[configKey::junkPacketMaxSize] = junkPacketMaxSize;
|
||||
clientProtocolConfig[configKey::initPacketJunkSize] = "0";
|
||||
clientProtocolConfig[configKey::responsePacketJunkSize] = "0";
|
||||
clientProtocolConfig[configKey::initPacketMagicHeader] = "1";
|
||||
clientProtocolConfig[configKey::responsePacketMagicHeader] = "2";
|
||||
clientProtocolConfig[configKey::underloadPacketMagicHeader] = "3";
|
||||
clientProtocolConfig[configKey::transportPacketMagicHeader] = "4";
|
||||
|
||||
clientProtocolConfig[configKey::cookieReplyPacketJunkSize] = "0";
|
||||
clientProtocolConfig[configKey::transportPacketJunkSize] = "0";
|
||||
|
||||
clientProtocolConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
|
||||
|
||||
clientProtocolConfig[configKey::isObfuscationEnabled] = true;
|
||||
|
||||
serverProtocolConfig[configKey::lastConfig] = QString(QJsonDocument(clientProtocolConfig).toJson());
|
||||
container[configKey::wireguard] = serverProtocolConfig;
|
||||
containers.replace(0, container);
|
||||
result[configKey::containers] = containers;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigTypes ImportController::checkConfigFormat(const QString &config) const
|
||||
{
|
||||
return ::checkConfigFormat(config);
|
||||
}
|
||||
|
||||
QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
{
|
||||
QJsonObject openVpnConfig;
|
||||
openVpnConfig[configKey::config] = data;
|
||||
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::lastConfig] = QString(QJsonDocument(openVpnConfig).toJson());
|
||||
lastConfig[configKey::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject containers;
|
||||
containers.insert(configKey::container, QJsonValue(configKey::amneziaOpenvpn));
|
||||
containers.insert(configKey::openvpn, QJsonValue(lastConfig));
|
||||
|
||||
QJsonArray arr;
|
||||
arr.push_back(containers);
|
||||
|
||||
QString hostName;
|
||||
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
|
||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||
if (hostNameMatch.hasMatch()) {
|
||||
hostName = hostNameMatch.captured(1);
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
if (dnsMatch.hasNext()) {
|
||||
config[configKey::dns1] = dnsMatch.next().captured(1);
|
||||
}
|
||||
if (dnsMatch.hasNext()) {
|
||||
config[configKey::dns2] = dnsMatch.next().captured(1);
|
||||
}
|
||||
|
||||
config[configKey::hostName] = hostName;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const
|
||||
{
|
||||
QMap<QString, QString> configMap;
|
||||
auto configByLines = data.split("\n");
|
||||
for (const QString &line : configByLines) {
|
||||
QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::config] = data;
|
||||
|
||||
auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) };
|
||||
QString hostName;
|
||||
QString port;
|
||||
if (!url.host().isEmpty()) {
|
||||
hostName = url.host();
|
||||
} else {
|
||||
qDebug() << "Key parameter" << protocols::wireguard::Endpoint << "is missing or has an invalid format";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (url.port() != -1) {
|
||||
port = QString::number(url.port());
|
||||
} else {
|
||||
port = protocols::wireguard::defaultPort;
|
||||
}
|
||||
|
||||
lastConfig[configKey::hostName] = hostName;
|
||||
lastConfig[configKey::port] = port.toInt();
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::Address).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
||||
lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey);
|
||||
lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address);
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey);
|
||||
}
|
||||
|
||||
lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey);
|
||||
} else {
|
||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
|
||||
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
|
||||
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
||||
|
||||
QString protocolName = configKey::wireguard;
|
||||
QString protocolVersion;
|
||||
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
||||
|
||||
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
|
||||
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
|
||||
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
|
||||
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
|
||||
configKey::transportPacketMagicHeader };
|
||||
|
||||
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
|
||||
configKey::transportPacketJunkSize,
|
||||
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
|
||||
configKey::specialJunk4, configKey::specialJunk5
|
||||
};
|
||||
|
||||
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
if (hasAllRequiredFields) {
|
||||
for (const QString &field : requiredJunkFields) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
|
||||
for (const QString &field : optionalJunkFields) {
|
||||
if (!configMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk5).isEmpty();
|
||||
|
||||
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
||||
protocolVersion = "2";
|
||||
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
|
||||
protocolVersion = "1.5";
|
||||
}
|
||||
protocolName = configKey::awg;
|
||||
detectedType = ConfigTypes::Awg;
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
} else {
|
||||
lastConfig[configKey::mtu] = (protocolName == configKey::awg)
|
||||
? protocols::awg::defaultMtu
|
||||
: protocols::wireguard::defaultMtu;
|
||||
}
|
||||
|
||||
QJsonObject wireguardConfig;
|
||||
wireguardConfig[configKey::lastConfig] = QString(QJsonDocument(lastConfig).toJson());
|
||||
wireguardConfig[configKey::isThirdPartyConfig] = true;
|
||||
wireguardConfig[configKey::port] = port;
|
||||
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
|
||||
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
|
||||
wireguardConfig[configKey::protocolVersion] = protocolVersion;
|
||||
}
|
||||
|
||||
QJsonObject containers;
|
||||
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;
|
||||
containers.insert(configKey::container, QJsonValue(containerName));
|
||||
containers.insert(protocolName, QJsonValue(wireguardConfig));
|
||||
|
||||
QJsonArray arr;
|
||||
arr.push_back(containers);
|
||||
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatch dnsMatch = dnsRegExp.match(data);
|
||||
if (dnsMatch.hasMatch()) {
|
||||
config[configKey::dns1] = dnsMatch.captured(1);
|
||||
config[configKey::dns2] = dnsMatch.captured(2);
|
||||
}
|
||||
|
||||
config[configKey::hostName] = hostName;
|
||||
|
||||
configType = detectedType;
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description) const
|
||||
{
|
||||
QJsonParseError parserErr;
|
||||
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
||||
|
||||
QJsonObject xrayVpnConfig;
|
||||
xrayVpnConfig[configKey::config] = jsonConf.toJson().constData();
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::lastConfig] = jsonConf.toJson().constData();
|
||||
lastConfig[configKey::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject containers;
|
||||
if (configType == ConfigTypes::ShadowSocks) {
|
||||
containers.insert(configKey::ssxray, QJsonValue(lastConfig));
|
||||
containers.insert(configKey::container, QJsonValue(configKey::amneziaSsxray));
|
||||
} else {
|
||||
containers.insert(configKey::container, QJsonValue(configKey::amneziaXray));
|
||||
containers.insert(configKey::xray, QJsonValue(lastConfig));
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
arr.push_back(containers);
|
||||
|
||||
QString hostName;
|
||||
|
||||
const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)");
|
||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||
if (hostNameMatch.hasMatch()) {
|
||||
hostName = hostNameMatch.captured(1);
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = (configType == ConfigTypes::ShadowSocks)
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
config[configKey::hostName] = hostName;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const
|
||||
{
|
||||
const QJsonArray &containers = serverConfig.value(configKey::containers).toArray();
|
||||
for (const QJsonValue &container : containers) {
|
||||
auto containerConfig = container.toObject();
|
||||
auto containerName = containerConfig[configKey::container].toString();
|
||||
if (containerName == ContainerUtils::containerToString(DockerContainer::OpenVpn)) {
|
||||
|
||||
QString protocolConfig =
|
||||
containerConfig[ProtocolUtils::protoToString(Proto::OpenVpn)].toObject()[configKey::lastConfig].toString();
|
||||
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[configKey::config].toString();
|
||||
|
||||
// https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst
|
||||
QStringList dangerousTags {
|
||||
"up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify"
|
||||
};
|
||||
|
||||
QStringList maliciousStrings;
|
||||
QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts);
|
||||
|
||||
for (const QString &rawLine : lines) {
|
||||
QString line = rawLine.trimmed();
|
||||
|
||||
QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty);
|
||||
if (dangerousTags.contains(command, Qt::CaseInsensitive)) {
|
||||
maliciousStrings << rawLine;
|
||||
}
|
||||
}
|
||||
|
||||
warningText = "This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious "
|
||||
"scripts, so only add it if you fully trust the provider of this config. ";
|
||||
|
||||
if (!maliciousStrings.isEmpty()) {
|
||||
warningText += "<br>In the imported configuration, potentially dangerous lines were found:";
|
||||
for (const auto &string : maliciousStrings) {
|
||||
warningText += QString("<br><i>%1</i>").arg(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImportController::processAmneziaConfig(QJsonObject &config) const
|
||||
{
|
||||
auto containers = config.value(configKey::containers).toArray();
|
||||
for (auto i = 0; i < containers.size(); i++) {
|
||||
auto container = containers.at(i).toObject();
|
||||
auto dockerContainer = ContainerUtils::containerFromString(container.value(configKey::container).toString());
|
||||
if (ContainerUtils::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) {
|
||||
auto containerConfig = container.value(ContainerUtils::containerTypeToProtocolString(dockerContainer)).toObject();
|
||||
auto protocolConfig = containerConfig.value(configKey::lastConfig).toString();
|
||||
if (protocolConfig.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
|
||||
jsonConfig[configKey::mtu] =
|
||||
ContainerUtils::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||
|
||||
containerConfig[configKey::lastConfig] = QString(QJsonDocument(jsonConfig).toJson());
|
||||
|
||||
container[ContainerUtils::containerTypeToProtocolString(dockerContainer)] = containerConfig;
|
||||
containers.replace(i, container);
|
||||
config.insert(configKey::containers, containers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
client/core/controllers/selfhosted/importController.h
Normal file
91
client/core/controllers/selfhosted/importController.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef IMPORTCONTROLLER_H
|
||||
#define IMPORTCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QByteArray>
|
||||
#include <QMap>
|
||||
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class ConfigTypes {
|
||||
Amnezia,
|
||||
OpenVpn,
|
||||
WireGuard,
|
||||
Awg,
|
||||
Xray,
|
||||
ShadowSocks,
|
||||
Backup,
|
||||
Invalid
|
||||
};
|
||||
}
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class ImportController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ImportResult
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
QJsonObject config;
|
||||
QString configFileName;
|
||||
QString maliciousWarningText;
|
||||
ConfigTypes configType = ConfigTypes::Invalid;
|
||||
bool isNativeWireGuardConfig = false;
|
||||
};
|
||||
|
||||
explicit ImportController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
struct QrParseResult {
|
||||
bool success = false;
|
||||
ImportResult importResult;
|
||||
int chunksReceived = 0;
|
||||
int chunksTotal = 0;
|
||||
};
|
||||
|
||||
ImportResult extractConfigFromData(const QString &data, const QString &configFileName = "");
|
||||
ImportResult extractConfigFromQr(const QByteArray &data);
|
||||
|
||||
void startDecodingQr();
|
||||
QrParseResult parseQrCodeChunk(const QString &code);
|
||||
bool isQrDecodingActive() const;
|
||||
int qrChunksReceived() const;
|
||||
int qrChunksTotal() const;
|
||||
|
||||
void importConfig(const QJsonObject &config);
|
||||
QJsonObject processNativeWireGuardConfig(const QJsonObject &config);
|
||||
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||
void restoreAppConfig(const QByteArray &data);
|
||||
|
||||
private:
|
||||
ConfigTypes checkConfigFormat(const QString &config) const;
|
||||
QJsonObject extractOpenVpnConfig(const QString &data) const;
|
||||
QJsonObject extractWireGuardConfig(const QString &data, ConfigTypes &configType) const;
|
||||
QJsonObject extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description = "") const;
|
||||
void checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const;
|
||||
void processAmneziaConfig(QJsonObject &config) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
QMap<int, QByteArray> m_qrCodeChunks;
|
||||
bool m_isQrCodeProcessed = false;
|
||||
int m_totalQrCodeChunksCount = 0;
|
||||
int m_receivedQrCodeChunksCount = 0;
|
||||
};
|
||||
|
||||
#endif // IMPORTCONTROLLER_H
|
||||
1179
client/core/controllers/selfhosted/installController.cpp
Normal file
1179
client/core/controllers/selfhosted/installController.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user