diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 488233ce..a6666b7c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -16,7 +16,10 @@ jobs:
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Install Qt'
@@ -83,7 +86,10 @@ jobs:
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Get sources'
@@ -146,7 +152,10 @@ jobs:
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Setup xcode'
@@ -238,7 +247,10 @@ jobs:
QT_VERSION: 6.4.3
QIF_VERSION: 4.6
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Setup xcode'
@@ -301,10 +313,13 @@ jobs:
env:
ANDROID_BUILD_PLATFORM: android-34
- QT_VERSION: 6.7.2
+ QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml
index dffb3ab1..2bcbd8c6 100644
--- a/.github/workflows/tag-deploy.yml
+++ b/.github/workflows/tag-deploy.yml
@@ -16,7 +16,10 @@ jobs:
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 287d92f2..b5e64e32 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
-project(${PROJECT} VERSION 4.8.0.1
+project(${PROJECT} VERSION 4.8.2.3
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
-set(APP_ANDROID_VERSION_CODE 59)
+set(APP_ANDROID_VERSION_CODE 2069)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
diff --git a/README.md b/README.md
index e4a6bf0c..eed800f5 100644
--- a/README.md
+++ b/README.md
@@ -10,21 +10,17 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
-
-
-
-
-
-
-
+
+[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads)
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
+
## Features
@@ -37,7 +33,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## 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://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)
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 2de5db48..05f9f17c 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -25,10 +25,11 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
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_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
+add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp
index 2d06b443..4e25097d 100644
--- a/client/amnezia_application.cpp
+++ b/client/amnezia_application.cpp
@@ -111,10 +111,11 @@ void AmneziaApplication::init()
qFatal("Android controller initialization failed");
}
- connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
- m_pageController->goToPageHome();
+ connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
+ emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
- m_pageController->goToPageViewConfig();
+ data.clear();
+ emit m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
@@ -122,16 +123,16 @@ void AmneziaApplication::init()
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
- connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
- m_pageController->goToPageHome();
+ connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
+ emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
- m_pageController->goToPageViewConfig();
+ emit m_pageController->goToPageViewConfig();
});
- connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
- m_pageController->goToPageHome();
+ connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
+ emit m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup();
- m_settingsController->importBackupFromOutside(filePath);
+ emit m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
diff --git a/client/android/gradle.properties b/client/android/gradle.properties
index 5a27838c..ce651e1c 100644
--- a/client/android/gradle.properties
+++ b/client/android/gradle.properties
@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
# For development copy and set local values for these parameters in local.properties
#androidCompileSdkVersion=android-34
#androidBuildToolsVersion=34.0.0
-#qtMinSdkVersion=24
+#qtMinSdkVersion=26
#qtTargetSdkVersion=34
#androidNdkVersion=26.1.10909125
#qtTargetAbiList=x86_64
diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt
index b5c382be..6e682aa4 100644
--- a/client/android/protocolApi/src/main/kotlin/Protocol.kt
+++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt
@@ -1,6 +1,5 @@
package org.amnezia.vpn.protocol
-import android.annotation.SuppressLint
import android.content.Context
import android.net.IpPrefix
import android.net.VpnService
@@ -8,9 +7,6 @@ import android.net.VpnService.Builder
import android.os.Build
import android.system.OsConstants
import androidx.annotation.RequiresApi
-import java.io.File
-import java.io.FileOutputStream
-import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
index b923e2c7..41443f6e 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
@@ -21,6 +21,7 @@ import android.os.Looper
import android.os.Message
import android.os.Messenger
import android.provider.Settings
+import android.view.MotionEvent
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
import android.widget.Toast
@@ -159,7 +160,7 @@ class AmneziaActivity : QtActivity() {
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Log.d(TAG, "Create Amnezia activity: $intent")
+ Log.d(TAG, "Create Amnezia activity")
loadLibs()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -202,7 +203,7 @@ class AmneziaActivity : QtActivity() {
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
)
) {
- Log.d(
+ Log.v(
TAG, "Notification state changed: ${it?.action}, blocked = " +
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
)
@@ -216,7 +217,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
- Log.d(TAG, "onNewIntent: $intent")
+ Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent)
}
@@ -405,7 +406,7 @@ class AmneziaActivity : QtActivity() {
@MainThread
private fun startVpn(vpnConfig: String) {
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 (proto.serviceClass == vpnProto?.serviceClass) {
vpnProto = proto
@@ -518,7 +519,7 @@ class AmneziaActivity : QtActivity() {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
- Log.d(TAG, "Save file to $uri")
+ Log.v(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
@@ -567,7 +568,7 @@ class AmneziaActivity : QtActivity() {
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
val uri = it?.data?.toString() ?: ""
- Log.d(TAG, "Open file: $uri")
+ Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
@@ -757,6 +758,66 @@ class AmneziaActivity : QtActivity() {
return blockingCall { billingRepository.queryPurchases() }
}
+ // 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
*/
diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
index 937127ee..8d108bc3 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
@@ -300,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
) {
it?.action?.let { action ->
- Log.d(TAG, "Broadcast request received: $action")
+ Log.v(TAG, "Broadcast request received: $action")
when (action) {
ACTION_CONNECT -> connect()
ACTION_DISCONNECT -> disconnect()
@@ -317,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
)
) {
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) {
enableNotification()
} else {
@@ -450,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
serviceNotification.isNotificationEnabled() &&
getSystemService()?.isInteractive != false
) {
- Log.d(TAG, "Launch traffic stats update")
+ Log.v(TAG, "Launch traffic stats update")
trafficStats.reset()
startTrafficStatsUpdateJob()
}
diff --git a/client/android/src/org/amnezia/vpn/AuthActivity.kt b/client/android/src/org/amnezia/vpn/AuthActivity.kt
index 2593315c..46401548 100644
--- a/client/android/src/org/amnezia/vpn/AuthActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AuthActivity.kt
@@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
- Log.d(TAG, "Authentication succeeded")
+ Log.v(TAG, "Authentication succeeded")
QtAndroidController.onAuthResult(true)
finish()
}
diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
index 9faa30d0..49823a36 100644
--- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
+++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Log.d(TAG, "Create Import Config Activity: $intent")
+ Log.v(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
- Log.d(TAG, "onNewIntent: $intent")
+ Log.v(TAG, "onNewIntent: $intent")
intent.let(::readConfig)
}
private fun readConfig(intent: Intent) {
when (intent.action) {
ACTION_SEND -> {
- Log.d(TAG, "Process SEND action, type: ${intent.type}")
+ Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) {
"application/octet-stream" -> {
intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
}
ACTION_VIEW -> {
- Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
+ Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) {
"file", "content" -> {
intent.data?.let { uri ->
diff --git a/client/android/src/org/amnezia/vpn/ServiceNotification.kt b/client/android/src/org/amnezia/vpn/ServiceNotification.kt
index f4707731..47e8f263 100644
--- a/client/android/src/org/amnezia/vpn/ServiceNotification.kt
+++ b/client/android/src/org/amnezia/vpn/ServiceNotification.kt
@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
val speedString = if (state == CONNECTED) zeroSpeed else null
- Log.d(TAG, "Build notification: $serverName, $state")
+ Log.v(TAG, "Build notification: $serverName, $state")
return notificationBuilder
.setSmallIcon(R.drawable.ic_amnezia_round)
@@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
fun isNotificationEnabled(): Boolean {
if (!context.isNotificationPermissionGranted()) return false
if (!notificationManager.areNotificationsEnabled()) return false
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
- ?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
- }
- return true
+ return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
+ it.importance != NotificationManager.IMPORTANCE_NONE
+ } ?: true
}
@SuppressLint("MissingPermission")
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
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))
}
}
diff --git a/client/android/utils/src/main/kotlin/LibraryLoader.kt b/client/android/utils/src/main/kotlin/LibraryLoader.kt
index f1c6465e..8def18d0 100644
--- a/client/android/utils/src/main/kotlin/LibraryLoader.kt
+++ b/client/android/utils/src/main/kotlin/LibraryLoader.kt
@@ -46,7 +46,7 @@ object LibraryLoader {
System.loadLibrary(libraryName)
return
} 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
try {
diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt
index a656b9ea..da11c200 100644
--- a/client/android/utils/src/main/kotlin/Log.kt
+++ b/client/android/utils/src/main/kotlin/Log.kt
@@ -1,8 +1,6 @@
package org.amnezia.vpn.util
import android.content.Context
-import android.icu.text.DateFormat
-import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
@@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
-import java.util.Date
-import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
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 |
*/
object Log {
- private val dateTimeFormat: Any =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
- else object : ThreadLocal() {
- override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
- }
+ private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
private lateinit var logDir: File
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 {
- val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
- } else {
- @Suppress("UNCHECKED_CAST")
- (dateTimeFormat as ThreadLocal).get()?.format(Date())
- }
+ val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt
index b71bf393..1cab5535 100644
--- a/client/android/utils/src/main/kotlin/net/NetworkState.kt
+++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt
@@ -42,18 +42,12 @@ class NetworkState(
private val networkCallback: NetworkCallback by lazy(NONE) {
object : NetworkCallback() {
override fun onAvailable(network: Network) {
- Log.d(TAG, "onAvailable: $network")
+ Log.v(TAG, "onAvailable: $network")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
- Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- checkNetworkState(network, networkCapabilities)
- } else {
- handler.post {
- checkNetworkState(network, networkCapabilities)
- }
- }
+ Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
+ checkNetworkState(network, networkCapabilities)
}
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
@@ -73,11 +67,11 @@ class NetworkState(
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
- Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
+ Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
}
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")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ } else {
val numberAttempts = 300
var attemptCount = 0
while(true) {
@@ -108,8 +102,6 @@ class NetworkState(
}
}
}
- } else {
- connectivityManager.requestNetwork(networkRequest, networkCallback)
}
isListenerBound = true
}
diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
index ac11374b..e93834f4 100644
--- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
+++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
@@ -66,7 +66,7 @@ open class Wireguard : Protocol() {
try {
delay(1000)
var log = getLogcat(time)
- Log.d(TAG, "First waiting log: $log")
+ Log.v(TAG, "First waiting log: $log")
// check that there is a connection log,
// to avoid infinite connection
if (!log.contains("Attaching to interface")) {
diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt
index 6e37c9c2..08242525 100644
--- a/client/android/xray/src/main/kotlin/Xray.kt
+++ b/client/android/xray/src/main/kotlin/Xray.kt
@@ -130,8 +130,8 @@ class Xray : Protocol() {
LibXray.initXray(assetsPath)
val geoDir = File(assetsPath, "geo").absolutePath
val configPath = File(context.cacheDir, "config.json")
- Log.d(TAG, "xray.location.asset: $geoDir")
- Log.d(TAG, "config: $configPath")
+ Log.v(TAG, "xray.location.asset: $geoDir")
+ Log.v(TAG, "config: $configPath")
try {
configPath.writeText(configJson)
} catch (e: IOException) {
diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake
index c96d9ab8..34ca5bff 100644
--- a/client/cmake/android.cmake
+++ b/client/cmake/android.cmake
@@ -1,6 +1,6 @@
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
"The minimum API level supported by the application or library" FORCE)
diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp
index 5cdaa7ae..75a3f93c 100644
--- a/client/core/controllers/apiController.cpp
+++ b/client/core/controllers/apiController.cpp
@@ -1,5 +1,8 @@
#include "apiController.h"
+#include
+#include
+
#include
#include
#include
@@ -11,6 +14,7 @@
#include "amnezia_application.h"
#include "configurators/wireguard_configurator.h"
#include "core/enums/apiEnums.h"
+#include "utilities.h"
#include "version.h"
namespace
@@ -33,6 +37,7 @@ namespace
constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code";
constexpr char serviceType[] = "service_type";
+ constexpr char serviceInfo[] = "service_info";
constexpr char aesKey[] = "aes_key";
constexpr char aesIv[] = "aes_iv";
@@ -40,9 +45,10 @@ namespace
constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload";
- }
- const QStringList proxyStorageUrl = { "" };
+ constexpr char apiConfig[] = "api_config";
+ constexpr char authData[] = "auth_data";
+ }
ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply)
{
@@ -63,6 +69,28 @@ namespace
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(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)
@@ -94,8 +122,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
} else if (protocol == configKey::awg) {
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
- auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
- auto containers = serverConfig.value(config_key::containers).toArray();
+ auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
+ auto containers = newServerConfig.value(config_key::containers).toArray();
if (containers.isEmpty()) {
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);
container[containerName] = containerConfig;
containers.replace(0, container);
- serverConfig[config_key::containers] = containers;
- configStr = QString(QJsonDocument(serverConfig).toJson());
+ newServerConfig[config_key::containers] = containers;
+ configStr = QString(QJsonDocument(newServerConfig).toJson());
}
- QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
- serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
- serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
- serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
- serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
+ QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
+ serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
+ serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
+ serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
+ serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
- if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
- serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
- serverConfig[config_key::description] = apiConfig.value(config_key::description);
- serverConfig[config_key::name] = apiConfig.value(config_key::name);
+ if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
+ serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
+ serverConfig[config_key::description] = newServerConfig.value(config_key::description);
+ serverConfig[config_key::name] = newServerConfig.value(config_key::name);
}
- auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
+ auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
serverConfig[config_key::defaultContainer] = defaultContainer;
+ QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
+ map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
+ auto apiConfig = QJsonObject::fromVariantMap(map);
+
+ if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
+ apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject());
+ }
+
+ serverConfig[configKey::apiConfig] = apiConfig;
+
return;
}
@@ -146,6 +184,15 @@ QStringList ApiController::getProxyUrls()
QList sslErrors;
QNetworkReply *reply;
+ QStringList proxyStorageUrl;
+ if (m_isDevEnvironment) {
+ proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
+ } else {
+ proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
+ }
+
+ QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
+
for (const auto &proxyStorageUrl : proxyStorageUrl) {
request.setUrl(proxyStorageUrl);
reply = amnApp->manager()->get(request);
@@ -166,11 +213,23 @@ QStringList ApiController::getProxyUrls()
EVP_PKEY *privateKey = nullptr;
QByteArray responseBody;
try {
- QByteArray key = PROD_PROXY_STORAGE_KEY;
- QSimpleCrypto::QRsa rsa;
- privateKey = rsa.getPrivateKeyFromByteArray(key, "");
- responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
+ if (!m_isDevEnvironment) {
+ QCryptographicHash hash(QCryptographicHash::Sha512);
+ hash.addData(key);
+ QByteArray hashResult = hash.result().toHex();
+
+ QByteArray key = QByteArray::fromHex(hashResult.left(64));
+ QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
+
+ QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
+
+ QSimpleCrypto::QBlockCipher blockCipher;
+ responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
+ } else {
+ responseBody = encryptedResponseBody;
+ }
} catch (...) {
+ Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload";
return {};
}
@@ -292,38 +351,44 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
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();
+ std::random_device randomDevice;
+ std::mt19937 generator(randomDevice());
+ std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
for (const QString &proxyUrl : m_proxyUrls) {
+ qDebug() << "Go to the next endpoint";
request.setUrl(QString("%1v1/services").arg(proxyUrl));
+ reply->deleteLater(); // delete the previous reply
reply = amnApp->manager()->get(request);
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
wait.exec();
- if (reply->error() != QNetworkReply::NetworkError::TimeoutError
- && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
+
+ responseBody = reply->readAll();
+ if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) {
break;
}
- reply->deleteLater();
}
}
- responseBody = reply->readAll();
auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater();
return errorCode;
}
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
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
- QNetworkAccessManager manager;
QNetworkRequest request;
request.setTransferTimeout(7000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -339,6 +404,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
}
apiPayload[configKey::serviceType] = serviceType;
apiPayload[configKey::uuid] = installationUuid;
+ if (!authData.isEmpty()) {
+ apiPayload[configKey::authData] = authData;
+ }
QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32);
@@ -361,6 +429,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
+ Utils::logException();
qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey;
}
@@ -370,14 +439,16 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
+ Utils::logException();
qCritical() << "error when encrypting the request body";
+ return ErrorCode::ApiConfigDecryptionError;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
- QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson());
+ QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
QEventLoop wait;
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 &errors) { sslErrors = errors; });
wait.exec();
- if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
- if (m_proxyUrls.isEmpty()) {
- m_proxyUrls = getProxyUrls();
- }
+ auto encryptedResponseBody = reply->readAll();
+
+ if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
+ m_proxyUrls = getProxyUrls();
+ std::random_device randomDevice;
+ std::mt19937 generator(randomDevice());
+ std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
for (const QString &proxyUrl : m_proxyUrls) {
+ qDebug() << "Go to the next endpoint";
request.setUrl(QString("%1v1/config").arg(proxyUrl));
- reply = 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);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
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;
}
- reply->deleteLater();
}
}
auto errorCode = checkErrors(sslErrors, reply);
+ reply->deleteLater();
if (errorCode) {
return errorCode;
}
- auto encryptedResponseBody = reply->readAll();
- reply->deleteLater();
try {
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
} catch (...) { // todo change error handling in QSimpleCrypto?
+ Utils::logException();
qCritical() << "error when decrypting the request body";
+ return ErrorCode::ApiConfigDecryptionError;
}
return errorCode;
diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h
index 1f811498..bcb25f96 100644
--- a/client/core/controllers/apiController.h
+++ b/client/core/controllers/apiController.h
@@ -21,7 +21,7 @@ public slots:
ErrorCode getServicesList(QByteArray &responseBody);
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:
void errorOccurred(ErrorCode errorCode);
diff --git a/client/core/defs.h b/client/core/defs.h
index 62e469c9..f871ae83 100644
--- a/client/core/defs.h
+++ b/client/core/defs.h
@@ -96,6 +96,7 @@ namespace amnezia
// import and install errors
ImportInvalidConfigError = 900,
+ ImportOpenConfigError = 901,
// Android errors
AndroidError = 1000,
@@ -107,6 +108,7 @@ namespace amnezia
ApiConfigTimeoutError = 1103,
ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105,
+ ApiConfigDecryptionError = 1106,
// QFile errors
OpenError = 1200,
diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp
index f880ef3e..3252c50c 100644
--- a/client/core/errorstrings.cpp
+++ b/client/core/errorstrings.cpp
@@ -50,6 +50,7 @@ QString errorString(ErrorCode code) {
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::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
// Android errors
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::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); 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
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp
index 3e237e9c..a234860b 100644
--- a/client/daemon/daemon.cpp
+++ b/client/daemon/daemon.cpp
@@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
- if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
+ if (!dnsutils()->restoreResolvers()) {
return false;
}
@@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
}
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
- if (!supportDnsUtils()) {
- return true;
- }
-
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
(config.m_hopType == InterfaceConfig::SingleHop)) {
QList resolvers;
@@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) {
}
// Cleanup DNS
- if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
- return false;
- }
-
- if (!wgutils()->interfaceExists()) {
- logger.warning() << "Wireguard interface does not exist.";
- return false;
+ if (!dnsutils()->restoreResolvers()) {
+ logger.warning() << "Failed to restore DNS resolvers.";
}
// Cleanup peers and routing
@@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) {
}
m_excludedAddrSet.clear();
- // Delete the interface
- if (!wgutils()->deleteInterface()) {
- return false;
- }
-
m_connections.clear();
- return true;
+ // Delete the interface
+ return wgutils()->deleteInterface();
}
QString Daemon::logs() {
diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h
index d3d8c34d..3d418d70 100644
--- a/client/daemon/daemon.h
+++ b/client/daemon/daemon.h
@@ -69,7 +69,6 @@ class Daemon : public QObject {
virtual WireguardUtils* wgutils() const = 0;
virtual bool supportIPUtils() const { return false; }
virtual IPUtils* iputils() { return nullptr; }
- virtual bool supportDnsUtils() const { return false; }
virtual DnsUtils* dnsutils() { return nullptr; }
static bool parseStringList(const QJsonObject& obj, const QString& name,
diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp
index 1a49b7e5..edbc4c9b 100644
--- a/client/daemon/daemonlocalserverconnection.cpp
+++ b/client/daemon/daemonlocalserverconnection.cpp
@@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
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") {
InterfaceConfig config;
if (!Daemon::parseConfig(obj, config)) {
@@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
if (type == "status") {
QJsonObject obj = Daemon::instance()->getStatus();
obj.insert("type", "status");
- m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
- m_socket->write("\n");
+ write(obj);
return;
}
@@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
QJsonObject obj;
obj.insert("type", "logs");
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
- m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
- m_socket->write("\n");
+ write(obj);
return;
}
diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp
index 4d040288..5e9f0f97 100644
--- a/client/mozilla/localsocketcontroller.cpp
+++ b/client/mozilla/localsocketcontroller.cpp
@@ -34,8 +34,8 @@ LocalSocketController::LocalSocketController() {
m_socket = new QLocalSocket(this);
connect(m_socket, &QLocalSocket::connected, this,
&LocalSocketController::daemonConnected);
- connect(m_socket, &QLocalSocket::disconnected, this,
- &LocalSocketController::disconnected);
+ connect(m_socket, &QLocalSocket::disconnected, this,
+ [&] { errorOccurred(QLocalSocket::PeerClosedError); });
connect(m_socket, &QLocalSocket::errorOccurred, this,
&LocalSocketController::errorOccurred);
connect(m_socket, &QLocalSocket::readyRead, this,
diff --git a/client/platforms/linux/daemon/linuxdaemon.h b/client/platforms/linux/daemon/linuxdaemon.h
index 7f5d27b7..dbac8cee 100644
--- a/client/platforms/linux/daemon/linuxdaemon.h
+++ b/client/platforms/linux/daemon/linuxdaemon.h
@@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon {
protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
- bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; }
diff --git a/client/platforms/macos/daemon/macosdaemon.h b/client/platforms/macos/daemon/macosdaemon.h
index a48c326c..4181648e 100644
--- a/client/platforms/macos/daemon/macosdaemon.h
+++ b/client/platforms/macos/daemon/macosdaemon.h
@@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon {
protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
- bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; }
diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h
index 9d051bae..7e38c41e 100644
--- a/client/platforms/windows/daemon/windowsdaemon.h
+++ b/client/platforms/windows/daemon/windowsdaemon.h
@@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon {
protected:
bool run(Op op, const InterfaceConfig& config) override;
WireguardUtils* wgutils() const override { return m_wgutils; }
- bool supportDnsUtils() const override { return true; }
DnsUtils* dnsutils() override { return m_dnsutils; }
private:
diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp
index 39941933..c4e893b2 100644
--- a/client/platforms/windows/daemon/windowssplittunnel.cpp
+++ b/client/platforms/windows/daemon/windowssplittunnel.cpp
@@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) {
// device should contain : for e.g C:
return "";
}
- QByteArray buffer(2048, 0xFF);
+ QByteArray buffer(2048, 0xFFu);
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
(wchar_t*)buffer.data(), buffer.size() / 2);
diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp
index a68551d7..1a220235 100644
--- a/client/platforms/windows/daemon/wireguardutilswindows.cpp
+++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp
@@ -248,7 +248,7 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
}
if (result != NO_ERROR) {
logger.error() << "Failed to create route to"
- << logger.sensitive(prefix.toString())
+ << prefix.toString()
<< "result:" << result;
}
return result == NO_ERROR;
@@ -265,7 +265,7 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
}
if (result != NO_ERROR) {
logger.error() << "Failed to delete route to"
- << logger.sensitive(prefix.toString())
+ << prefix.toString()
<< "result:" << result;
}
return result == NO_ERROR;
diff --git a/client/platforms/windows/windowscommons.cpp b/client/platforms/windows/windowscommons.cpp
index c0a14dda..4c0d8176 100644
--- a/client/platforms/windows/windowscommons.cpp
+++ b/client/platforms/windows/windowscommons.cpp
@@ -21,7 +21,7 @@
#include "platforms/windows/windowsutils.h"
constexpr const char* VPN_NAME = "AmneziaVPN";
-constexpr const char* WIREGUARD_DIR = "WireGuard";
+constexpr const char* WIREGUARD_DIR = "AmneziaWG";
constexpr const char* DATA_DIR = "Data";
namespace {
diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp
index c7f95000..f8516f6e 100644
--- a/client/ui/controllers/connectionController.cpp
+++ b/client/ui/controllers/connectionController.cpp
@@ -34,13 +34,13 @@ ConnectionController::ConnectionController(const QSharedPointer &s
void ConnectionController::openConnection()
{
-// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
-// if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
-// {
-// emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
-// return;
-// }
-// #endif
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
+ {
+ emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
+ return;
+ }
+#endif
int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
@@ -51,6 +51,9 @@ void ConnectionController::openConnection()
if (configVersion == ApiConfigSources::Telegram
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromTelegram();
+ } else if (configVersion == ApiConfigSources::AmneziaGateway
+ && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
+ emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by end_date event";
if (configVersion == ApiConfigSources::Telegram) {
diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp
index 261551ea..f7e96bff 100644
--- a/client/ui/controllers/importController.cpp
+++ b/client/ui/controllers/importController.cpp
@@ -39,11 +39,12 @@ namespace
const QString amneziaConfigPatternUserName = "userName";
const QString amneziaConfigPatternPassword = "password";
const QString amneziaFreeConfigPattern = "api_key";
+ const QString amneziaPremiumConfigPattern = "auth_data";
const QString backupPattern = "Servers/serversList";
if (config.contains(backupPattern)) {
return ConfigTypes::Backup;
- } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
+ } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern)
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia;
@@ -84,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
return extractConfigFromData(data);
}
- emit importErrorOccurred(tr("Unable to open file"), false);
+ emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
return false;
}
@@ -188,12 +189,12 @@ bool ImportController::extractConfigFromData(QString data)
if (!m_serversModel->getServersCount()) {
emit restoreAppConfig(config.toUtf8());
} else {
- emit importErrorOccurred(tr("Invalid configuration file"), false);
+ emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
}
break;
}
case ConfigTypes::Invalid: {
- emit importErrorOccurred(tr("Invalid configuration file"), false);
+ emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
break;
}
}
diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h
index 61205253..05e320a5 100644
--- a/client/ui/controllers/importController.h
+++ b/client/ui/controllers/importController.h
@@ -54,7 +54,6 @@ public slots:
signals:
void importFinished();
- void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
void qrDecodingFinished();
diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp
old mode 100644
new mode 100755
index c6f17057..306e7f38
--- a/client/ui/controllers/installController.cpp
+++ b/client/ui/controllers/installController.cpp
@@ -32,32 +32,8 @@ namespace
constexpr char availableCountries[] = "available_countries";
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, const QSharedPointer &containersModel,
@@ -135,10 +111,10 @@ void InstallController::install(DockerContainer container, int port, TransportPr
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
} else if (container == DockerContainer::Sftp) {
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) {
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));
@@ -667,7 +643,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw
QString hostname = serverCredentials.hostName;
#ifdef Q_OS_WINDOWS
- mountPath = getNextDriverLetter() + ":";
+ mountPath = Utils::getNextDriverLetter() + ":";
// QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3")
// .arg(labelTftpUserNameText())
// .arg(labelTftpPortText())
@@ -768,7 +744,7 @@ bool InstallController::checkSshConnection(QSharedPointer serv
} else {
if (output.contains(tr("Please login as the user"))) {
output.replace("\n", "");
- emit installationErrorOccurred(output);
+ emit wrongInstallationUser(output);
return false;
}
}
@@ -826,7 +802,7 @@ bool InstallController::installServiceFromApi()
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
m_apiServicesModel->getSelectedServiceType(),
- m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig);
+ m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
@@ -853,24 +829,26 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
+ auto authData = serverConfig.value(configKey::authData).toObject();
QJsonObject newServerConfig;
- ErrorCode errorCode =
- apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
- apiConfig.value(configKey::serviceType).toString(),
- apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig);
+ ErrorCode errorCode = apiController.getConfigForService(
+ m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
+ apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode,
+ authData, newServerConfig);
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
}
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::serviceType, apiConfig.value(configKey::serviceType));
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
newServerConfig.insert(configKey::apiConfig, newApiConfig);
+ newServerConfig.insert(configKey::authData, authData);
+ newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
m_serversModel->editServer(newServerConfig, serverIndex);
if (reloadServiceConfig) {
diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h
index 7eea216a..d7ab3553 100644
--- a/client/ui/controllers/installController.h
+++ b/client/ui/controllers/installController.h
@@ -75,8 +75,8 @@ signals:
void removeAllContainersFinished(const QString &finishedMessage);
void removeProcessedContainerFinished(const QString &finishedMessage);
- void installationErrorOccurred(const QString &errorMessage);
void installationErrorOccurred(ErrorCode errorCode);
+ void wrongInstallationUser(const QString &message);
void serverAlreadyExists(int serverIndex);
diff --git a/client/ui/models/apiCountryModel.cpp b/client/ui/models/apiCountryModel.cpp
index ae58329f..922a9d56 100644
--- a/client/ui/models/apiCountryModel.cpp
+++ b/client/ui/models/apiCountryModel.cpp
@@ -39,6 +39,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
case CountryNameRole: {
return countryInfo.value(configKey::serverCountryName).toString();
}
+ case CountryImageCodeRole: {
+ return countryInfo.value(configKey::serverCountryCode).toString().toUpper();
+ }
}
return QVariant();
@@ -76,5 +79,6 @@ QHash ApiCountryModel::roleNames() const
QHash roles;
roles[CountryNameRole] = "countryName";
roles[CountryCodeRole] = "countryCode";
+ roles[CountryImageCodeRole] = "countryImageCode";
return roles;
}
diff --git a/client/ui/models/apiCountryModel.h b/client/ui/models/apiCountryModel.h
index 8789158b..b9e243d0 100644
--- a/client/ui/models/apiCountryModel.h
+++ b/client/ui/models/apiCountryModel.h
@@ -11,7 +11,8 @@ class ApiCountryModel : public QAbstractListModel
public:
enum Roles {
CountryNameRole = Qt::UserRole + 1,
- CountryCodeRole
+ CountryCodeRole,
+ CountryImageCodeRole
};
explicit ApiCountryModel(QObject *parent = nullptr);
diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp
index 7d3be2cb..7445d60f 100644
--- a/client/ui/models/clientManagementModel.cpp
+++ b/client/ui/models/clientManagementModel.cpp
@@ -77,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
{
beginResetModel();
m_clientsTable = QJsonArray();
+ endResetModel();
ErrorCode error = ErrorCode::NoError;
@@ -90,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the clientsTable file from the server";
- endResetModel();
return error;
}
+ beginResetModel();
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) {
@@ -601,5 +602,6 @@ QHash ClientManagementModel::roleNames() const
roles[LatestHandshakeRole] = "latestHandshake";
roles[DataReceivedRole] = "dataReceived";
roles[DataSentRole] = "dataSent";
+ roles[AllowedIpsRole] = "allowedIps";
return roles;
}
diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp
index 85e5dae2..c87499a7 100644
--- a/client/ui/models/servers_model.cpp
+++ b/client/ui/models/servers_model.cpp
@@ -771,5 +771,5 @@ const QString ServersModel::getDefaultServerImagePathCollapsed()
if (countryCode.isEmpty()) {
return "";
}
- return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode);
+ return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper());
}
diff --git a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
index 234e5142..120313cd 100644
--- a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
+++ b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
@@ -90,7 +90,7 @@ PageType {
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
- source: "qrc:/countriesFlags/images/flagKit/" + countryCode + ".svg"
+ source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
}
}
diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
index 7f7cf9e1..7c031997 100644
--- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
@@ -49,6 +49,8 @@ PageType {
HeaderType {
+ property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
+
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
@@ -56,7 +58,7 @@ PageType {
headerText: qsTr("Connection")
- actionButtonImage: PageController.isStartPageVisible() ? "qrc:/images/controls/more-vertical.svg" : ""
+ actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() {
moreActionsDrawer.open()
}
@@ -67,18 +69,19 @@ PageType {
parent: root
anchors.fill: parent
- expandedHeight: root.height * 0.35
+ expandedHeight: root.height * 0.5
expandedContent: ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.leftMargin: 16
- anchors.rightMargin: 16
+ spacing: 0
HeaderType {
Layout.fillWidth: true
Layout.topMargin: 32
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
headerText: qsTr("Settings")
}
@@ -87,9 +90,12 @@ PageType {
id: switcher
Layout.fillWidth: true
Layout.topMargin: 16
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
text: qsTr("Enable logs")
+ visible: PageController.isStartPageVisible()
checked: SettingsController.isLoggingEnabled
onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) {
@@ -98,6 +104,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()
+ }
+ }
+ }
}
}
}
diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
index 3aac1555..92048f36 100644
--- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
@@ -37,7 +37,7 @@ PageType {
Connections {
target: ImportController
- function onImportErrorOccurred(errorMessage, goToPageHome) {
+ function onImportErrorOccurred(error, goToPageHome) {
if (goToPageHome) {
PageController.goToStartPage()
} else {
diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml
index 6640df36..617b1091 100644
--- a/client/ui/qml/Pages2/PageShare.qml
+++ b/client/ui/qml/Pages2/PageShare.qml
@@ -772,7 +772,8 @@ PageType {
}
}
- anchors.fill: parent
+ width: root.width
+ height: root.height
expandedContent: ColumnLayout {
id: expandedContent
@@ -783,8 +784,6 @@ PageType {
anchors.leftMargin: 16
anchors.rightMargin: 16
- spacing: 8
-
onImplicitHeightChanged: {
clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32
}
@@ -797,57 +796,54 @@ PageType {
}
}
- Header2Type {
- Layout.fillWidth: true
-
- headerText: clientName
- }
-
- ColumnLayout
- {
- id: textColumn
- property string textColor: AmneziaStyle.color.mutedGray
+ Header2TextType {
+ Layout.maximumWidth: parent.width
Layout.bottomMargin: 24
- ParagraphTextType {
- color: textColumn.textColor
- visible: creationDate
- Layout.fillWidth: true
+ text: clientName
+ maximumLineCount: 2
+ wrapMode: Text.Wrap
+ elide: Qt.ElideRight
+ }
- text: qsTr("Creation date: %1").arg(creationDate)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: creationDate
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: latestHandshake
- Layout.fillWidth: true
+ text: qsTr("Creation date: %1").arg(creationDate)
+ }
- text: qsTr("Latest handshake: %1").arg(latestHandshake)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: latestHandshake
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: dataReceived
- Layout.fillWidth: true
+ text: qsTr("Latest handshake: %1").arg(latestHandshake)
+ }
- text: qsTr("Data received: %1").arg(dataReceived)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: dataReceived
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: dataSent
- Layout.fillWidth: true
+ text: qsTr("Data received: %1").arg(dataReceived)
+ }
- text: qsTr("Data sent: %1").arg(dataSent)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: dataSent
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: allowedIps
- Layout.fillWidth: true
+ text: qsTr("Data sent: %1").arg(dataSent)
+ }
- 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 {
@@ -952,6 +948,7 @@ PageType {
BasicButtonType {
id: revokeButton
Layout.fillWidth: true
+ Layout.topMargin: 8
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml
index bb6663fb..640c61ef 100644
--- a/client/ui/qml/Pages2/PageStart.qml
+++ b/client/ui/qml/Pages2/PageStart.qml
@@ -123,6 +123,10 @@ PageType {
}
}
+ function onWrongInstallationUser(message) {
+ onInstallationErrorOccurred(message)
+ }
+
function onUpdateContainerFinished(message) {
PageController.showNotificationMessage(message)
PageController.closePage()
diff --git a/client/utilities.cpp b/client/utilities.cpp
old mode 100644
new mode 100755
index 4047365f..1cc69aeb
--- a/client/utilities.cpp
+++ b/client/utilities.cpp
@@ -10,18 +10,72 @@
#include
#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)
{
- const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
-
+ const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString;
+
for (int i = 0; i < len; ++i) {
- quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
- QChar nextChar = possibleCharacters.at(index);
- randomString.append(nextChar);
+ randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
}
+
return randomString;
}
@@ -109,30 +163,34 @@ QString Utils::usrExecutable(const QString &baseName)
bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
{
#ifdef Q_OS_WIN
- QProcess process;
- process.setReadChannel(QProcess::StandardOutput);
- process.setProcessChannelMode(QProcess::MergedChannels);
- process.start("wmic.exe",
- 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;
- }
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE) {
+ qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot";
+ return false;
+ }
- 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;
}
- }
+ } while (Process32NextW(hSnapshot, &pe32));
+
+ CloseHandle(hSnapshot);
return false;
-#elif defined(Q_OS_IOS)
+
+#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
return false;
#else
QProcess process;
@@ -150,13 +208,45 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
#endif
}
-void Utils::killProcessByName(const QString &name)
+bool Utils::killProcessByName(const QString &name)
{
qDebug().noquote() << "Kill process" << name;
#ifdef Q_OS_WIN
- QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F");
-#elif defined Q_OS_IOS
- return;
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ 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
QProcess::execute(QString("pkill %1").arg(name));
#endif
@@ -244,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
}
#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 (...) {}
+}
diff --git a/client/utilities.h b/client/utilities.h
old mode 100644
new mode 100755
index 9bf8c82a..4a1985b1
--- a/client/utilities.h
+++ b/client/utilities.h
@@ -7,7 +7,8 @@
#include
#ifdef Q_OS_WIN
- #include "Windows.h"
+#include
+#include
#endif
class Utils : public QObject
@@ -27,15 +28,19 @@ public:
static bool initializePath(const QString &path);
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 wireguardExecPath();
static QString certUtilPath();
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
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
+ static QString getNextDriverLetter();
#endif
};
diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp
index 591e396f..ac881bd7 100644
--- a/client/vpnconnection.cpp
+++ b/client/vpnconnection.cpp
@@ -56,14 +56,15 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
{
#ifdef AMNEZIA_DESKTOP
- QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex());
+ auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
if (IpcClient::Interface()) {
if (state == Vpn::ConnectionState::Connected) {
IpcClient::Interface()->resetIpStack();
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 dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
diff --git a/metadata/img-readme/andr.png b/metadata/img-readme/andr.png
deleted file mode 100644
index a39cd52f..00000000
Binary files a/metadata/img-readme/andr.png and /dev/null differ
diff --git a/metadata/img-readme/download.png b/metadata/img-readme/download.png
new file mode 100644
index 00000000..0e6a8850
Binary files /dev/null and b/metadata/img-readme/download.png differ
diff --git a/metadata/img-readme/lin.png b/metadata/img-readme/lin.png
deleted file mode 100644
index 352eae5a..00000000
Binary files a/metadata/img-readme/lin.png and /dev/null differ
diff --git a/metadata/img-readme/mac.png b/metadata/img-readme/mac.png
deleted file mode 100644
index 2cbb32ae..00000000
Binary files a/metadata/img-readme/mac.png and /dev/null differ
diff --git a/metadata/img-readme/testiny.png b/metadata/img-readme/testiny.png
new file mode 100644
index 00000000..4f38a3a9
Binary files /dev/null and b/metadata/img-readme/testiny.png differ
diff --git a/metadata/img-readme/win.png b/metadata/img-readme/win.png
deleted file mode 100644
index 5a35cf49..00000000
Binary files a/metadata/img-readme/win.png and /dev/null differ