Compare commits

..

54 Commits

Author SHA1 Message Date
vladimir.kuznetsov 7420226b7c fixed margins on PageSetupWizardApiServicesList 2024-11-25 12:39:24 +07:00
vladimir.kuznetsov 74169ef7b3 added scroll on page with services list 2024-11-21 14:45:28 +07:00
Nethius aa871bd1c9 feature: added country selection on home page drawer (#1215) 2024-11-12 13:22:34 +07:00
albexk 23806e1def chore: bump version to 4.8.2.4 (#1240) 2024-11-08 15:22:16 +07:00
Nethius 31867993ce chore: minor fixes (#1235) 2024-11-06 12:57:39 +07:00
pokamest 7b7a922d92 Merge pull request #1226 from amnezia-vpn/fix/android-awg-connection
Fix connection check for AWG/WG
2024-11-05 12:25:49 +01:00
pokamest 09bd958d8d Merge pull request #1231 from amnezia-vpn/fix/android-network-state
Add CHANGE_NETWORK_STATE permission for all Android versions
2024-11-05 12:25:11 +01:00
albexk 576e2226fe fix(android): add CHANGE_NETWORK_STATE permission for all Android versions 2024-11-03 16:11:23 +03:00
albexk 1533270e4e Fix connection check for AWG/WG 2024-11-02 00:54:24 +03:00
albexk e7b25719e4 Chore/bump version (#1204)
* chore: bump Android version code

---------

Co-authored-by: Nethius <nethiuswork@gmail.com>
2024-10-25 23:23:05 +07:00
pokamest 7183e8541c Merge pull request #1202 from Aftershock669/update-readme
Fix / Update README
2024-10-25 16:43:50 +01:00
Nethius 9e71e64cbd chore: bump version to 4.8.2.3 (#1203) 2024-10-25 22:19:28 +07:00
Aftershock669 4f3bae4a9a Fix / Update README 2024-10-25 17:00:28 +03:00
pokamest 990059f8a6 Merge pull request #1200 from amnezia-vpn/bugfix/proxy-bypass-enc-check 2024-10-25 10:50:07 +01:00
vladimir.kuznetsov af55af5e76 bugfix: fixed proxy bypass encryption check 2024-10-25 17:48:22 +08:00
pokamest 82d96a9691 Merge pull request #1197 from Aftershock669/update-readme
Update README
2024-10-24 23:57:58 +01:00
Aftershock669 9f3f215452 Update README
- add website mirror links
- remove direct platform download links
- add "Testiny" sponsored badge
2024-10-24 22:32:35 +03:00
pokamest 2dfc6a87b8 Merge pull request #1196 from amnezia-vpn/bump-version
Bump version to 4.8.2.1
2024-10-24 17:36:50 +01:00
albexk 7261a86c48 Bump version to 4.8.2.1 2024-10-24 19:25:44 +03:00
pokamest 2946dd2278 Merge pull request #1124 from amnezia-vpn/bugfix/page-share-recursive-rearrange
bugfix: fixed clientInfoDrawer.expandedHeight recursive rearrange
2024-10-24 16:39:04 +01:00
vladimir.kuznetsov 5065262aac bugfix: fixed clientInfoDrawer recursive rearrange 2024-10-24 23:37:42 +08:00
Nethius 4685d3b543 bugfix/api auth data saving (#1195)
* bugfix: fixed authData saving

* bugfix: added serviceInfo processing from api response
2024-10-24 16:12:53 +01:00
pokamest 7a389e8755 Merge pull request #1188 from amnezia-vpn/chore/global-network-manager
chore/using the global network manager
2024-10-24 16:10:57 +01:00
vladimir.kuznetsov 4e5daf22a3 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into chore/global-network-manager 2024-10-24 22:53:56 +08:00
pokamest 3bf9c10d7d Merge pull request #1192 from amnezia-vpn/bugfix/awg-wg-routes-vpnconnection
bugfix/removed adding routes in vpnconnection class for awg and wg protocols
2024-10-24 14:11:26 +01:00
pokamest 2e175cb9fc Merge pull request #1189 from amnezia-vpn/feature/support-tag
feature/added support tag to PageSetupWizardConfigSource
2024-10-24 14:08:28 +01:00
pokamest 823c1b5d3a Merge pull request #1190 from amnezia-vpn/chore/win-routes-logger
chore/displaying route addresses when adding to split tunneling fails
2024-10-24 14:04:45 +01:00
pokamest 92bc1a6f09 Merge pull request #1194 from amnezia-vpn/feature/proxy-bypass-checks
feature/proxy bypass checks
2024-10-24 14:03:56 +01:00
vladimir.kuznetsov d511220f8b added a randomized proxy bypass 2024-10-24 10:59:50 +08:00
vladimir.kuznetsov 923e358aaa added a check to trigger proxy bypass 2024-10-24 01:02:30 +08:00
vladimir.kuznetsov 92b19eccf6 bugfix/removed adding routes in vpnconnection class for awg and wg protocols 2024-10-23 00:33:22 +08:00
vladimir.kuznetsov 5358aaeb00 chore/displaying route addresses when adding to split tunneling fails 2024-10-22 23:14:41 +08:00
vladimir.kuznetsov e31a2066c0 feature/added support tag to PageSetupWizardConfigSource 2024-10-22 23:05:58 +08:00
vladimir.kuznetsov 928c4f18c9 chore/using the global network manager 2024-10-22 22:24:23 +08:00
pokamest 628e22869d Merge pull request #1085 from amnezia-vpn/bugfix/win_check_ps
Refactoring wmic to winapi
2024-10-18 15:45:32 +01:00
Pokamest Nikak c9cd860654 Merge branch 'dev' into bugfix/win_check_ps 2024-10-18 15:42:08 +01:00
pokamest 17984adae5 Merge pull request #1181 from amnezia-vpn/chore/workflow-envs
chore: added new env for workflows
2024-10-18 15:02:36 +01:00
vladimir.kuznetsov 5601bc4fdf chore: added new env for workflows 2024-10-18 21:39:09 +08:00
pokamest e14681801e Merge pull request #1086 from amnezia-vpn/bugfix/pw_rnd_gen
Switched to secure PRNG & some pw len increased
2024-10-18 14:17:33 +01:00
pokamest f106b4d367 Merge pull request #1117 from amnezia-vpn/feature/process-auth-data
added processing of auth_data section when requesting api config
2024-10-18 10:57:57 +01:00
Nethius 74802f30ed feature/proxy storage bypass (#1179)
* feature: added proxy storage bypass
- added encryption error handling to apiController

* chore: fixed include
2024-10-18 10:57:38 +01:00
albexk d63bf15011 Android qt 6.7.3 (#1143)
* Up Qt to 6.7.3

* Bump version to 4.8.2.0

* Raise the minimum Android version to 8 (API 26)

* Update version code to separate versions for new and old Androids

* Fix mouse not working on TVs

* Refactor logging

* Bump version code
2024-10-18 10:52:24 +01:00
Nethius 60de146f03 chore/mozilla upstream (#1136)
* cherry-pick commit 5a51e292d44ec0fb07867aff0401b4c2a8fca1e8 from mozila upstream

* cherry-pick commit e8ecb857dcfb804b7766a54e725b442fc6c0e661 from mozila upstream

* cherry-pick commit 16269ffa600905b09678014f64951748fb0ff8ad from mozila upstream
2024-10-18 10:47:53 +01:00
pokamest c4f32eed31 Merge pull request #1180 from amnezia-vpn/bugfix/open-file-error-missing-text
bugfix: added missing text in the errors [no ci]
2024-10-18 10:45:10 +01:00
vladimir.kuznetsov 2c9067b0de bugfix: added missing text in the errors 2024-10-18 14:57:20 +08:00
pokamest 6844a2375b Merge pull request #1107 from amnezia-vpn/chore/fix-warnings
chore: added clear() after extractConfigFromData() on android
2024-10-13 12:18:46 +01:00
Nethius 7b838e77a0 bugfix: removed the importErrorOccurred() signal overload, since qml does not know how to handle signal overloads (#1111) 2024-10-13 12:14:43 +01:00
Nethius 694e781beb bugfix: fixed path to log folder for wireguard on windows (#1137) 2024-10-11 08:58:53 +07:00
Nethius 399a8c6d28 bugfix: fixed qml warnings when loading user list on PageShare (#1119) 2024-10-11 08:58:30 +07:00
vladimir.kuznetsov dce08b3ecc added processing of auth_data section when requesting api config
- fixed saving api_config section when processing backend response
2024-10-06 13:23:19 +08:00
vladimir.kuznetsov 2763da960f chore: added clear() after extractConfigFromData() on android 2024-10-02 13:20:16 +08:00
Pokamest Nikak 1542adba82 Switched to secure PRNG & some pw len increased 2024-09-23 00:44:25 +01:00
Pokamest Nikak 3aa8a46f6e wip 2024-09-23 01:19:46 +03:00
Pokamest Nikak 1f08d78b43 wip 2024-09-22 22:52:59 +01:00
70 changed files with 712 additions and 417 deletions
+16 -1
View File
@@ -16,7 +16,10 @@ jobs:
QT_VERSION: 6.6.2 QT_VERSION: 6.6.2
QIF_VERSION: 4.7 QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Install Qt' - name: 'Install Qt'
@@ -83,7 +86,10 @@ jobs:
QIF_VERSION: 4.7 QIF_VERSION: 4.7
BUILD_ARCH: 64 BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Get sources' - name: 'Get sources'
@@ -146,7 +152,10 @@ jobs:
CC: cc CC: cc
CXX: c++ CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Setup xcode' - name: 'Setup xcode'
@@ -238,7 +247,10 @@ jobs:
QT_VERSION: 6.4.3 QT_VERSION: 6.4.3
QIF_VERSION: 4.6 QIF_VERSION: 4.6
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Setup xcode' - name: 'Setup xcode'
@@ -301,10 +313,13 @@ jobs:
env: env:
ANDROID_BUILD_PLATFORM: android-34 ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.2 QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Install desktop Qt' - name: 'Install desktop Qt'
+3
View File
@@ -16,7 +16,10 @@ jobs:
QT_VERSION: 6.4.1 QT_VERSION: 6.4.1
QIF_VERSION: 4.5 QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps: steps:
- name: 'Install desktop Qt' - name: 'Install desktop Qt'
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.8.1.9 project(${PROJECT} VERSION 4.8.2.4
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 65) set(APP_ANDROID_VERSION_CODE 2071)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+4 -8
View File
@@ -10,21 +10,17 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
<br> <br>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a> <a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_Linux_4.7.0.0.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.7.0.0"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
<br>
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a> <a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a> <a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads)
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases) [All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
<br> <br>
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
## Features ## Features
@@ -37,7 +33,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## Links ## Links
- [https://amnezia.org](https://amnezia.org) - project website - [https://amnezia.org](https://amnezia.org) - project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit - [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 support channel (English) - [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi) - [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
+2 -1
View File
@@ -25,10 +25,11 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}") add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}") add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
if(IOS) if(IOS)
set(PACKAGES ${PACKAGES} Multimedia) set(PACKAGES ${PACKAGES} Multimedia)
+10 -9
View File
@@ -111,10 +111,11 @@ void AmneziaApplication::init()
qFatal("Android controller initialization failed"); qFatal("Android controller initialization failed");
} }
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
m_pageController->goToPageHome(); emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data); m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig(); data.clear();
emit m_pageController->goToPageViewConfig();
}); });
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
@@ -122,16 +123,16 @@ void AmneziaApplication::init()
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
IosController::Instance()->initialize(); IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
m_pageController->goToPageHome(); emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data); m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig(); emit m_pageController->goToPageViewConfig();
}); });
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
m_pageController->goToPageHome(); emit m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup(); m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath); emit m_settingsController->importBackupFromOutside(filePath);
}); });
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
+1 -1
View File
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- To request network state --> <!-- To request network state -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+1 -1
View File
@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
# For development copy and set local values for these parameters in local.properties # For development copy and set local values for these parameters in local.properties
#androidCompileSdkVersion=android-34 #androidCompileSdkVersion=android-34
#androidBuildToolsVersion=34.0.0 #androidBuildToolsVersion=34.0.0
#qtMinSdkVersion=24 #qtMinSdkVersion=26
#qtTargetSdkVersion=34 #qtTargetSdkVersion=34
#androidNdkVersion=26.1.10909125 #androidNdkVersion=26.1.10909125
#qtTargetAbiList=x86_64 #qtTargetAbiList=x86_64
@@ -1,6 +1,5 @@
package org.amnezia.vpn.protocol package org.amnezia.vpn.protocol
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.IpPrefix import android.net.IpPrefix
import android.net.VpnService import android.net.VpnService
@@ -8,9 +7,6 @@ import android.net.VpnService.Builder
import android.os.Build import android.os.Build
import android.system.OsConstants import android.system.OsConstants
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
@@ -21,6 +21,7 @@ import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.provider.Settings import android.provider.Settings
import android.view.MotionEvent
import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
@@ -158,7 +159,7 @@ class AmneziaActivity : QtActivity() {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity: $intent") Log.d(TAG, "Create Amnezia activity")
loadLibs() loadLibs()
window.apply { window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -200,7 +201,7 @@ class AmneziaActivity : QtActivity() {
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
) )
) { ) {
Log.d( Log.v(
TAG, "Notification state changed: ${it?.action}, blocked = " + TAG, "Notification state changed: ${it?.action}, blocked = " +
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}" "${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
) )
@@ -214,7 +215,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@@ -403,7 +404,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun startVpn(vpnConfig: String) { private fun startVpn(vpnConfig: String) {
getVpnProto(vpnConfig)?.let { proto -> getVpnProto(vpnConfig)?.let { proto ->
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto") Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
if (isServiceConnected) { if (isServiceConnected) {
if (proto.serviceClass == vpnProto?.serviceClass) { if (proto.serviceClass == vpnProto?.serviceClass) {
vpnProto = proto vpnProto = proto
@@ -516,7 +517,7 @@ class AmneziaActivity : QtActivity() {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = { onSuccess = {
it?.data?.let { uri -> it?.data?.let { uri ->
Log.d(TAG, "Save file to $uri") Log.v(TAG, "Save file to $uri")
try { try {
contentResolver.openOutputStream(uri)?.use { os -> contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) } os.bufferedWriter().use { it.write(data) }
@@ -565,7 +566,7 @@ class AmneziaActivity : QtActivity() {
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = { onAny = {
val uri = it?.data?.toString() ?: "" val uri = it?.data?.toString() ?: ""
Log.d(TAG, "Open file: $uri") Log.v(TAG, "Open file: $uri")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
QtAndroidController.onFileOpened(uri) QtAndroidController.onFileOpened(uri)
@@ -720,6 +721,66 @@ class AmneziaActivity : QtActivity() {
} }
} }
// workaround for a bug in Qt that causes the mouse click event not to be handled
// also disable right-click, as it causes the application to crash
private var lastButtonState = 0
private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
downTime,
eventTime,
action,
pointerCount,
(0 until pointerCount).map { i ->
MotionEvent.PointerProperties().apply {
getPointerProperties(i, this)
}
}.toTypedArray(),
(0 until pointerCount).map { i ->
MotionEvent.PointerCoords().apply {
getPointerCoords(i, this)
}
}.toTypedArray(),
metaState,
MotionEvent.BUTTON_PRIMARY,
xPrecision,
yPrecision,
deviceId,
edgeFlags,
source,
flags
)
private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
lastButtonState = ev.buttonState
if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
}
MotionEvent.ACTION_UP -> {
when (lastButtonState) {
MotionEvent.BUTTON_SECONDARY -> return true
MotionEvent.BUTTON_PRIMARY -> {
val modEvent = ev.fixCopy()
return superDispatch(modEvent).apply { modEvent.recycle() }
}
}
}
}
return superDispatch(ev)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
}
return super.dispatchTouchEvent(ev)
}
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
return super.dispatchTrackballEvent(ev)
}
/** /**
* Utils methods * Utils methods
*/ */
@@ -300,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
) { ) {
it?.action?.let { action -> it?.action?.let { action ->
Log.d(TAG, "Broadcast request received: $action") Log.v(TAG, "Broadcast request received: $action")
when (action) { when (action) {
ACTION_CONNECT -> connect() ACTION_CONNECT -> connect()
ACTION_DISCONNECT -> disconnect() ACTION_DISCONNECT -> disconnect()
@@ -317,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
) )
) { ) {
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false) val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state") Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
if (state == false) { if (state == false) {
enableNotification() enableNotification()
} else { } else {
@@ -450,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
serviceNotification.isNotificationEnabled() && serviceNotification.isNotificationEnabled() &&
getSystemService<PowerManager>()?.isInteractive != false getSystemService<PowerManager>()?.isInteractive != false
) { ) {
Log.d(TAG, "Launch traffic stats update") Log.v(TAG, "Launch traffic stats update")
trafficStats.reset() trafficStats.reset()
startTrafficStatsUpdateJob() startTrafficStatsUpdateJob()
} }
@@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
object : BiometricPrompt.AuthenticationCallback() { object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) { override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result) super.onAuthenticationSucceeded(result)
Log.d(TAG, "Authentication succeeded") Log.v(TAG, "Authentication succeeded")
QtAndroidController.onAuthResult(true) QtAndroidController.onAuthResult(true)
finish() finish()
} }
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Import Config Activity: $intent") Log.v(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent.let(::readConfig) intent.let(::readConfig)
} }
private fun readConfig(intent: Intent) { private fun readConfig(intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_SEND -> { ACTION_SEND -> {
Log.d(TAG, "Process SEND action, type: ${intent.type}") Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) { when (intent.type) {
"application/octet-stream" -> { "application/octet-stream" -> {
intent.getUriCompat()?.let { uri -> intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
} }
ACTION_VIEW -> { ACTION_VIEW -> {
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) { when (intent.scheme) {
"file", "content" -> { "file", "content" -> {
intent.data?.let { uri -> intent.data?.let { uri ->
@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification { fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
val speedString = if (state == CONNECTED) zeroSpeed else null val speedString = if (state == CONNECTED) zeroSpeed else null
Log.d(TAG, "Build notification: $serverName, $state") Log.v(TAG, "Build notification: $serverName, $state")
return notificationBuilder return notificationBuilder
.setSmallIcon(R.drawable.ic_amnezia_round) .setSmallIcon(R.drawable.ic_amnezia_round)
@@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
fun isNotificationEnabled(): Boolean { fun isNotificationEnabled(): Boolean {
if (!context.isNotificationPermissionGranted()) return false if (!context.isNotificationPermissionGranted()) return false
if (!notificationManager.areNotificationsEnabled()) return false if (!notificationManager.areNotificationsEnabled()) return false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) it.importance != NotificationManager.IMPORTANCE_NONE
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true } ?: true
}
return true
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) { fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
if (context.isNotificationPermissionGranted()) { if (context.isNotificationPermissionGranted()) {
Log.d(TAG, "Update notification: $serverName, $state") Log.v(TAG, "Update notification: $serverName, $state")
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state)) notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
} }
} }
@@ -46,7 +46,7 @@ object LibraryLoader {
System.loadLibrary(libraryName) System.loadLibrary(libraryName)
return return
} catch (_: UnsatisfiedLinkError) { } catch (_: UnsatisfiedLinkError) {
Log.d(TAG, "Failed to load library, try to extract it from apk") Log.w(TAG, "Failed to load library, try to extract it from apk")
} }
var tempFile: File? = null var tempFile: File? = null
try { try {
+2 -15
View File
@@ -1,8 +1,6 @@
package org.amnezia.vpn.util package org.amnezia.vpn.util
import android.content.Context import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build import android.os.Build
import android.os.Process import android.os.Process
import java.io.File import java.io.File
@@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock import java.nio.channels.FileLock
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E import org.amnezia.vpn.util.Log.Priority.E
@@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
* | | | create a report and/or terminate the process | * | | | create a report and/or terminate the process |
*/ */
object Log { object Log {
private val dateTimeFormat: Any = private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
}
private lateinit var logDir: File private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) } private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
@@ -143,12 +135,7 @@ object Log {
} }
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String { private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val date = LocalDateTime.now().format(dateTimeFormat)
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " + return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n" "$tag: $msg\n"
} }
@@ -42,18 +42,12 @@ class NetworkState(
private val networkCallback: NetworkCallback by lazy(NONE) { private val networkCallback: NetworkCallback by lazy(NONE) {
object : NetworkCallback() { object : NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
Log.d(TAG, "onAvailable: $network") Log.v(TAG, "onAvailable: $network")
} }
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities") Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { checkNetworkState(network, networkCapabilities)
checkNetworkState(network, networkCapabilities)
} else {
handler.post {
checkNetworkState(network, networkCapabilities)
}
}
} }
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) { private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
@@ -73,11 +67,11 @@ class NetworkState(
} }
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked") Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
} }
override fun onLost(network: Network) { override fun onLost(network: Network) {
Log.d(TAG, "onLost: $network") Log.v(TAG, "onLost: $network")
} }
} }
} }
@@ -87,7 +81,7 @@ class NetworkState(
Log.d(TAG, "Bind network listener") Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { } else {
val numberAttempts = 300 val numberAttempts = 300
var attemptCount = 0 var attemptCount = 0
while(true) { while(true) {
@@ -108,8 +102,6 @@ class NetworkState(
} }
} }
} }
} else {
connectivityManager.requestNetwork(networkRequest, networkCallback)
} }
isListenerBound = true isListenerBound = true
} }
@@ -1,11 +1,12 @@
package org.amnezia.vpn.protocol.wireguard package org.amnezia.vpn.protocol.wireguard
import android.net.VpnService.Builder import android.net.VpnService.Builder
import java.io.IOException import kotlinx.coroutines.CoroutineScope
import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.launch
import org.amnezia.awg.GoBackend import org.amnezia.awg.GoBackend
import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
@@ -27,6 +28,8 @@ open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1 private var tunnelHandle: Int = -1
protected open val ifName: String = "amn0" protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope
private var statusJob: Job? = null
override val statistics: Statistics override val statistics: Statistics
get() { get() {
@@ -49,46 +52,17 @@ open class Wireguard : Protocol() {
override fun internalInit() { override fun internalInit() {
if (!isInitialized) loadSharedLibrary(context, "wg-go") if (!isInitialized) loadSharedLibrary(context, "wg-go")
if (this::scope.isInitialized) {
scope.cancel()
}
scope = CoroutineScope(Dispatchers.IO)
} }
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config) val wireguardConfig = parseConfig(config)
val startTime = System.currentTimeMillis()
start(wireguardConfig, vpnBuilder, protect) start(wireguardConfig, vpnBuilder, protect)
waitForConnection(startTime)
state.value = CONNECTED
} }
private suspend fun waitForConnection(startTime: Long) {
Log.d(TAG, "Waiting for connection")
withContext(Dispatchers.IO) {
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
try {
delay(1000)
var log = getLogcat(time)
Log.d(TAG, "First waiting log: $log")
// check that there is a connection log,
// to avoid infinite connection
if (!log.contains("Attaching to interface")) {
Log.w(TAG, "Logs do not contain a connection log")
return@withContext
}
while (!log.contains("Received handshake response")) {
delay(1000)
log = getLogcat(time)
}
} catch (e: IOException) {
Log.e(TAG, "Failed to get logcat: $e")
}
}
}
private fun getLogcat(time: String): String =
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
.redirectErrorStream(true)
.start()
.inputStream.reader().readText()
protected open fun parseConfig(config: JSONObject): WireguardConfig { protected open fun parseConfig(config: JSONObject): WireguardConfig {
val configData = config.getJSONObject("wireguard_config_data") val configData = config.getJSONObject("wireguard_config_data")
return WireguardConfig.build { return WireguardConfig.build {
@@ -178,6 +152,43 @@ open class Wireguard : Protocol() {
tunnelHandle = -1 tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked") throw VpnStartException("Protect VPN interface: permission not granted or revoked")
} }
launchStatusJob()
}
private fun launchStatusJob() {
Log.d(TAG, "Launch status job")
statusJob = scope.launch {
while (true) {
val lastHandshake = getLastHandshake()
Log.v(TAG, "lastHandshake=$lastHandshake")
if (lastHandshake == 0L) {
delay(1000)
continue
}
if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
else if (lastHandshake == -1L) state.value = DISCONNECTED
statusJob = null
break
}
}
}
private fun getLastHandshake(): Long {
if (tunnelHandle == -1) {
Log.e(TAG, "Trying to get config of a non-existent tunnel")
return -1
}
val config = GoBackend.awgGetConfig(tunnelHandle)
if (config == null) {
Log.e(TAG, "Failed to get tunnel config")
return -2
}
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
if (lastHandshake == null) {
Log.e(TAG, "Failed to get last_handshake_time_sec")
return -2
}
return lastHandshake
} }
override fun stopVpn() { override fun stopVpn() {
@@ -185,6 +196,8 @@ open class Wireguard : Protocol() {
Log.w(TAG, "Tunnel already down") Log.w(TAG, "Tunnel already down")
return return
} }
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle val handleToClose = tunnelHandle
tunnelHandle = -1 tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose) GoBackend.awgTurnOff(handleToClose)
+2 -2
View File
@@ -130,8 +130,8 @@ class Xray : Protocol() {
LibXray.initXray(assetsPath) LibXray.initXray(assetsPath)
val geoDir = File(assetsPath, "geo").absolutePath val geoDir = File(assetsPath, "geo").absolutePath
val configPath = File(context.cacheDir, "config.json") val configPath = File(context.cacheDir, "config.json")
Log.d(TAG, "xray.location.asset: $geoDir") Log.v(TAG, "xray.location.asset: $geoDir")
Log.d(TAG, "config: $configPath") Log.v(TAG, "config: $configPath")
try { try {
configPath.writeText(configJson) configPath.writeText(configJson)
} catch (e: IOException) { } catch (e: IOException) {
+1 -1
View File
@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 24) set(APP_ANDROID_MIN_SDK 26)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE) "The minimum API level supported by the application or library" FORCE)
+115 -38
View File
@@ -1,5 +1,8 @@
#include "apiController.h" #include "apiController.h"
#include <algorithm>
#include <random>
#include <QEventLoop> #include <QEventLoop>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
@@ -11,6 +14,7 @@
#include "amnezia_application.h" #include "amnezia_application.h"
#include "configurators/wireguard_configurator.h" #include "configurators/wireguard_configurator.h"
#include "core/enums/apiEnums.h" #include "core/enums/apiEnums.h"
#include "utilities.h"
#include "version.h" #include "version.h"
namespace namespace
@@ -33,6 +37,7 @@ namespace
constexpr char userCountryCode[] = "user_country_code"; constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code"; constexpr char serverCountryCode[] = "server_country_code";
constexpr char serviceType[] = "service_type"; constexpr char serviceType[] = "service_type";
constexpr char serviceInfo[] = "service_info";
constexpr char aesKey[] = "aes_key"; constexpr char aesKey[] = "aes_key";
constexpr char aesIv[] = "aes_iv"; constexpr char aesIv[] = "aes_iv";
@@ -40,9 +45,10 @@ namespace
constexpr char apiPayload[] = "api_payload"; constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload"; constexpr char keyPayload[] = "key_payload";
}
const QStringList proxyStorageUrl = { "" }; constexpr char apiConfig[] = "api_config";
constexpr char authData[] = "auth_data";
}
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply) ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
{ {
@@ -63,6 +69,28 @@ namespace
return ErrorCode::ApiConfigDownloadError; 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) ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
@@ -94,8 +122,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
} else if (protocol == configKey::awg) { } else if (protocol == configKey::awg) {
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
auto containers = serverConfig.value(config_key::containers).toArray(); auto containers = newServerConfig.value(config_key::containers).toArray();
if (containers.isEmpty()) { if (containers.isEmpty()) {
return; // todo process error return; // todo process error
} }
@@ -114,25 +142,35 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
container[containerName] = containerConfig; container[containerName] = containerConfig;
containers.replace(0, container); containers.replace(0, container);
serverConfig[config_key::containers] = containers; newServerConfig[config_key::containers] = containers;
configStr = QString(QJsonDocument(serverConfig).toJson()); configStr = QString(QJsonDocument(newServerConfig).toJson());
} }
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1); serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2); serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
serverConfig[config_key::containers] = apiConfig.value(config_key::containers); serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName); serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion); serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
serverConfig[config_key::description] = apiConfig.value(config_key::description); serverConfig[config_key::description] = newServerConfig.value(config_key::description);
serverConfig[config_key::name] = apiConfig.value(config_key::name); serverConfig[config_key::name] = newServerConfig.value(config_key::name);
} }
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString(); auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
serverConfig[config_key::defaultContainer] = defaultContainer; 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; return;
} }
@@ -146,6 +184,15 @@ QStringList ApiController::getProxyUrls()
QList<QSslError> sslErrors; QList<QSslError> sslErrors;
QNetworkReply *reply; 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) { for (const auto &proxyStorageUrl : proxyStorageUrl) {
request.setUrl(proxyStorageUrl); request.setUrl(proxyStorageUrl);
reply = amnApp->manager()->get(request); reply = amnApp->manager()->get(request);
@@ -166,11 +213,23 @@ QStringList ApiController::getProxyUrls()
EVP_PKEY *privateKey = nullptr; EVP_PKEY *privateKey = nullptr;
QByteArray responseBody; QByteArray responseBody;
try { try {
QByteArray key = PROD_PROXY_STORAGE_KEY; if (!m_isDevEnvironment) {
QSimpleCrypto::QRsa rsa; QCryptographicHash hash(QCryptographicHash::Sha512);
privateKey = rsa.getPrivateKeyFromByteArray(key, ""); hash.addData(key);
responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING); 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 (...) { } catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload"; qCritical() << "error loading private key from environment variables or decrypting payload";
return {}; return {};
} }
@@ -292,38 +351,44 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) { responseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
m_proxyUrls = getProxyUrls(); 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) { for (const QString &proxyUrl : m_proxyUrls) {
qDebug() << "Go to the next endpoint";
request.setUrl(QString("%1v1/services").arg(proxyUrl)); request.setUrl(QString("%1v1/services").arg(proxyUrl));
reply->deleteLater(); // delete the previous reply
reply = amnApp->manager()->get(request); reply = amnApp->manager()->get(request);
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) { responseBody = reply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) {
break; break;
} }
reply->deleteLater();
} }
} }
responseBody = reply->readAll();
auto errorCode = checkErrors(sslErrors, reply); auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater(); reply->deleteLater();
return errorCode; return errorCode;
} }
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig) const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData,
QJsonObject &serverConfig)
{ {
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess(); IosController::Instance()->requestInetAccess();
QThread::msleep(10); QThread::msleep(10);
#endif #endif
QNetworkAccessManager manager;
QNetworkRequest request; QNetworkRequest request;
request.setTransferTimeout(7000); request.setTransferTimeout(7000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -339,6 +404,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
} }
apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::serviceType] = serviceType;
apiPayload[configKey::uuid] = installationUuid; apiPayload[configKey::uuid] = installationUuid;
if (!authData.isEmpty()) {
apiPayload[configKey::authData] = authData;
}
QSimpleCrypto::QBlockCipher blockCipher; QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32); QByteArray key = blockCipher.generatePrivateSalt(32);
@@ -361,6 +429,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
QSimpleCrypto::QRsa rsa; QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey); publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) { } catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables"; qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey; return ErrorCode::ApiMissingAgwPublicKey;
} }
@@ -370,14 +439,16 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto? } catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when encrypting the request body"; qCritical() << "error when encrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
} }
QJsonObject requestBody; QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson()); QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
QEventLoop wait; QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
@@ -386,37 +457,43 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) { auto encryptedResponseBody = reply->readAll();
if (m_proxyUrls.isEmpty()) {
m_proxyUrls = getProxyUrls(); 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) { for (const QString &proxyUrl : m_proxyUrls) {
qDebug() << "Go to the next endpoint";
request.setUrl(QString("%1v1/config").arg(proxyUrl)); request.setUrl(QString("%1v1/config").arg(proxyUrl));
reply = manager.post(request, QJsonDocument(requestBody).toJson()); reply->deleteLater(); // delete the previous reply
reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) { encryptedResponseBody = reply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
break; break;
} }
reply->deleteLater();
} }
} }
auto errorCode = checkErrors(sslErrors, reply); auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater();
if (errorCode) { if (errorCode) {
return errorCode; return errorCode;
} }
auto encryptedResponseBody = reply->readAll();
reply->deleteLater();
try { try {
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
} catch (...) { // todo change error handling in QSimpleCrypto? } catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when decrypting the request body"; qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
} }
return errorCode; return errorCode;
+1 -1
View File
@@ -21,7 +21,7 @@ public slots:
ErrorCode getServicesList(QByteArray &responseBody); ErrorCode getServicesList(QByteArray &responseBody);
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig); const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig);
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);
+2
View File
@@ -96,6 +96,7 @@ namespace amnezia
// import and install errors // import and install errors
ImportInvalidConfigError = 900, ImportInvalidConfigError = 900,
ImportOpenConfigError = 901,
// Android errors // Android errors
AndroidError = 1000, AndroidError = 1000,
@@ -107,6 +108,7 @@ namespace amnezia
ApiConfigTimeoutError = 1103, ApiConfigTimeoutError = 1103,
ApiConfigSslError = 1104, ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105, ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,
+2
View File
@@ -50,6 +50,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
// Android errors // Android errors
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
@@ -61,6 +62,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break; case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break; case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
// QFile errors // QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+5 -18
View File
@@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false; return false;
} }
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { if (!dnsutils()->restoreResolvers()) {
return false; return false;
} }
@@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
} }
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
if (!supportDnsUtils()) {
return true;
}
if ((config.m_hopType == InterfaceConfig::MultiHopExit) || if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
(config.m_hopType == InterfaceConfig::SingleHop)) { (config.m_hopType == InterfaceConfig::SingleHop)) {
QList<QHostAddress> resolvers; QList<QHostAddress> resolvers;
@@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) {
} }
// Cleanup DNS // Cleanup DNS
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { if (!dnsutils()->restoreResolvers()) {
return false; logger.warning() << "Failed to restore DNS resolvers.";
}
if (!wgutils()->interfaceExists()) {
logger.warning() << "Wireguard interface does not exist.";
return false;
} }
// Cleanup peers and routing // Cleanup peers and routing
@@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) {
} }
m_excludedAddrSet.clear(); m_excludedAddrSet.clear();
// Delete the interface
if (!wgutils()->deleteInterface()) {
return false;
}
m_connections.clear(); m_connections.clear();
return true; // Delete the interface
return wgutils()->deleteInterface();
} }
QString Daemon::logs() { QString Daemon::logs() {
-1
View File
@@ -69,7 +69,6 @@ class Daemon : public QObject {
virtual WireguardUtils* wgutils() const = 0; virtual WireguardUtils* wgutils() const = 0;
virtual bool supportIPUtils() const { return false; } virtual bool supportIPUtils() const { return false; }
virtual IPUtils* iputils() { return nullptr; } virtual IPUtils* iputils() { return nullptr; }
virtual bool supportDnsUtils() const { return false; }
virtual DnsUtils* dnsutils() { return nullptr; } virtual DnsUtils* dnsutils() { return nullptr; }
static bool parseStringList(const QJsonObject& obj, const QString& name, static bool parseStringList(const QJsonObject& obj, const QString& name,
+13 -4
View File
@@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
logger.debug() << "Command received:" << type; logger.debug() << "Command received:" << type;
// It is expected that sometimes the client will request backend logs
// before the first authentication. In these cases we just return empty
// logs.
if (type == "logs") {
QJsonObject obj;
obj.insert("type", "logs");
obj.insert("logs", "");
write(obj);
return;
}
if (type == "activate") { if (type == "activate") {
InterfaceConfig config; InterfaceConfig config;
if (!Daemon::parseConfig(obj, config)) { if (!Daemon::parseConfig(obj, config)) {
@@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
if (type == "status") { if (type == "status") {
QJsonObject obj = Daemon::instance()->getStatus(); QJsonObject obj = Daemon::instance()->getStatus();
obj.insert("type", "status"); obj.insert("type", "status");
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); write(obj);
m_socket->write("\n");
return; return;
} }
@@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
QJsonObject obj; QJsonObject obj;
obj.insert("type", "logs"); obj.insert("type", "logs");
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|")); obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); write(obj);
m_socket->write("\n");
return; return;
} }
+2 -2
View File
@@ -34,8 +34,8 @@ LocalSocketController::LocalSocketController() {
m_socket = new QLocalSocket(this); m_socket = new QLocalSocket(this);
connect(m_socket, &QLocalSocket::connected, this, connect(m_socket, &QLocalSocket::connected, this,
&LocalSocketController::daemonConnected); &LocalSocketController::daemonConnected);
connect(m_socket, &QLocalSocket::disconnected, this, connect(m_socket, &QLocalSocket::disconnected, this,
&LocalSocketController::disconnected); [&] { errorOccurred(QLocalSocket::PeerClosedError); });
connect(m_socket, &QLocalSocket::errorOccurred, this, connect(m_socket, &QLocalSocket::errorOccurred, this,
&LocalSocketController::errorOccurred); &LocalSocketController::errorOccurred);
connect(m_socket, &QLocalSocket::readyRead, this, connect(m_socket, &QLocalSocket::readyRead, this,
@@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon {
protected: protected:
WireguardUtils* wgutils() const override { return m_wgutils; } WireguardUtils* wgutils() const override { return m_wgutils; }
bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; } bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; } IPUtils* iputils() override { return m_iputils; }
@@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon {
protected: protected:
WireguardUtils* wgutils() const override { return m_wgutils; } WireguardUtils* wgutils() const override { return m_wgutils; }
bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; } bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; } IPUtils* iputils() override { return m_iputils; }
@@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon {
protected: protected:
bool run(Op op, const InterfaceConfig& config) override; bool run(Op op, const InterfaceConfig& config) override;
WireguardUtils* wgutils() const override { return m_wgutils; } WireguardUtils* wgutils() const override { return m_wgutils; }
bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
private: private:
@@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) {
// device should contain : for e.g C: // device should contain : for e.g C:
return ""; return "";
} }
QByteArray buffer(2048, 0xFF); QByteArray buffer(2048, 0xFFu);
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter), auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
(wchar_t*)buffer.data(), buffer.size() / 2); (wchar_t*)buffer.data(), buffer.size() / 2);
@@ -248,7 +248,7 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
} }
if (result != NO_ERROR) { if (result != NO_ERROR) {
logger.error() << "Failed to create route to" logger.error() << "Failed to create route to"
<< logger.sensitive(prefix.toString()) << prefix.toString()
<< "result:" << result; << "result:" << result;
} }
return result == NO_ERROR; return result == NO_ERROR;
@@ -265,7 +265,7 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
} }
if (result != NO_ERROR) { if (result != NO_ERROR) {
logger.error() << "Failed to delete route to" logger.error() << "Failed to delete route to"
<< logger.sensitive(prefix.toString()) << prefix.toString()
<< "result:" << result; << "result:" << result;
} }
return result == NO_ERROR; return result == NO_ERROR;
+1 -1
View File
@@ -21,7 +21,7 @@
#include "platforms/windows/windowsutils.h" #include "platforms/windows/windowsutils.h"
constexpr const char* VPN_NAME = "AmneziaVPN"; constexpr const char* VPN_NAME = "AmneziaVPN";
constexpr const char* WIREGUARD_DIR = "WireGuard"; constexpr const char* WIREGUARD_DIR = "AmneziaWG";
constexpr const char* DATA_DIR = "Data"; constexpr const char* DATA_DIR = "Data";
namespace { namespace {
+10 -7
View File
@@ -34,13 +34,13 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection() void ConnectionController::openConnection()
{ {
// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
// if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
// { {
// emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
// return; return;
// } }
// #endif #endif
int serverIndex = m_serversModel->getDefaultServerIndex(); int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
@@ -51,6 +51,9 @@ void ConnectionController::openConnection()
if (configVersion == ApiConfigSources::Telegram if (configVersion == ApiConfigSources::Telegram
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromTelegram(); emit updateApiConfigFromTelegram();
} else if (configVersion == ApiConfigSources::AmneziaGateway
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by end_date event"; qDebug() << "attempt to update api config by end_date event";
if (configVersion == ApiConfigSources::Telegram) { if (configVersion == ApiConfigSources::Telegram) {
+5 -4
View File
@@ -39,11 +39,12 @@ namespace
const QString amneziaConfigPatternUserName = "userName"; const QString amneziaConfigPatternUserName = "userName";
const QString amneziaConfigPatternPassword = "password"; const QString amneziaConfigPatternPassword = "password";
const QString amneziaFreeConfigPattern = "api_key"; const QString amneziaFreeConfigPattern = "api_key";
const QString amneziaPremiumConfigPattern = "auth_data";
const QString backupPattern = "Servers/serversList"; const QString backupPattern = "Servers/serversList";
if (config.contains(backupPattern)) { if (config.contains(backupPattern)) {
return ConfigTypes::Backup; return ConfigTypes::Backup;
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern)
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) { && config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia; return ConfigTypes::Amnezia;
@@ -84,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
return extractConfigFromData(data); return extractConfigFromData(data);
} }
emit importErrorOccurred(tr("Unable to open file"), false); emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
return false; return false;
} }
@@ -188,12 +189,12 @@ bool ImportController::extractConfigFromData(QString data)
if (!m_serversModel->getServersCount()) { if (!m_serversModel->getServersCount()) {
emit restoreAppConfig(config.toUtf8()); emit restoreAppConfig(config.toUtf8());
} else { } else {
emit importErrorOccurred(tr("Invalid configuration file"), false); emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
} }
break; break;
} }
case ConfigTypes::Invalid: { case ConfigTypes::Invalid: {
emit importErrorOccurred(tr("Invalid configuration file"), false); emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
break; break;
} }
} }
-1
View File
@@ -54,7 +54,6 @@ public slots:
signals: signals:
void importFinished(); void importFinished();
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
void qrDecodingFinished(); void qrDecodingFinished();
+12 -35
View File
@@ -32,32 +32,8 @@ namespace
constexpr char availableCountries[] = "available_countries"; constexpr char availableCountries[] = "available_countries";
constexpr char apiConfig[] = "api_config"; constexpr char apiConfig[] = "api_config";
constexpr char authData[] = "auth_data";
} }
#ifdef Q_OS_WINDOWS
QString getNextDriverLetter()
{
QProcess drivesProc;
drivesProc.start("wmic logicaldisk get caption");
drivesProc.waitForFinished();
QString drives = drivesProc.readAll();
qDebug() << drives;
QString letters = "CFGHIJKLMNOPQRSTUVWXYZ";
QString letter;
for (int i = letters.size() - 1; i > 0; i--) {
letter = letters.at(i);
if (!drives.contains(letter + ":"))
break;
}
if (letter == "C:") {
// set err info
qDebug() << "Can't find free drive letter";
return "";
}
return letter;
}
#endif
} }
InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel, InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
@@ -135,10 +111,10 @@ void InstallController::install(DockerContainer container, int port, TransportPr
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
} else if (container == DockerContainer::Sftp) { } else if (container == DockerContainer::Sftp) {
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
containerConfig.insert(config_key::password, Utils::getRandomString(10)); containerConfig.insert(config_key::password, Utils::getRandomString(16));
} else if (container == DockerContainer::Socks5Proxy) { } else if (container == DockerContainer::Socks5Proxy) {
containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName); containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName);
containerConfig.insert(config_key::password, Utils::getRandomString(10)); containerConfig.insert(config_key::password, Utils::getRandomString(16));
} }
config.insert(config_key::container, ContainerProps::containerToString(container)); config.insert(config_key::container, ContainerProps::containerToString(container));
@@ -667,7 +643,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw
QString hostname = serverCredentials.hostName; QString hostname = serverCredentials.hostName;
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
mountPath = getNextDriverLetter() + ":"; mountPath = Utils::getNextDriverLetter() + ":";
// QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3")
// .arg(labelTftpUserNameText()) // .arg(labelTftpUserNameText())
// .arg(labelTftpPortText()) // .arg(labelTftpPortText())
@@ -768,7 +744,7 @@ bool InstallController::checkSshConnection(QSharedPointer<ServerController> serv
} else { } else {
if (output.contains(tr("Please login as the user"))) { if (output.contains(tr("Please login as the user"))) {
output.replace("\n", ""); output.replace("\n", "");
emit installationErrorOccurred(output); emit wrongInstallationUser(output);
return false; return false;
} }
} }
@@ -826,7 +802,7 @@ bool InstallController::installServiceFromApi()
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(), ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
m_apiServicesModel->getSelectedServiceType(), m_apiServicesModel->getSelectedServiceType(),
m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig); m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode); emit installationErrorOccurred(errorCode);
return false; return false;
@@ -853,24 +829,25 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto authData = serverConfig.value(configKey::authData).toObject();
QJsonObject newServerConfig; QJsonObject newServerConfig;
ErrorCode errorCode = ErrorCode errorCode = apiController.getConfigForService(
apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode,
apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig); authData, newServerConfig);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode); emit installationErrorOccurred(errorCode);
return false; return false;
} }
QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject();
newApiConfig.insert(configKey::serviceInfo, apiConfig.value(configKey::serviceInfo));
newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode));
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::apiConfig, newApiConfig);
newServerConfig.insert(configKey::authData, authData);
m_serversModel->editServer(newServerConfig, serverIndex); m_serversModel->editServer(newServerConfig, serverIndex);
if (reloadServiceConfig) { if (reloadServiceConfig) {
+1 -1
View File
@@ -75,8 +75,8 @@ signals:
void removeAllContainersFinished(const QString &finishedMessage); void removeAllContainersFinished(const QString &finishedMessage);
void removeProcessedContainerFinished(const QString &finishedMessage); void removeProcessedContainerFinished(const QString &finishedMessage);
void installationErrorOccurred(const QString &errorMessage);
void installationErrorOccurred(ErrorCode errorCode); void installationErrorOccurred(ErrorCode errorCode);
void wrongInstallationUser(const QString &message);
void serverAlreadyExists(int serverIndex); void serverAlreadyExists(int serverIndex);
+4
View File
@@ -39,6 +39,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
case CountryNameRole: { case CountryNameRole: {
return countryInfo.value(configKey::serverCountryName).toString(); return countryInfo.value(configKey::serverCountryName).toString();
} }
case CountryImageCodeRole: {
return countryInfo.value(configKey::serverCountryCode).toString().toUpper();
}
} }
return QVariant(); return QVariant();
@@ -76,5 +79,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[CountryNameRole] = "countryName"; roles[CountryNameRole] = "countryName";
roles[CountryCodeRole] = "countryCode"; roles[CountryCodeRole] = "countryCode";
roles[CountryImageCodeRole] = "countryImageCode";
return roles; return roles;
} }
+2 -1
View File
@@ -11,7 +11,8 @@ class ApiCountryModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
CountryNameRole = Qt::UserRole + 1, CountryNameRole = Qt::UserRole + 1,
CountryCodeRole CountryCodeRole,
CountryImageCodeRole
}; };
explicit ApiCountryModel(QObject *parent = nullptr); explicit ApiCountryModel(QObject *parent = nullptr);
+1 -1
View File
@@ -771,5 +771,5 @@ const QString ServersModel::getDefaultServerImagePathCollapsed()
if (countryCode.isEmpty()) { if (countryCode.isEmpty()) {
return ""; return "";
} }
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode); return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper());
} }
@@ -84,7 +84,7 @@ DrawerType2 {
Layout.topMargin: 16 Layout.topMargin: 16
text: qsTr("Share") text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg" leftImageSource: "qrc:/images/controls/share-2.svg"
KeyNavigation.tab: copyConfigTextButton KeyNavigation.tab: copyConfigTextButton
@@ -120,7 +120,7 @@ DrawerType2 {
borderWidth: 1 borderWidth: 1
text: qsTr("Copy") text: qsTr("Copy")
imageSource: "qrc:/images/controls/copy.svg" leftImageSource: "qrc:/images/controls/copy.svg"
Keys.onReturnPressed: { copyConfigTextButton.clicked() } Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Keys.onEnterPressed: { copyConfigTextButton.clicked() } Keys.onEnterPressed: { copyConfigTextButton.clicked() }
@@ -143,7 +143,7 @@ DrawerType2 {
borderWidth: 1 borderWidth: 1
text: qsTr("Copy config string") text: qsTr("Copy config string")
imageSource: "qrc:/images/controls/copy.svg" leftImageSource: "qrc:/images/controls/copy.svg"
KeyNavigation.tab: showSettingsButton KeyNavigation.tab: showSettingsButton
} }
+14 -8
View File
@@ -22,9 +22,10 @@ Button {
property int borderWidth: 0 property int borderWidth: 0
property int borderFocusedWidth: 1 property int borderFocusedWidth: 1
property string imageSource property string leftImageSource
property string rightImageSource property string rightImageSource
property string leftImageColor: textColor property string leftImageColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false property bool squareLeftSide: false
@@ -127,18 +128,23 @@ Button {
anchors.centerIn: parent anchors.centerIn: parent
Image { Image {
Layout.preferredHeight: 20 id: leftImage
Layout.preferredWidth: 20 source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
source: root.imageSource
visible: root.imageSource === "" ? false : true
layer { layer {
enabled: true enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay { effect: ColorOverlay {
color: leftImageColor color: leftImageColor
} }
} }
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
} }
ButtonTextType { ButtonTextType {
@@ -14,7 +14,7 @@ Popup {
visible: false visible: false
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: Qt.rgba(14/255, 14/255, 17/255, 0.8) color: AmneziaStyle.color.translucentMidnightBlack
} }
background: Rectangle { background: Rectangle {
+1 -1
View File
@@ -19,7 +19,7 @@ RadioButton {
property string textColor: AmneziaStyle.color.midnightBlack property string textColor: AmneziaStyle.color.midnightBlack
property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
property string selectedBorderColor: AmneziaStyle.color.goldenApricot property string selectedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent property string defaultBodredColor: AmneziaStyle.color.transparent
property int borderWidth: 0 property int borderWidth: 0
@@ -145,6 +145,7 @@ Button {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: true hoverEnabled: true
enabled: root.enabled
onEntered: { onEntered: {
backgroundRect.color = root.hoveredColor backgroundRect.color = root.hoveredColor
+1 -1
View File
@@ -92,7 +92,7 @@ Item {
id: background id: background
anchors.fill: parent anchors.fill: parent
color: root.isCollapsed ? AmneziaStyle.color.transparent : Qt.rgba(14/255, 14/255, 17/255, 0.8) color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
Behavior on color { Behavior on color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
+1 -1
View File
@@ -24,7 +24,7 @@ Popup {
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
visible: root.closeButtonVisible visible: root.closeButtonVisible
color: Qt.rgba(14/255, 14/255, 17/255, 0.8) color: AmneziaStyle.color.translucentMidnightBlack
} }
onOpened: { onOpened: {
@@ -183,7 +183,7 @@ Item {
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
text: root.buttonText text: root.buttonText
imageSource: root.buttonImageSource leftImageSource: root.buttonImageSource
anchors.top: content.top anchors.top: content.top
anchors.bottom: content.bottom anchors.bottom: content.bottom
@@ -14,7 +14,7 @@ Popup {
visible: false visible: false
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
color: Qt.rgba(14/255, 14/255, 17/255, 0.8) color: AmneziaStyle.color.translucentMidnightBlack
} }
background: Rectangle { background: Rectangle {
@@ -22,5 +22,9 @@ QtObject {
readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12) readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12)
readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08) readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08)
readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05) readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05)
readonly property color translucentMidnightBlack: Qt.rgba(14/255, 14/255, 17/255, 0.8)
readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
} }
} }
+33 -12
View File
@@ -98,7 +98,6 @@ PageType {
pressedColor: AmneziaStyle.color.sheerWhite pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.mutedGray textColor: AmneziaStyle.color.mutedGray
leftImageColor: AmneziaStyle.color.transparent
borderWidth: 0 borderWidth: 0
buttonTextLabel.lineHeight: 20 buttonTextLabel.lineHeight: 20
@@ -110,7 +109,7 @@ PageType {
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
rightImageSource: "qrc:/images/controls/chevron-down.svg" rightImageSource: "qrc:/images/controls/chevron-down.svg"
Keys.onEnterPressed: splitTunnelingButton.clicked() Keys.onEnterPressed: splitTunnelingButton.clicked()
@@ -166,6 +165,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: 0
Component.onCompleted: { Component.onCompleted: {
drawer.collapsedHeight = collapsed.implicitHeight drawer.collapsedHeight = collapsed.implicitHeight
@@ -267,18 +267,39 @@ PageType {
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 89 : 44 Layout.topMargin: 8
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
spacing: 0 spacing: 0
Image { BasicButtonType {
Layout.rightMargin: 8 enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
visible: source !== "" hoverEnabled: enabled
source: ServersModel.defaultServerImagePathCollapsed
} implicitHeight: 36
leftPadding: 16
rightPadding: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.transparent
textColor: AmneziaStyle.color.mutedGray
buttonTextLabel.lineHeight: 16
buttonTextLabel.font.pixelSize: 13
buttonTextLabel.font.weight: 400
LabelTextType {
id: collapsedServerMenuDescription
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
leftImageSource: ServersModel.defaultServerImagePathCollapsed
changeLeftImageSize: false
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
onClicked: {
ServersModel.processedIndex = ServersModel.defaultIndex
PageController.goToPage(PageEnum.PageSettingsServerInfo)
}
} }
} }
} }
@@ -316,8 +337,8 @@ PageType {
rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonImageColor: AmneziaStyle.color.midnightBlack
rootButtonBackgroundColor: AmneziaStyle.color.paleGray rootButtonBackgroundColor: AmneziaStyle.color.paleGray
rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray
rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
rootButtonTextTopMargin: 8 rootButtonTextTopMargin: 8
@@ -90,7 +90,7 @@ PageType {
Layout.rightMargin: 32 Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
source: "qrc:/countriesFlags/images/flagKit/" + countryCode + ".svg" source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
} }
} }
@@ -132,8 +132,8 @@ PageType {
implicitHeight: 32 implicitHeight: 32
defaultColor: "transparent" defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08) hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: Qt.rgba(1, 1, 1, 0.12) pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Reload API config") text: qsTr("Reload API config")
@@ -172,8 +172,8 @@ PageType {
implicitHeight: 32 implicitHeight: 32
defaultColor: "transparent" defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08) hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: Qt.rgba(1, 1, 1, 0.12) pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Remove from application") text: qsTr("Remove from application")
@@ -16,83 +16,82 @@ PageType {
defaultActiveFocusItem: focusItem defaultActiveFocusItem: focusItem
FlickableType { ColumnLayout {
id: fl id: header
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.left: parent.left
contentHeight: content.height anchors.right: parent.right
ColumnLayout { spacing: 0
id: content
anchors.top: parent.top Item {
anchors.left: parent.left id: focusItem
anchors.right: parent.right KeyNavigation.tab: backButton
}
spacing: 0 BackButtonType {
id: backButton
Item { Layout.topMargin: 20
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType {
id: backButton
Layout.topMargin: 20
// KeyNavigation.tab: fileButton.rightButton // KeyNavigation.tab: fileButton.rightButton
} }
HeaderType { HeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 Layout.topMargin: 8
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 32 Layout.bottomMargin: 16
headerText: qsTr("VPN by Amnezia") headerText: qsTr("VPN by Amnezia")
descriptionText: qsTr("Choose a VPN service that suits your needs.") descriptionText: qsTr("Choose a VPN service that suits your needs.")
} }
}
ListView { ListView {
id: containers id: servicesListView
width: parent.width anchors.top: header.bottom
height: containers.contentItem.height anchors.right: parent.right
spacing: 16 anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: 16
spacing: 0
currentIndex: 1 currentIndex: 1
interactive: false clip: true
model: ApiServicesModel model: ApiServicesModel
delegate: Item { ScrollBar.vertical: ScrollBar {}
implicitWidth: containers.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout { delegate: Item {
id: delegateContent implicitWidth: servicesListView.width
implicitHeight: delegateContent.implicitHeight
anchors.top: parent.top ColumnLayout {
anchors.left: parent.left id: delegateContent
anchors.right: parent.right
CardWithIconsType { anchors.fill: parent
id: card
Layout.fillWidth: true CardWithIconsType {
Layout.rightMargin: 16 id: card
Layout.leftMargin: 16
headerText: name Layout.fillWidth: true
bodyText: cardDescription Layout.rightMargin: 16
footerText: price Layout.leftMargin: 16
Layout.bottomMargin: 16
rightImageSource: "qrc:/images/controls/chevron-right.svg" headerText: name
bodyText: cardDescription
footerText: price
onClicked: { rightImageSource: "qrc:/images/controls/chevron-right.svg"
if (isServiceAvailable) {
ApiServicesModel.setServiceIndex(index) enabled: isServiceAvailable
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
} onClicked: {
} if (isServiceAvailable) {
ApiServicesModel.setServiceIndex(index)
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
} }
} }
} }
@@ -47,8 +47,9 @@ PageType {
KeyNavigation.tab: textKey.textField KeyNavigation.tab: textKey.textField
} }
HeaderType { HeaderType {
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -56,7 +57,7 @@ PageType {
headerText: qsTr("Connection") headerText: qsTr("Connection")
actionButtonImage: PageController.isStartPageVisible() ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() { actionButtonFunction: function() {
moreActionsDrawer.open() moreActionsDrawer.open()
} }
@@ -67,18 +68,19 @@ PageType {
parent: root parent: root
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.35 expandedHeight: root.height * 0.5
expandedContent: ColumnLayout { expandedContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: 16 spacing: 0
anchors.rightMargin: 16
HeaderType { HeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Settings") headerText: qsTr("Settings")
} }
@@ -87,9 +89,12 @@ PageType {
id: switcher id: switcher
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Enable logs") text: qsTr("Enable logs")
visible: PageController.isStartPageVisible()
checked: SettingsController.isLoggingEnabled checked: SettingsController.isLoggingEnabled
onCheckedChanged: { onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) { if (checked !== SettingsController.isLoggingEnabled) {
@@ -98,6 +103,28 @@ PageType {
} }
} }
LabelWithButtonType {
id: supportUuid
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Support tag")
descriptionText: SettingsController.getInstallationUuid()
descriptionOnTop: true
rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray
visible: SettingsController.getInstallationUuid() !== ""
clickedFunction: function() {
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
if (!GC.isMobile()) {
this.rightButton.forceActiveFocus()
}
}
}
} }
} }
} }
@@ -37,7 +37,7 @@ PageType {
Connections { Connections {
target: ImportController target: ImportController
function onImportErrorOccurred(errorMessage, goToPageHome) { function onImportErrorOccurred(error, goToPageHome) {
if (goToPageHome) { if (goToPageHome) {
PageController.goToStartPage() PageController.goToStartPage()
} else { } else {
+41 -44
View File
@@ -573,7 +573,7 @@ PageType {
visible: accessTypeSelector.currentIndex === 0 visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share") text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg" leftImageSource: "qrc:/images/controls/share-2.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem) Keys.onTabPressed: lastItemTabClicked(focusItem)
@@ -772,7 +772,8 @@ PageType {
} }
} }
anchors.fill: parent width: root.width
height: root.height
expandedContent: ColumnLayout { expandedContent: ColumnLayout {
id: expandedContent id: expandedContent
@@ -783,8 +784,6 @@ PageType {
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.rightMargin: 16 anchors.rightMargin: 16
spacing: 8
onImplicitHeightChanged: { onImplicitHeightChanged: {
clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32 clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32
} }
@@ -797,57 +796,54 @@ PageType {
} }
} }
Header2Type { Header2TextType {
Layout.fillWidth: true Layout.maximumWidth: parent.width
headerText: clientName
}
ColumnLayout
{
id: textColumn
property string textColor: AmneziaStyle.color.mutedGray
Layout.bottomMargin: 24 Layout.bottomMargin: 24
ParagraphTextType { text: clientName
color: textColumn.textColor maximumLineCount: 2
visible: creationDate wrapMode: Text.Wrap
Layout.fillWidth: true elide: Qt.ElideRight
}
text: qsTr("Creation date: %1").arg(creationDate) ParagraphTextType {
} color: AmneziaStyle.color.mutedGray
visible: creationDate
Layout.fillWidth: true
ParagraphTextType { text: qsTr("Creation date: %1").arg(creationDate)
color: textColumn.textColor }
visible: latestHandshake
Layout.fillWidth: true
text: qsTr("Latest handshake: %1").arg(latestHandshake) ParagraphTextType {
} color: AmneziaStyle.color.mutedGray
visible: latestHandshake
Layout.fillWidth: true
ParagraphTextType { text: qsTr("Latest handshake: %1").arg(latestHandshake)
color: textColumn.textColor }
visible: dataReceived
Layout.fillWidth: true
text: qsTr("Data received: %1").arg(dataReceived) ParagraphTextType {
} color: AmneziaStyle.color.mutedGray
visible: dataReceived
Layout.fillWidth: true
ParagraphTextType { text: qsTr("Data received: %1").arg(dataReceived)
color: textColumn.textColor }
visible: dataSent
Layout.fillWidth: true
text: qsTr("Data sent: %1").arg(dataSent) ParagraphTextType {
} color: AmneziaStyle.color.mutedGray
visible: dataSent
Layout.fillWidth: true
ParagraphTextType { text: qsTr("Data sent: %1").arg(dataSent)
color: textColumn.textColor }
visible: allowedIps
Layout.fillWidth: true
text: qsTr("Allowed IPs: %1").arg(allowedIps) ParagraphTextType {
} color: AmneziaStyle.color.mutedGray
visible: allowedIps
Layout.fillWidth: true
text: qsTr("Allowed IPs: %1").arg(allowedIps)
} }
Item { Item {
@@ -952,6 +948,7 @@ PageType {
BasicButtonType { BasicButtonType {
id: revokeButton id: revokeButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8
defaultColor: AmneziaStyle.color.transparent defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite hoveredColor: AmneziaStyle.color.translucentWhite
+1 -1
View File
@@ -135,7 +135,7 @@ PageType {
Layout.topMargin: 40 Layout.topMargin: 40
text: qsTr("Share") text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg" leftImageSource: "qrc:/images/controls/share-2.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem) Keys.onTabPressed: lastItemTabClicked(focusItem)
+4
View File
@@ -123,6 +123,10 @@ PageType {
} }
} }
function onWrongInstallationUser(message) {
onInstallationErrorOccurred(message)
}
function onUpdateContainerFinished(message) { function onUpdateContainerFinished(message) {
PageController.showNotificationMessage(message) PageController.showNotificationMessage(message)
PageController.closePage() PageController.closePage()
Regular → Executable
+139 -30
View File
@@ -10,18 +10,72 @@
#include <QJsonObject> #include <QJsonObject>
#include "utilities.h" #include "utilities.h"
#include "version.h"
#ifdef Q_OS_WINDOWS
QString printErrorMessage(DWORD errorCode) {
LPVOID lpMsgBuf;
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS;
DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
FormatMessageW(
dwFlags,
NULL,
errorCode,
dwLanguageId,
(LPWSTR)&lpMsgBuf,
0,
NULL
);
QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return errorMsg.trimmed();
}
QString Utils::getNextDriverLetter()
{
DWORD drivesBitmask = GetLogicalDrives();
if (drivesBitmask == 0) {
DWORD error = GetLastError();
qDebug() << "GetLogicalDrives failed. Error code:" << error;
return "";
}
QString letters = "FGHIJKLMNOPQRSTUVWXYZ";
QString availableLetter;
for (int i = letters.size() - 1; i >= 0; --i) {
QChar letterChar = letters.at(i);
int driveIndex = letterChar.toLatin1() - 'A';
if ((drivesBitmask & (1 << driveIndex)) == 0) {
availableLetter = letterChar;
break;
}
}
if (availableLetter.isEmpty()) {
qDebug() << "Can't find free drive letter";
return "";
}
return availableLetter;
}
#endif
QString Utils::getRandomString(int len) QString Utils::getRandomString(int len)
{ {
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString; QString randomString;
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length(); randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
QChar nextChar = possibleCharacters.at(index);
randomString.append(nextChar);
} }
return randomString; return randomString;
} }
@@ -109,30 +163,34 @@ QString Utils::usrExecutable(const QString &baseName)
bool Utils::processIsRunning(const QString &fileName, const bool fullFlag) bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QProcess process; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
process.setReadChannel(QProcess::StandardOutput); if (hSnapshot == INVALID_HANDLE_VALUE) {
process.setProcessChannelMode(QProcess::MergedChannels); qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot";
process.start("wmic.exe", return false;
QStringList() << "/OUTPUT:STDOUT" }
<< "PROCESS"
<< "get"
<< "Caption");
process.waitForStarted();
process.waitForFinished();
QString processData(process.readAll());
QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
foreach (const QString &rawLine, processList) {
const QString line = rawLine.simplified();
if (line.isEmpty()) {
continue;
}
if (line == fileName) { PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hSnapshot, &pe32)) {
CloseHandle(hSnapshot);
qWarning() << "Utils::processIsRunning error Process32FirstW";
return false;
}
do {
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) {
CloseHandle(hSnapshot);
return true; return true;
} }
} } while (Process32NextW(hSnapshot, &pe32));
CloseHandle(hSnapshot);
return false; return false;
#elif defined(Q_OS_IOS)
#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
return false; return false;
#else #else
QProcess process; QProcess process;
@@ -150,13 +208,45 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
#endif #endif
} }
void Utils::killProcessByName(const QString &name) bool Utils::killProcessByName(const QString &name)
{ {
qDebug().noquote() << "Kill process" << name; qDebug().noquote() << "Kill process" << name;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F"); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
#elif defined Q_OS_IOS if (hSnapshot == INVALID_HANDLE_VALUE)
return; return false;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
bool success = false;
if (Process32FirstW(hSnapshot, &pe32)) {
do {
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
if (exeFile.compare(name, Qt::CaseInsensitive) == 0) {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (hProcess != NULL) {
if (TerminateProcess(hProcess, 0)) {
success = true;
} else {
DWORD error = GetLastError();
qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
}
CloseHandle(hProcess);
} else {
DWORD error = GetLastError();
qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
}
}
} while (Process32NextW(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return success;
#elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
return false;
#else #else
QProcess::execute(QString("pkill %1").arg(name)); QProcess::execute(QString("pkill %1").arg(name));
#endif #endif
@@ -244,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
} }
#endif #endif
void Utils::logException(const std::exception &e)
{
qCritical() << e.what();
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
logException(nested);
} catch (...) {}
}
void Utils::logException(const std::exception_ptr &eptr)
{
try {
if (eptr) std::rethrow_exception(eptr);
} catch (const std::exception &e) {
logException(e);
} catch (...) {}
}
Regular → Executable
+7 -2
View File
@@ -7,7 +7,8 @@
#include <QJsonDocument> #include <QJsonDocument>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "Windows.h" #include <windows.h>
#include <tlhelp32.h>
#endif #endif
class Utils : public QObject class Utils : public QObject
@@ -27,15 +28,19 @@ public:
static bool initializePath(const QString &path); static bool initializePath(const QString &path);
static bool processIsRunning(const QString &fileName, const bool fullFlag = false); static bool processIsRunning(const QString &fileName, const bool fullFlag = false);
static void killProcessByName(const QString &name); static bool killProcessByName(const QString &name);
static QString openVpnExecPath(); static QString openVpnExecPath();
static QString wireguardExecPath(); static QString wireguardExecPath();
static QString certUtilPath(); static QString certUtilPath();
static QString tun2socksPath(); static QString tun2socksPath();
static void logException(const std::exception &e);
static void logException(const std::exception_ptr &eptr = std::current_exception());
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
static QString getNextDriverLetter();
#endif #endif
}; };
+3 -2
View File
@@ -56,14 +56,15 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
{ {
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex()); auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
if (IpcClient::Interface()) { if (IpcClient::Interface()) {
if (state == Vpn::ConnectionState::Connected) { if (state == Vpn::ConnectionState::Connected) {
IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->resetIpStack();
IpcClient::Interface()->flushDns(); IpcClient::Interface()->flushDns();
if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) { if (!m_vpnConfiguration.value(config_key::configVersion).toInt() && container != DockerContainer::Awg
&& container != DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB