Compare commits

..

5 Commits

Author SHA1 Message Date
dranik
77418c7f79 fix disconnect Host 2026-06-04 17:35:44 +03:00
dranik
f975a75e23 remove comment 2026-06-04 15:56:37 +03:00
dranik
87b0a98460 reset file 2026-06-04 15:55:46 +03:00
dranik
17b34f7891 fix moved to if-statement 2026-06-04 15:53:24 +03:00
dranik
1c32561dd4 Fix: selfhosted server installations 2026-06-04 15:25:40 +03:00
180 changed files with 3669 additions and 6827 deletions

View File

@@ -157,7 +157,7 @@ jobs:
run: pip install "conan==2.28.0"
- name: 'Build dependencies'
run: cmake -S . -B build -DPREBUILTS_ONLY=1
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'
@@ -414,10 +414,6 @@ jobs:
matrix:
xcode-version: [16.2, 16.4, 26.4]
include:
- xcode-version: 16.2
os: macos-15
- xcode-version: 16.4
os: macos-15
- xcode-version: 26.4
os: macos-26
@@ -454,7 +450,7 @@ jobs:
# ------------------------------------------------------
Build-MacOS:
runs-on: macos-15
runs-on: macos-latest
needs: Bake-Prebuilts-MacOS
if: ${{ always() }}
@@ -501,7 +497,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.3.0'
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
@@ -554,10 +550,6 @@ jobs:
matrix:
xcode-version: [16.2, 16.4, 26.4]
include:
- xcode-version: 16.2
os: macos-15
- xcode-version: 16.4
os: macos-15
- xcode-version: 26.4
os: macos-26
@@ -594,7 +586,7 @@ jobs:
# ------------------------------------------------------
Build-MacOS-NE:
runs-on: macos-26
runs-on: macos-latest
needs: Bake-Prebuilts-MacOS-NE
if: ${{ always() }}
@@ -618,12 +610,6 @@ jobs:
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- uses: ./.github/actions/apple-setup-provisioning-profile
with:
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
@@ -661,6 +647,12 @@ jobs:
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup python'
uses: actions/setup-python@v6
with:

3
.gitmodules vendored
View File

@@ -7,6 +7,9 @@
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git

View File

@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.7)
set(AMNEZIAVPN_VERSION 4.9.0.1)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2134)
set(APP_ANDROID_VERSION_CODE 2122)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -38,14 +38,14 @@
## Links
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/amnezia.org/utm_source=github&utm_campaign=amnezia_website-read)
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Documentation
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Telegram support channel (Russian)
- [Get Premium for 6 or 12 months](https://storage.googleapis.com/amnezia/pay/?utm_source=github&utm_campaign=ampay-read)
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
## Tech

View File

@@ -28,21 +28,21 @@
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) и XRay.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
## Ссылки
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/amnezia.org/utm_source=github&utm_campaign=amnezia_website-read)
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Документация
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [Оформите Premium на 6 или 12 месяцев](https://storage.googleapis.com/amnezia/pay/?utm_source=github&utm_campaign=ampay-read)
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии

1
client/3rd/QSimpleCrypto vendored Submodule

View File

@@ -103,7 +103,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR}/../common/crypto
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
@@ -258,10 +257,6 @@ install(SCRIPT ${QT_DEPLOY_SCRIPT}
if (APPLE AND NOT IOS AND NOT MACOS_NE)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
)
endif()
if (LINUX AND NOT ANDROID)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")

View File

@@ -22,7 +22,6 @@
#include "logger.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "ui/utils/mtProxyPublicHostInput.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
@@ -120,13 +119,7 @@ void AmneziaApplication::init()
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
win->show();
#else
if (!m_coreController || !m_coreController->pageController()->shouldStartMinimized()) {
win->show();
}
#endif
}
},
Qt::QueuedConnection);
@@ -222,9 +215,6 @@ void AmneziaApplication::registerTypes()
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
qmlRegisterType<PublicHostInputValidator>("TelemtConfig", 1, 0, "PublicHostInputValidator");
amnezia::declareQmlProtocolEnum();
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="135"
android:startColor="#2A2A2E"
android:centerColor="#17171A"
android:endColor="#0E0E11" />
</shape>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_amnezia_round"
android:insetLeft="19.5%"
android:insetTop="19.5%"
android:insetRight="19.5%"
android:insetBottom="19.5%" />

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NoActionBar">
<item name="android:windowBackground">@color/black</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/icon</item>
</style>
</resources>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#0E0E11</color>
</resources>

View File

@@ -42,7 +42,6 @@ import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.File
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -764,13 +763,7 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
.firstOrNull { pkg ->
try { packageManager.getPackageInfo(pkg, 0); true }
catch (_: PackageManager.NameNotFoundException) { false }
}
val intent = if (!isOnTv() && systemPickerPackage != null) {
val intent = if (!isOnTv()) {
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
@@ -796,7 +789,6 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*"
}
}
`package` = systemPickerPackage
}
} else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
@@ -808,11 +800,8 @@ class AmneziaActivity : QtActivity() {
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
showNoFileBrowserAlertDialog()
}
val uri = it?.data?.let { u ->
if (u.scheme == "content") {
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
}
u
val uri = it?.data?.apply {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
if (uri.isNotEmpty()) {
@@ -852,12 +841,7 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Get fd for $fileName")
return blockingCall(Dispatchers.IO) {
try {
val uri = Uri.parse(fileName)
pfd = if (uri.scheme == "file") {
ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)
} else {
contentResolver.openFileDescriptor(uri, "r")
}
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
pfd?.fd ?: -1
} catch (e: Exception) {
Log.e(TAG, "Failed to get fd: $e")
@@ -1077,10 +1061,12 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun sendTouch(x: Float, y: Float) {
Log.v(TAG, "Send touch: $x, $y")
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
blockingCall {
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
}
}
}

View File

@@ -1,36 +1,30 @@
package org.amnezia.vpn
import android.Manifest
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.amnezia.vpn.util.Log
import java.io.File
private const val TAG = "TvFilePicker"
private const val READ_STORAGE_REQUEST_CODE = 1001
class TvFilePicker : ComponentActivity() {
// SAF launcher for Android 10+ where File API is blocked by scoped storage
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activities.all {
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
@@ -38,140 +32,38 @@ class TvFilePicker : ComponentActivity() {
}
return intent
}
}) { uri ->
}) {
setResult(RESULT_OK, Intent().apply {
data = uri
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
private val directoryStack = ArrayDeque<File>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
launchSaf()
} else {
checkPermissionAndBrowse()
}
getFile()
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
navigateBack()
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent")
getFile()
}
private fun launchSaf() {
private fun getFile() {
try {
safLauncher.launch(arrayOf("*/*"))
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "No SAF activity found")
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
finish()
} catch (e: Exception) {
Log.e(TAG, "SAF launch failed: $e")
Log.e(TAG, "Failed to get file: $e")
setResult(RESULT_CANCELED)
finish()
}
}
private fun checkPermissionAndBrowse() {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_STORAGE_REQUEST_CODE)
} else {
showRootDirectory()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == READ_STORAGE_REQUEST_CODE &&
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
showRootDirectory()
} else {
setResult(RESULT_CANCELED)
finish()
}
}
private fun showRootDirectory() {
@Suppress("DEPRECATION")
val primaryExternal = Environment.getExternalStorageDirectory()
val storageDir = File("/storage")
// Pre-seed stack with /storage so Back from primary storage goes there (USB drives etc.)
if (storageDir.exists() && storageDir.canonicalPath != primaryExternal.canonicalPath) {
directoryStack.addLast(storageDir)
}
showDirectory(primaryExternal)
}
private fun navigateBack() {
if (directoryStack.size > 1) {
directoryStack.removeLast()
val parent = directoryStack.removeLast()
showDirectory(parent)
} else {
setResult(RESULT_CANCELED)
finish()
}
}
private fun showDirectory(dir: File) {
directoryStack.addLast(dir)
Log.v(TAG, "Showing directory: ${dir.absolutePath}")
val entries = try {
dir.listFiles()
?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
?: emptyList()
} catch (e: Exception) {
Log.e(TAG, "Failed to list directory: $e")
emptyList()
}
val names = entries.map { if (it.isDirectory) "[${it.name}]" else it.name }.toTypedArray()
val builder = AlertDialog.Builder(this)
.setTitle(dir.absolutePath)
if (entries.isEmpty()) {
builder.setMessage("No files available")
} else {
builder.setItems(names) { dialog, which ->
dialog.dismiss()
val selected = entries[which]
if (selected.isDirectory) {
showDirectory(selected)
} else {
Log.v(TAG, "Selected file: ${selected.absolutePath}")
setResult(RESULT_OK, Intent().apply {
data = Uri.fromFile(selected)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
}
}
if (directoryStack.size > 1) {
builder.setNegativeButton("↑ Back") { dialog, _ ->
dialog.dismiss()
navigateBack()
}
} else {
builder.setNegativeButton(android.R.string.cancel) { _, _ ->
setResult(RESULT_CANCELED)
finish()
}
}
builder.setOnCancelListener {
setResult(RESULT_CANCELED)
finish()
}
builder.show()
}
}

View File

@@ -2,8 +2,9 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel ${CMAKE_BINARY_DIR}/3rd/SortFilterProxyModel)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
@@ -11,24 +12,20 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CMAKE_BINARY_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad ${CMAKE_BINARY_DIR}/3rd/qtgamepad)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET ${PROJECT})
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
else()
list(APPEND LIBS GamepadLegacy)
endif()
if(TARGET ${PROJECT})
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
else()
list(APPEND LIBS GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
@@ -39,6 +36,7 @@ endif()
set(LIBS ${LIBS} qt6keychain)
include_directories(
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
)

View File

@@ -0,0 +1,21 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
include_directories(${QSIMPLECRYPTO_DIR})
set(HEADERS ${HEADERS}
${QSIMPLECRYPTO_DIR}/include/QAead.h
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
${QSIMPLECRYPTO_DIR}/include/QRsa.h
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
${QSIMPLECRYPTO_DIR}/include/QX509.h
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
)
set(SOURCES ${SOURCES}
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
)

View File

@@ -121,11 +121,6 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets

View File

@@ -133,11 +133,6 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
@@ -157,5 +152,5 @@ message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -52,16 +52,13 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
${CLIENT_ROOT_DIR}/ui/utils/pages.h
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.h
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
${CLIENT_ROOT_DIR}/core/utils/payloadSender.h
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
@@ -130,11 +127,9 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.cpp
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/utils/payloadSender.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
@@ -144,7 +139,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp

View File

@@ -63,16 +63,18 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
connData.caCert =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = m_sshSession->getTextFileFromContainer(
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
const QStringList certPaths = {
QString::fromLatin1(amnezia::protocols::openvpn::caCertPath),
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId),
QString::fromLatin1(amnezia::protocols::openvpn::taKeyPath)
};
const QList<QByteArray> certs = m_sshSession->getTextFilesFromContainer(container, credentials, certPaths, errorCode);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
connData.caCert = certs.value(0);
connData.clientCert = certs.value(1);
connData.taKey = certs.value(2);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
errorCode = ErrorCode::SshScpFailureError;

View File

@@ -165,20 +165,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
connData.clientIP = nextIp.toString();
// Get keys
connData.serverPubKey =
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
const QList<QByteArray> keys =
m_sshSession->getTextFilesFromContainer(container, credentials, {m_serverPublicKeyPath, m_serverPskKeyPath}, errorCode);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.serverPubKey = keys.value(0);
connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey = keys.value(1);
connData.pskKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
// Add client to config
QString configPart = QString("[Peer]\n"
"PublicKey = %1\n"

View File

@@ -244,7 +244,11 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
<< "appendClient=" << appendNewClient;
const QString flowValue = srv.flow;
QString flowValue = srv.flow;
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
flowValue = QStringLiteral("xtls-rprx-vision");
}
QString realityPublicKey;
QString realityShortId;
if (srv.security == QLatin1String("reality")) {
@@ -559,12 +563,9 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
if (pad.obfsMode) {
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
QJsonObject br;
const int fromV = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
int toV = pad.bytesMax.isEmpty() ? 256 : pad.bytesMax.toInt();
if (toV < fromV)
toV = fromV;
br[QStringLiteral("from")] = fromV;
br[QStringLiteral("to")] = toV;
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
: pad.bytesMax.toInt();
xo[QStringLiteral("xPaddingBytes")] = br;
}
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;

View File

@@ -78,8 +78,7 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());

View File

@@ -242,7 +242,7 @@ ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &services
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
{
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
m_appSettingsRepository->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody);
}

View File

@@ -56,38 +56,6 @@ QString getSubscriptionStatusForRenewal(const ApiConfig &apiConfig)
return QStringLiteral("active");
}
QString normalizeCaptchaSolution(const QString &captchaSolution)
{
QString normalizedSolution;
normalizedSolution.reserve(captchaSolution.size());
for (const QChar &ch : captchaSolution) {
const ushort u = ch.unicode();
if (u >= '0' && u <= '9') {
normalizedSolution += ch;
} else if (u >= 0xFF10 && u <= 0xFF19) {
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
}
}
return normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
}
bool fillCaptchaInfoFromResponse(const QByteArray &responseBody, SubscriptionController::CaptchaInfo &captchaInfo)
{
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (!jsonDoc.isObject()) {
return false;
}
const QJsonObject jsonObj = jsonDoc.object();
if (!jsonObj.contains(QStringLiteral("captcha_id")) || !jsonObj.contains(QStringLiteral("captcha_image"))) {
return false;
}
captchaInfo.captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
captchaInfo.captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
captchaInfo.hint = jsonObj.value(QStringLiteral("hint")).toString();
captchaInfo.isRequired = true;
return true;
}
}
@@ -230,6 +198,9 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
}
if (responseObj.contains(apiDefs::key::serviceInfo)) {
apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject());
}
@@ -241,7 +212,7 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase)
{
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
m_appSettingsRepository->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody);
}
@@ -266,7 +237,14 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
fillCaptchaInfoFromResponse(responseBody, captchaInfo);
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
captchaInfo.captchaId = jsonObj.value("captcha_id").toString();
captchaInfo.captchaImageBase64 = jsonObj.value("captcha_image").toString();
captchaInfo.hint = jsonObj.value("hint").toString();
captchaInfo.isRequired = true;
}
return errorCode;
}
@@ -432,8 +410,7 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
@@ -441,25 +418,8 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString serviceProtocol = apiV2->serviceProtocol();
if (!newCountryCode.isEmpty()) {
const auto availableCountries = apiV2->apiConfig.availableCountries;
for (const auto &country : availableCountries) {
const auto countryObject = country.toObject();
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != newCountryCode) {
continue;
}
const auto availableProtocols = countryObject.value(apiDefs::key::availableProtocols).toArray();
if (!availableProtocols.isEmpty() && !availableProtocols.contains(serviceProtocol)) {
serviceProtocol = availableProtocols.first().toString();
}
break;
}
}
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
@@ -481,11 +441,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfoOut) {
if (fillCaptchaInfoFromResponse(responseBody, *captchaInfoOut) && usedProtocolDataOut) {
*usedProtocolDataOut = protocolData;
}
}
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
ApiV2ServerConfig expiredApiV2 = *apiV2;
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
@@ -495,40 +450,29 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
return errorCode;
}
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
}
ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
const ProtocolData &protocolData, const QByteArray &responseBody)
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
QJsonObject serverConfigJson;
ErrorCode errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
newApiV2->apiConfig.isInAppPurchase = apiV2->apiConfig.isInAppPurchase;
newApiV2->apiConfig.subscriptionExpiredByServer = false;
newApiV2->authData = apiV2->authData;
newApiV2->crc = apiV2->crc;
if (apiV2->nameOverriddenByUser) {
newApiV2->name = apiV2->name;
newApiV2->displayName = apiV2->displayName;
@@ -540,59 +484,6 @@ ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serve
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode,
bool isConnectEvent, const ProtocolData &protocolData,
const QString &captchaId, const QString &captchaSolution,
CaptchaInfo *retryCaptchaOut)
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString serviceProtocol = apiV2->serviceProtocol();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
newCountryCode,
apiV2->serviceType(),
serviceProtocol,
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
if (isConnectEvent) {
apiPayload[apiDefs::key::isConnectEvent] = true;
}
apiPayload["captcha_id"] = captchaId;
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
if (retryCaptchaOut
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
}
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
ApiV2ServerConfig expiredApiV2 = *apiV2;
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
}
return errorCode;
}
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
}
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
@@ -757,20 +648,19 @@ ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, Q
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
{
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
return ErrorCode::NoError;
}
if (!hasInstalledContainers) {
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
return updateServiceFromGateway(serverId, "", true);
}
if (isApiKeyExpired(serverId)) {
qDebug() << "attempt to update api config by expires_at event";
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
return updateServiceFromGateway(serverId, "", true);
}
return ErrorCode::NoError;
@@ -862,36 +752,6 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
}
QString SubscriptionController::currentProtocol(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
return apiV2.has_value() ? apiV2->serviceProtocol() : QString();
}
QStringList SubscriptionController::availableProtocols(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return {};
}
const auto currentCountryCode = apiV2->apiConfig.serverCountryCode;
const auto availableCountries = apiV2->apiConfig.availableCountries;
QStringList protocols;
for (const auto &country : availableCountries) {
const auto countryObject = country.toObject();
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != currentCountryCode) {
continue;
}
for (const auto &protocol : countryObject.value(apiDefs::key::availableProtocols).toArray()) {
protocols.push_back(protocol.toString());
}
break;
}
return protocols;
}
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
int *duplicateServerIndex)
@@ -1089,8 +949,7 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
m_appSettingsRepository->isStrictKillSwitchEnabled());
auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
auto *watcher = new QFutureWatcher<QPair<ErrorCode, QByteArray>>();
QObject::connect(watcher, &QFutureWatcher<QPair<ErrorCode, QByteArray>>::finished,
@@ -1134,7 +993,17 @@ ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &use
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload["captcha_id"] = captchaId;
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
QString normalizedSolution;
normalizedSolution.reserve(captchaSolution.size());
for (const QChar &ch : captchaSolution) {
const ushort u = ch.unicode();
if (u >= '0' && u <= '9') {
normalizedSolution += ch;
} else if (u >= 0xFF10 && u <= 0xFF19) {
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
}
}
apiPayload["captcha_solution"] = normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
@@ -1142,7 +1011,16 @@ ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &use
if (retryCaptchaOut
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
const QJsonObject jsonObj = jsonDoc.object();
if (jsonObj.contains(QStringLiteral("captcha_id")) && jsonObj.contains(QStringLiteral("captcha_image"))) {
retryCaptchaOut->captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
retryCaptchaOut->captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
retryCaptchaOut->hint = jsonObj.value(QStringLiteral("hint")).toString();
retryCaptchaOut->isRequired = true;
}
}
}
return errorCode;
}

View File

@@ -66,12 +66,7 @@ public:
const QString &transactionId, bool isTestPurchase,
int *duplicateServerIndex = nullptr);
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
ErrorCode resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
const ProtocolData &protocolData, const QString &captchaId,
const QString &captchaSolution, CaptchaInfo *retryCaptchaOut = nullptr);
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
ErrorCode deactivateDevice(const QString &serverId);
@@ -83,8 +78,7 @@ public:
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
void removeApiConfig(const QString &serverId);
@@ -92,8 +86,6 @@ public:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId) const;
QString currentProtocol(const QString &serverId) const;
QStringList availableProtocols(const QString &serverId) const;
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
@@ -123,10 +115,8 @@ private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
bool isApiKeyExpired(const QString &serverId) const;
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
ErrorCode applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
const ProtocolData &protocolData, const QByteArray &responseBody);
void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
const QString &serviceProtocol, const QString &userCountryCode,
const QByteArray &apiResponseBody);

View File

@@ -6,7 +6,6 @@
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/payloadSender.h"
#include "core/utils/utilities.h"
#include "core/utils/serverConfigUtils.h"
#include "version.h"
@@ -31,6 +30,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
#ifdef Q_OS_ANDROID
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
@@ -44,99 +44,19 @@ bool ConnectionController::isConnected() const
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
{
emit connectionStateChanged(state);
}
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
{
const auto kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
if (m_vpnConnection) {
emit setConnectionStateRequested(state);
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
return ErrorCode::LegacyApiV1NotSupportedError;
case serverConfigUtils::ConfigType::Invalid:
default:
return ErrorCode::InternalError;
}
}
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
{
if (serverId.isEmpty()) {
return ErrorCode::InternalError;
}
if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning;
}
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
if (serverConfigUtils::isLegacyApiSubscription(kind)) {
return ErrorCode::LegacyApiV1NotSupportedError;
}
DockerContainer container = DockerContainer::None;
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (container == DockerContainer::None) {
if (serverConfigUtils::isApiV2Subscription(kind)) {
return ErrorCode::NoError;
}
return ErrorCode::NoInstalledContainersError;
}
if (ContainerUtils::isUnsupportedContainer(container)) {
return ErrorCode::LegacyContainerNotSupportedError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
return ErrorCode::NoError;
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning;
}
ContainerConfig containerConfigModel;
QPair<QString, QString> dns;
QString hostName;
@@ -200,6 +120,10 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
return ErrorCode::InternalError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);
@@ -216,11 +140,6 @@ ErrorCode ConnectionController::openConnection(const QString &serverId)
return errorCode;
}
const auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (apiV2.has_value() && !apiV2->sendPayload.isEmpty()) {
PayloadSender::sendAll(apiV2->sendPayload);
}
emit openConnectionRequested(serverId, container, vpnConfiguration);
return ErrorCode::NoError;
}

View File

@@ -34,8 +34,6 @@ public:
QJsonObject& vpnConfiguration,
DockerContainer& container);
ErrorCode isConnectionSupported(const QString &serverId) const;
ErrorCode openConnection(const QString &serverId);
void closeConnection();
@@ -67,6 +65,7 @@ signals:
void connectionStateChanged(Vpn::ConnectionState state);
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled);
#ifdef Q_OS_ANDROID
@@ -74,8 +73,6 @@ signals:
#endif
private:
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
VpnConnection* m_vpnConnection;

View File

@@ -22,8 +22,7 @@
#endif
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
QQmlApplicationEngine *engine, QObject *parent,
bool skipPlatformControllerInit)
QQmlApplicationEngine *engine, QObject *parent)
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
{
initRepositories();
@@ -32,10 +31,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initControllers();
initSignalHandlers();
if (!skipPlatformControllerInit) {
initAndroidController();
initAppleController();
}
initAndroidController();
initAppleController();
initLogging();
m_translator = new QTranslator(this);
@@ -194,7 +191,7 @@ void CoreController::initControllers()
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
setQmlContextProperty("LanguageUiController", m_languageUiController);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
setQmlContextProperty("SettingsController", m_settingsUiController);
m_pageController = new PageController(m_serversController, m_settingsController, this);
@@ -285,10 +282,6 @@ void CoreController::initSignalHandlers()
if (m_serversUiController->hasServersFromGatewayApi()) {
m_apiNewsUiController->fetchNews(false);
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_updateController->checkForUpdates();
#endif
}
void CoreController::updateTranslator(const QLocale &locale)

View File

@@ -90,8 +90,7 @@ class CoreController : public QObject
public:
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
QQmlApplicationEngine *engine, QObject *parent = nullptr,
bool skipPlatformControllerInit = false);
QQmlApplicationEngine *engine, QObject *parent = nullptr);
PageController* pageController() const;
void setQmlRoot();
@@ -115,7 +114,7 @@ protected:
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
LanguageModel* languageModelProtected() const { return m_languageModel; }
ConnectionUiController* connectionUiControllerProtected() const { return m_connectionUiController; }
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
ExportController* exportControllerProtected() const { return m_exportController; }

View File

@@ -1,7 +1,6 @@
#include "coreSignalHandlers.h"
#include <QTimer>
#include <QtConcurrent>
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/errorCodes.h"
@@ -34,6 +33,7 @@
#include "core/controllers/connectionController.h"
#include "ui/models/clientManagementModel.h"
#include "ui/controllers/api/apiNewsUiController.h"
#include "ui/models/api/apiCountryModel.h"
#include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h"
@@ -145,9 +145,7 @@ void CoreSignalHandlers::initExportControllerHandler()
});
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
[this](const QString &serverId, int row, DockerContainer container) {
QtConcurrent::run([this, serverId, row, container]() {
m_coreController->m_usersController->revokeClient(serverId, row, container);
});
m_coreController->m_usersController->revokeClient(serverId, row, container);
});
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
@@ -158,17 +156,15 @@ void CoreSignalHandlers::initExportControllerHandler()
void CoreSignalHandlers::initImportControllerHandler()
{
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (m_coreController->m_connectionUiController->isConnected()) {
return;
}
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
if (!m_coreController->m_connectionController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
}
});
}
@@ -180,14 +176,17 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
if (processedServerId.isEmpty()) {
return;
}
QJsonArray availableCountries;
QString serverCountryCode;
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
if (!apiV2.has_value()) {
return;
if (apiV2.has_value()) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
}
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
apiV2->apiConfig.serverCountryCode);
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
});
}
@@ -206,12 +205,12 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
}, Qt::DirectConnection);
});
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
}, Qt::DirectConnection);
});
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
&InstallController::clearCachedProfile);
@@ -238,16 +237,13 @@ void CoreSignalHandlers::initLanguageHandler()
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
});
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
});
}
void CoreSignalHandlers::initAutoConnectHandler()
{
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
}
}
@@ -288,8 +284,6 @@ void CoreSignalHandlers::initClientManagementModelUpdateHandler()
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
connect(m_coreController->m_usersController, &UsersController::revokeFinished,
m_coreController->m_exportController, &ExportController::revokeFinished);
}
void CoreSignalHandlers::initSitesModelUpdateHandler()
@@ -354,9 +348,6 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
}
void CoreSignalHandlers::initStrictKillSwitchHandler()
@@ -440,6 +431,9 @@ void CoreSignalHandlers::initNotificationHandler()
void CoreSignalHandlers::initUpdateFoundHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
&UpdateUiController::checkForUpdates);
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
const QString version = m_coreController->m_updateUiController->getVersion();
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);

View File

@@ -12,14 +12,14 @@
#include <QPromise>
#include <QUrl>
#include <openssl/rsa.h>
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amneziaApplication.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/networkUtilities.h"
#include "cryptoUtils.h"
#include "core/utils/utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/utils/ipcClient.h"
@@ -45,75 +45,15 @@ namespace
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
QStringList shuffledProxyUrls(const QStringList &proxyUrls)
{
QStringList shuffled = proxyUrls;
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(shuffled.begin(), shuffled.end(), generator);
return shuffled;
}
QString getProxyUrlsCacheKey(const QString &serviceType, const QString &userCountryCode)
{
return QStringLiteral("service_%1_country_%2").arg(serviceType, userCountryCode);
}
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
{
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedPayload);
decryptedPayload = CryptoUtils::decryptAes256Cbc(ba, decKey, iv);
if (decryptedPayload.isEmpty()) {
return false;
}
} else {
decryptedPayload = encryptedPayload;
}
return true;
}
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
{
if (cachedProxyUrlsEncrypted.isEmpty()) {
return {};
}
QByteArray cachedProxyUrlsDecrypted;
if (!decryptProxyUrlsPayload(cachedProxyUrlsEncrypted, isDevEnvironment, cachedProxyUrlsDecrypted)) {
qCritical() << "error decrypting cached proxy urls payload";
return {};
}
QJsonArray endpointsArray = QJsonDocument::fromJson(cachedProxyUrlsDecrypted).array();
QStringList endpoints;
endpoints.reserve(endpointsArray.size());
for (const QJsonValue &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
}
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
QObject *parent)
const bool isStrictKillSwitchEnabled, QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
m_appSettingsRepository(appSettingsRepository)
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
{
}
@@ -147,29 +87,40 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
}
#endif
encRequestData.key = CryptoUtils::generateRandomBytes(32);
encRequestData.iv = CryptoUtils::generateRandomBytes(32);
encRequestData.salt = CryptoUtils::generateRandomBytes(8);
QSimpleCrypto::QBlockCipher blockCipher;
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
EVP_PKEY *publicKey = CryptoUtils::loadPublicKeyFromPem(rsaKey);
if (publicKey == nullptr) {
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
QByteArray encryptedKeyPayload = CryptoUtils::rsaEncrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
EVP_PKEY *publicKey = nullptr;
try {
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
QByteArray encryptedApiPayload = CryptoUtils::encryptAes256Cbc(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv);
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
if (encryptedKeyPayload.isEmpty() || encryptedApiPayload.isEmpty()) {
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
Utils::logException();
qCritical() << "error when encrypting the request body";
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
@@ -191,11 +142,11 @@ GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(co
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
QByteArray decrypted = CryptoUtils::decryptAes256Cbc(encryptedResponseBody, key, iv);
if (!decrypted.isEmpty()) {
result.decryptedBody = decrypted;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} else {
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
@@ -314,6 +265,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
}
if (!decryptionResult.isDecryptionSuccessful) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
@@ -356,9 +308,8 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
QStringList proxyStorageUrls;
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
getProxyUrlsAsync(proxyStorageUrls, 0, proxyUrlsCacheKey, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
@@ -404,6 +355,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
@@ -419,12 +372,10 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
QStringList proxyStorageUrls;
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
if (proxyStorageUrls.empty()) {
qDebug() << "empty storage endpoint list";
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
return {};
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
@@ -439,8 +390,26 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
auto encryptedResponseBody = reply->readAll();
reply->deleteLater();
EVP_PKEY *privateKey = nullptr;
QByteArray responseBody;
if (!decryptProxyUrlsPayload(encryptedResponseBody, m_isDevEnvironment, responseBody)) {
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
continue;
}
@@ -451,8 +420,6 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encryptedResponseBody);
return endpoints;
} else {
auto replyError = reply->error();
@@ -464,7 +431,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
reply->deleteLater();
}
}
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
return {};
}
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
@@ -606,12 +573,10 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
}
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete)
std::function<void(const QStringList &)> onComplete)
{
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
onComplete(shuffledProxyUrls(readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment)));
onComplete({});
return;
}
@@ -624,17 +589,33 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
connect(reply, &QNetworkReply::finished, this,
[this, proxyStorageUrls, currentProxyStorageIndex, proxyUrlsCacheKey, onComplete, reply]() {
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray encrypted = reply->readAll();
reply->deleteLater();
QByteArray responseBody;
if (!decryptProxyUrlsPayload(encrypted, m_isDevEnvironment, responseBody)) {
try {
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encrypted);
QSimpleCrypto::QBlockCipher cipher;
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
responseBody = encrypted;
}
} catch (...) {
Utils::logException();
qCritical() << "error decrypting payload";
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
return;
}
@@ -642,9 +623,13 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
QStringList endpoints;
for (const QJsonValue &endpoint : endpointsArray)
endpoints.push_back(endpoint.toString());
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encrypted);
onComplete(shuffledProxyUrls(endpoints));
QStringList shuffled = endpoints;
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(shuffled.begin(), shuffled.end(), generator);
onComplete(shuffled);
return;
}
@@ -653,7 +638,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}

View File

@@ -16,16 +16,13 @@
#include "platforms/ios/ios_controller.h"
#endif
class SecureAppSettingsRepository;
class GatewayController : public QObject
{
Q_OBJECT
public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
QObject *parent = nullptr);
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
@@ -58,7 +55,7 @@ private:
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete);
std::function<void(const QStringList &)> onComplete);
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
@@ -68,7 +65,6 @@ private:
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
inline static QString m_proxyUrl;
};

View File

@@ -48,7 +48,6 @@ signals:
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
void updateClientsRequested(const QString &serverId, DockerContainer container);
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
void revokeFinished(ErrorCode errorCode);
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
public slots:

View File

@@ -2,7 +2,6 @@
#include "core/models/protocolConfig.h"
#include <QCoreApplication>
#include <QDebug>
#include <QEventLoop>
#include <QFutureWatcher>
@@ -21,6 +20,7 @@
#include "core/installers/sftpInstaller.h"
#include "core/installers/socks5Installer.h"
#include "core/installers/mtProxyInstaller.h"
#include "core/configurators/xrayConfigurator.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
@@ -72,16 +72,6 @@ namespace
}
return false;
}
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
{
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
if (removeDataVolume) {
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
script = SshSession::replaceVars(script, vars);
}
return script;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -100,10 +90,9 @@ InstallController::~InstallController()
}
ErrorCode InstallController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config,
bool isUpdate)
SshSession &sshSession, bool isUpdate)
{
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
SshSession sshSession;
ErrorCode e = ErrorCode::NoError;
e = isUserInSudo(credentials, sshSession);
@@ -130,10 +119,14 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
const amnezia::ScriptVars removeContainerVars =
amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
if (!isUpdate) {
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
}
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
removeContainerVars));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
@@ -158,8 +151,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return startupContainerWorker(credentials, container, config, sshSession);
}
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
{
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
@@ -168,11 +161,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
}
if (container == DockerContainer::MtProxy) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession;
SshSession sshSession(this);
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::Telemt) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession;
SshSession sshSession(this);
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
adminConfig->updateContainerConfig(container, newConfig);
@@ -188,24 +181,43 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
bool xrayServerSettingsChanged = false;
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
xrayServerSettingsChanged =
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
}
}
ErrorCode errorCode = ErrorCode::NoError;
if (reinstallRequired) {
errorCode = setupContainer(credentials, container, newConfig, true);
errorCode = setupContainer(credentials, container, newConfig, sshSession, true);
} else {
errorCode = configureContainerWorker(credentials, container, newConfig, sshSession);
if (errorCode == ErrorCode::NoError) {
errorCode = startupContainerWorker(credentials, container, newConfig, sshSession);
}
}
if (errorCode == ErrorCode::NoError
&& (container == DockerContainer::MtProxy || container == DockerContainer::Telemt)) {
const QString containerName = ContainerUtils::containerToString(container);
errorCode = sshSession.runScript(credentials, "sudo docker restart " + containerName);
const bool skipXrayInboundSync =
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
<< reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
<< static_cast<int>(errorCode);
}
}
@@ -215,9 +227,7 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
if (reinstallRequired) {
clearCachedProfile(serverId, container);
}
clearCachedProfile(serverId, container);
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
@@ -225,41 +235,6 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
return errorCode;
}
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto config = m_serversRepository->selfHostedUserConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
auto config = m_serversRepository->nativeConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
return ErrorCode::NoError;
}
default:
return ErrorCode::InternalError;
}
}
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
{
if (ContainerUtils::containerService(container) == ServiceType::Other) {
@@ -697,7 +672,13 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
if (oldXrayConfig && newXrayConfig) {
if (!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig)) {
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: oldXrayConfig->serverConfig.port;
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
? QString(protocols::xray::defaultPort)
: newXrayConfig->serverConfig.port;
if (oldPort != newPort) {
return true;
}
}
@@ -714,6 +695,18 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: oldMt->transportMode;
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: newMt->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldMt->tlsDomain != newMt->tlsDomain) {
return true;
}
}
}
@@ -728,6 +721,39 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: oldT->transportMode;
const QString newTransport = newT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: newT->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldT->tlsDomain != newT->tlsDomain) {
return true;
}
if (oldT->maskEnabled != newT->maskEnabled) {
return true;
}
if (oldT->tlsEmulation != newT->tlsEmulation) {
return true;
}
if (oldT->useMiddleProxy != newT->useMiddleProxy) {
return true;
}
if (oldT->tag != newT->tag) {
return true;
}
const QString oldUser = oldT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: oldT->userName;
const QString newUser = newT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: newT->userName;
if (oldUser != newUser) {
return true;
}
}
}
@@ -767,23 +793,9 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
QString conntrackOut;
auto cbConntrack = [&](const QString &data, libssh::Client &) {
conntrackOut += data + "\n";
return ErrorCode::NoError;
};
sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::install_conntrack),
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
cbConntrack, cbConntrack);
qDebug().noquote() << "InstallController::installDockerWorker install_conntrack:" << conntrackOut;
}
if (container == DockerContainer::Awg2) {
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
@@ -796,19 +808,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("Container runtime is not supported"))
return ErrorCode::ServerContainerRuntimeNotSupported;
QRegularExpression notFoundRegex(
R"(^.*(?:sudo:|docker:).*not found.*$)",
QRegularExpression::MultilineOption);
if (notFoundRegex.match(stdOut).hasMatch()) {
if (stdOut.contains("command not found"))
return ErrorCode::ServerDockerFailedError;
}
if (stdOut.contains("Container runtime service not running"))
return ErrorCode::ContainerRuntimeServiceNotRunning;
return error;
}
@@ -845,7 +846,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired;
@@ -927,7 +928,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
QString script = QString("sudo reboot");
@@ -955,7 +956,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
if (errorCode == ErrorCode::NoError) {
@@ -977,12 +978,13 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
const amnezia::ScriptVars removeContainerVars =
SshSession sshSession(this);
amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
ErrorCode errorCode =
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
ErrorCode errorCode = sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
@@ -1030,10 +1032,10 @@ ContainerConfig InstallController::generateConfig(DockerContainer container, int
}
ErrorCode InstallController::installContainer(const ServerCredentials &credentials, DockerContainer container, int port,
TransportProto transportProto, ContainerConfig &config)
TransportProto transportProto, ContainerConfig &config, SshSession &sshSession)
{
config = generateConfig(container, port, transportProto);
return setupContainer(credentials, container, config, false);
return setupContainer(credentials, container, config, sshSession, false);
}
@@ -1086,7 +1088,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
QMap<DockerContainer, ContainerConfig> installedContainers;
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
@@ -1129,7 +1131,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
TransportProto transportProto, bool &wasContainerInstalled)
{
SshSession sshSession;
SshSession sshSession(this);
QMap<DockerContainer, ContainerConfig> installedContainers;
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
if (errorCode) {
@@ -1139,7 +1141,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
wasContainerInstalled = false;
if (!installedContainers.contains(container)) {
ContainerConfig config;
errorCode = installContainer(credentials, container, port, transportProto, config);
errorCode = installContainer(credentials, container, port, transportProto, config, sshSession);
if (errorCode) {
return errorCode;
}
@@ -1198,7 +1200,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
QMap<DockerContainer, ContainerConfig> installedContainers;
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
@@ -1209,7 +1211,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
wasContainerInstalled = false;
if (!installedContainers.contains(container)) {
ContainerConfig config;
errorCode = installContainer(credentials, container, port, transportProto, config);
errorCode = installContainer(credentials, container, port, transportProto, config, sshSession);
if (errorCode) {
return errorCode;
}
@@ -1240,7 +1242,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
std::function<QString()> passphraseCallback)
{
SshSession sshSession;
SshSession sshSession(this);
ErrorCode errorCode = ErrorCode::NoError;
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
@@ -1460,7 +1462,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = containerAndPortMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
if (container == DockerContainer::None) {
continue;
}
@@ -1485,7 +1487,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
if (container == DockerContainer::None) {
continue;
}
@@ -1521,7 +1523,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
SshSession sshSession;
SshSession sshSession(this);
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
: QStringLiteral("sudo docker stop %1").arg(containerName);
const ErrorCode runError = sshSession.runScript(credentials, script);
@@ -1561,7 +1563,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession;
SshSession sshSession(this);
const QString script = QStringLiteral(
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
.arg(containerName);
@@ -1595,7 +1597,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
SshSession sshSession(this);
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
}
@@ -1618,7 +1620,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession;
SshSession sshSession(this);
const QString path = QStringLiteral("/data/secret");
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);

View File

@@ -33,13 +33,8 @@ public:
QObject *parent = nullptr);
~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession, bool isUpdate = false);
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
ErrorCode rebootServer(const QString &serverId);
ErrorCode removeAllContainers(const QString &serverId);
@@ -60,7 +55,7 @@ public:
ErrorCode scanServerForInstalledContainers(const QString &serverId);
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config);
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config, SshSession &sshSession);
ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto,
bool &wasContainerInstalled);

View File

@@ -698,7 +698,7 @@ ErrorCode UsersController::revokeXray(const int row,
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
error = sshSession->runScript(
credentials,
credentials,
sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString()))
);
if (error != ErrorCode::NoError) {
@@ -758,17 +758,14 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const int index
ContainerConfig containerCfg = adminConfig->containerConfig(container);
QString containerClientId = containerCfg.protocolConfig.clientId();
const bool isAdminMatch = !clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId);
if (isAdminMatch) {
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
emit adminConfigRevoked(serverId, container);
}
emit clientRevoked(index);
emit clientsUpdated(m_clientsTable);
}
emit clientsUpdated(m_clientsTable);
emit revokeFinished(errorCode);
return errorCode;
}

View File

@@ -37,7 +37,6 @@ signals:
void clientAdded(const QJsonObject &client);
void clientRenamed(int row, const QString &newName);
void clientRevoked(int row);
void revokeFinished(ErrorCode errorCode);
void adminConfigRevoked(const QString &serverId, DockerContainer container);
public slots:

View File

@@ -95,8 +95,7 @@ void UpdateController::fetchGatewayUrl()
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
7000,
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);

View File

@@ -71,62 +71,48 @@ ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const Serve
DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out)
{
out = { };
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
const QString containerName = ContainerUtils::containerToString(container);
const bool isTelemt = container == DockerContainer::Telemt;
const QString sportFilter = QString::number(listenPort);
const QString peersCmd = QStringLiteral("sudo conntrack -L -p tcp --dport ") + sportFilter
+ QStringLiteral(" 2>/dev/null | grep ESTABLISHED | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){print "
"substr($i,5); break}}'");
const QString publicFilter = QStringLiteral(" | grep -vE "
"'^(10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3["
"01])\\.|::1$|fe80:|f[cd][0-9a-f][0-9a-f]:)'");
const QString clientsCmd =
QStringLiteral("CLIENTS=$(") + peersCmd + publicFilter + QStringLiteral(" | sort -u | grep -c .); ");
const QString confFile =
isTelemt ? QStringLiteral("/data/config.toml") : QStringLiteral("/data/proxy-multi.conf");
const QString statsUrl = QString();
const QString script = QStringLiteral("CN=") + containerName + QStringLiteral("; ")
+ QStringLiteral("PORT_OK=$(sudo ss -tlnp 2>/dev/null | grep -q :") + QString::number(listenPort)
+ QStringLiteral(" && echo yes || echo no); ")
+ QStringLiteral("TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "
"https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || "
"echo no); ")
+ clientsCmd + QStringLiteral("CONF_TIME=$(sudo docker exec \"$CN\" sh -c 'stat -c \"%y\" ") + confFile
+ QStringLiteral(" 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); ")
+ QStringLiteral("echo \"PORT_OK=${PORT_OK}\"; ") + QStringLiteral("echo \"TG_OK=${TG_OK}\"; ")
+ QStringLiteral("echo \"CLIENTS=${CLIENTS:-0}\"; ") + QStringLiteral("echo \"CONF_TIME=${CONF_TIME}\"; ")
+ QStringLiteral("echo \"STATS=") + statsUrl + QStringLiteral("\";");
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
if (line.startsWith(QLatin1String("PORT_OK="))) {
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("TG_OK="))) {
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
out.clientsConnected = line.mid(8).trimmed().toInt();
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
out.lastConfigRefresh = line.mid(10).trimmed();
} else if (line.startsWith(QLatin1String("STATS="))) {
out.statsEndpoint = line.mid(6).trimmed();
}
}
return ErrorCode::NoError;
out = {};
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
const QString script =
QStringLiteral(
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
"echo \"PORT_OK=${PORT_OK}\"; "
"echo \"TG_OK=${TG_OK}\"; "
"echo \"CLIENTS=${CLIENTS:-0}\"; "
"echo \"CONF_TIME=${CONF_TIME}\"; "
"echo \"STATS=http://localhost:2398/stats\";")
.arg(containerName)
.arg(listenPort);
return ErrorCode::InternalError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
if (line.startsWith(QLatin1String("PORT_OK="))) {
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("TG_OK="))) {
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
out.clientsConnected = line.mid(8).trimmed().toInt();
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
out.lastConfigRefresh = line.mid(10).trimmed();
} else if (line.startsWith(QLatin1String("STATS="))) {
out.statsEndpoint = line.mid(6).trimmed();
}
}
return ErrorCode::NoError;
}
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,

View File

@@ -144,6 +144,10 @@ QJsonObject ApiConfig::toJson() const
obj[apiDefs::key::availableCountries] = availableCountries;
}
if (!supportedProtocols.isEmpty()) {
obj[apiDefs::key::supportedProtocols] = supportedProtocols;
}
QJsonObject serviceInfoObj = serviceInfo.toJson();
if (!serviceInfoObj.isEmpty()) {
obj[apiDefs::key::serviceInfo] = serviceInfoObj;
@@ -194,6 +198,7 @@ ApiConfig ApiConfig::fromJson(const QJsonObject& json)
config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0);
config.availableCountries = json.value(apiDefs::key::availableCountries).toArray();
config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray();
QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject();
if (!serviceInfoObj.isEmpty()) {

View File

@@ -34,6 +34,7 @@ struct ApiConfig
int maxDeviceCount;
int issuedConfigs;
QJsonArray availableCountries;
QJsonArray supportedProtocols;
struct ServiceInfo {
bool isAdVisible = false;

View File

@@ -121,11 +121,7 @@ QJsonObject ApiV2ServerConfig::toJson() const
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (!sendPayload.isEmpty()) {
obj[configKey::sendPayload] = sendPayload;
}
if (crc > 0) {
obj[configKey::crc] = crc;
}
@@ -169,7 +165,6 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.sendPayload = json.value(configKey::sendPayload).toArray();
config.crc = json.value(configKey::crc).toInt(0);

View File

@@ -1,7 +1,6 @@
#ifndef APIV2SERVERCONFIG_H
#define APIV2SERVERCONFIG_H
#include <QJsonArray>
#include <QJsonObject>
#include <QMap>
#include <QPair>
@@ -29,7 +28,6 @@ struct ApiV2ServerConfig {
DockerContainer defaultContainer;
QString dns1;
QString dns2;
QJsonArray sendPayload;
QString name;
bool nameOverriddenByUser = false;

View File

@@ -196,21 +196,11 @@ QJsonObject AwgClientConfig::toJson() const
obj[configKey::transportPacketMagicHeader] = transportPacketMagicHeader;
}
if (!specialJunk1.isEmpty()) {
obj[configKey::specialJunk1] = specialJunk1;
}
if (!specialJunk2.isEmpty()) {
obj[configKey::specialJunk2] = specialJunk2;
}
if (!specialJunk3.isEmpty()) {
obj[configKey::specialJunk3] = specialJunk3;
}
if (!specialJunk4.isEmpty()) {
obj[configKey::specialJunk4] = specialJunk4;
}
if (!specialJunk5.isEmpty()) {
obj[configKey::specialJunk5] = specialJunk5;
}
obj[configKey::specialJunk1] = specialJunk1;
obj[configKey::specialJunk2] = specialJunk2;
obj[configKey::specialJunk3] = specialJunk3;
obj[configKey::specialJunk4] = specialJunk4;
obj[configKey::specialJunk5] = specialJunk5;
if (isObfuscationEnabled) {
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;

View File

@@ -32,7 +32,7 @@ XrayXPaddingConfig XrayXPaddingConfig::fromJson(const QJsonObject &json)
c.bytesMin = json.value(configKey::xPaddingBytesMin).toString();
c.bytesMax = json.value(configKey::xPaddingBytesMax).toString();
c.obfsMode = json.value(configKey::xPaddingObfsMode).toBool(true);
c.key = json.value(configKey::xPaddingKey).toString();
c.key = json.value(configKey::xPaddingKey).toString(protocols::xray::defaultSite);
c.header = json.value(configKey::xPaddingHeader).toString();
c.placement = json.value(configKey::xPaddingPlacement).toString(protocols::xray::defaultXPaddingPlacement);
c.method = json.value(configKey::xPaddingMethod).toString(protocols::xray::defaultXPaddingMethod);
@@ -365,8 +365,6 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) const
{
return port == other.port
&& transportProto == other.transportProto
&& subnetAddress == other.subnetAddress
&& site == other.site
&& security == other.security
&& flow == other.flow
@@ -468,17 +466,6 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
}
}
}
const QJsonArray outbounds = parsed.value(protocols::xray::outbounds).toArray();
if (!outbounds.isEmpty()) {
const QJsonObject settings = outbounds[0].toObject().value(protocols::xray::settings).toObject();
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
if (!vnext.isEmpty()) {
const QJsonArray users = vnext[0].toObject().value(protocols::xray::users).toArray();
if (!users.isEmpty()) {
clientCfg.id = users[0].toObject().value(protocols::xray::id).toString();
}
}
}
c.clientConfig = clientCfg;
} else {
c.clientConfig = XrayClientConfig::fromJson(parsed);

View File

@@ -29,11 +29,6 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
return containers.value(container);
}
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;

View File

@@ -27,8 +27,6 @@ struct NativeServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;

View File

@@ -43,11 +43,6 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
return containers.value(container);
}
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
const QString &secondaryDns) const
{

View File

@@ -32,8 +32,6 @@ struct SelfHostedUserServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;

View File

@@ -39,44 +39,33 @@ QString OpenVpnProtocol::defaultConfigPath()
return p;
}
void OpenVpnProtocol::cleanupResources()
void OpenVpnProtocol::stop()
{
if (m_openVpnProcess || openVpnProcessIsRunning()) {
qDebug() << "OpenVpnProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
// TODO: need refactoring
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|| (m_connectionState == Vpn::ConnectionState::Connected)
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
QThread::msleep(10);
m_managementServer.stop();
}
m_managementServer.stop();
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
#endif
}
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|| m_connectionState == Vpn::ConnectionState::Connecting
|| m_connectionState == Vpn::ConnectionState::Connected
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
if (wasActive) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
}
cleanupResources();
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
setConnectionState(Vpn::ConnectionState::Disconnected);
}
ErrorCode OpenVpnProtocol::prepare()
@@ -179,7 +168,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
ErrorCode OpenVpnProtocol::start()
{
cleanupResources();
OpenVpnProtocol::stop();
if (!QFileInfo::exists(configPath())) {
setLastError(ErrorCode::OpenVpnConfigMissing);

View File

@@ -29,7 +29,6 @@ protected slots:
void onReadyReadDataFromManagementServer();
private:
void cleanupResources();
QString configPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();

View File

@@ -280,24 +280,6 @@ void SecureAppSettingsRepository::toggleDevGatewayEnv(bool enabled)
setValue("Conf/devGatewayEnv", enabled);
}
QByteArray SecureAppSettingsRepository::readGatewayProxyUrls(const QString &cacheKey) const
{
if (cacheKey.isEmpty()) {
return {};
}
return value(QStringLiteral("Conf/proxyUrls/") + cacheKey).toByteArray();
}
void SecureAppSettingsRepository::writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted)
{
if (cacheKey.isEmpty()) {
return;
}
setValue(QStringLiteral("Conf/proxyUrls/") + cacheKey, proxyUrlsEncrypted);
}
bool SecureAppSettingsRepository::isKillSwitchEnabled() const
{
return value("Conf/killSwitchEnabled", true).toBool();

View File

@@ -59,9 +59,7 @@ public:
void setDevGatewayEndpoint();
bool isDevGatewayEnv(bool isTestPurchase = false) const;
void toggleDevGatewayEnv(bool enabled);
QByteArray readGatewayProxyUrls(const QString &cacheKey) const;
void writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted);
bool isKillSwitchEnabled() const;
void setKillSwitchEnabled(bool enabled);
bool isStrictKillSwitchEnabled() const;

View File

@@ -208,8 +208,8 @@ QString SecureServersRepository::nextAvailableServerName() const
int i = 0;
QString candidate;
do {
++i;
candidate = tr("Server") + QLatin1Char(' ') + QString::number(i);
i++;
candidate = QStringLiteral("Server %1").arg(i);
} while (usedNames.contains(candidate));
return candidate;

View File

@@ -18,8 +18,8 @@ namespace apiDefs
constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String cliName("cli_name");
constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String availableProtocols("available_protocols");
constexpr QLatin1String installationUuid("installation_uuid");
constexpr QLatin1String uuid("installation_uuid");
constexpr QLatin1String osVersion("os_version");

View File

@@ -25,13 +25,6 @@ namespace amnezia
constexpr QLatin1String config("config");
constexpr QLatin1String configVersion("config_version");
constexpr QLatin1String sendPayload("send_payload");
constexpr QLatin1String sendPayloadEndpoint("endpoint");
constexpr QLatin1String sendPayloadTimeoutMs("timeout_ms");
constexpr QLatin1String sendPayloadProtocol("protocol");
constexpr QLatin1String sendPayloadData("payload");
constexpr QLatin1String sendPayloadExpectedResponse("expected_response");
constexpr QLatin1String containers("containers");
constexpr QLatin1String container("container");
constexpr QLatin1String defaultContainer("defaultContainer");

View File

@@ -64,7 +64,7 @@ namespace amnezia
constexpr char defaultFlow[] = "xtls-rprx-vision";
constexpr char defaultTransport[] = "raw";
constexpr char defaultFingerprint[] = "chrome";
constexpr char defaultSni[] = "www.googletagmanager.com";
constexpr char defaultSni[] = "cdn.example.com";
constexpr char defaultAlpn[] = "HTTP/2";
constexpr char defaultXhttpMode[] = "Auto";
@@ -271,7 +271,6 @@ namespace amnezia
constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual";
constexpr int maxWorkers = 32;
constexpr int botTagHexLength = 32;
}
} // namespace protocols

View File

@@ -15,8 +15,6 @@ namespace amnezia
Awg2,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,

View File

@@ -21,8 +21,6 @@ QString ContainerUtils::containerToString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
@@ -64,8 +62,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{
return { { DockerContainer::None, "Not installed" },
{ DockerContainer::OpenVpn, "OpenVPN" },
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
@@ -87,10 +83,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
return { { DockerContainer::OpenVpn,
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::Cloak,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") },
@@ -202,9 +194,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
ServiceType ContainerUtils::containerService(DockerContainer c)
{
if (isUnsupportedContainer(c)) {
return ServiceType::Vpn;
}
return ProtocolUtils::protocolService(defaultProtocol(c));
}
@@ -213,8 +202,6 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
switch (c) {
case DockerContainer::None: return Proto::Unknown;
case DockerContainer::OpenVpn: return Proto::OpenVpn;
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks: return Proto::Unknown;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
@@ -265,8 +252,6 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension allow OpenVPN for parity with iOS.
switch (c) {
case DockerContainer::OpenVpn: return true;
case DockerContainer::Cloak: return false;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
@@ -351,10 +336,6 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
bool ContainerUtils::isShareable(DockerContainer container)
{
if (isUnsupportedContainer(container)) {
return false;
}
switch (container) {
case DockerContainer::TorWebSite: return false;
case DockerContainer::Dns: return false;
@@ -371,11 +352,6 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
{
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
}
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))

View File

@@ -45,8 +45,6 @@ namespace amnezia
bool isAwgContainer(DockerContainer container);
bool isUnsupportedContainer(DockerContainer container);
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
int installPageOrder(DockerContainer container);

View File

@@ -38,8 +38,6 @@ namespace amnezia
XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217,
ServerContainerRuntimeNotSupported = 218,
ContainerRuntimeServiceNotRunning = 219,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -81,7 +79,6 @@ namespace amnezia
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905,
LegacyContainerNotSupportedError = 906,
// Android errors
AndroidError = 1000,
@@ -126,3 +123,5 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H

View File

@@ -39,8 +39,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break;
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -71,7 +69,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;

View File

@@ -1,193 +0,0 @@
#include "payloadSender.h"
#include <QDebug>
#include <QEventLoop>
#include <QHostAddress>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QTcpSocket>
#include <QTimer>
#include <QUdpSocket>
#include "core/utils/constants/configKeys.h"
#include "core/utils/networkUtilities.h"
using namespace amnezia;
namespace
{
constexpr char protoUdp[] = "udp";
constexpr char protoTcp[] = "tcp";
QByteArray randomBytes(int count)
{
if (count <= 0) {
return {};
}
QByteArray bytes(count, Qt::Uninitialized);
for (int i = 0; i < count; ++i) {
bytes[i] = static_cast<char>(QRandomGenerator::global()->bounded(256));
}
return bytes;
}
bool parseEndpoint(const QString &endpoint, QString &host, quint16 &port)
{
const int separatorIndex = endpoint.lastIndexOf(QLatin1Char(':'));
if (separatorIndex <= 0 || separatorIndex == endpoint.size() - 1) {
return false;
}
host = endpoint.left(separatorIndex);
bool ok = false;
const uint parsedPort = endpoint.mid(separatorIndex + 1).toUInt(&ok);
if (!ok || parsedPort == 0 || parsedPort > 65535) {
return false;
}
port = static_cast<quint16>(parsedPort);
return true;
}
enum class ExchangeResult { Sent, Matched, Mismatch, Timeout };
ExchangeResult waitForResponse(QAbstractSocket &socket, const QByteArray &expectedResponse, int timeoutMs)
{
if (expectedResponse.isEmpty()) {
return ExchangeResult::Sent;
}
QByteArray response;
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QIODevice::readyRead, &loop, [&]() {
response.append(socket.readAll());
if (response.size() >= expectedResponse.size()) {
loop.quit();
}
});
timer.start(timeoutMs);
loop.exec();
if (response.isEmpty()) {
return ExchangeResult::Timeout;
}
return response == expectedResponse ? ExchangeResult::Matched : ExchangeResult::Mismatch;
}
}
QByteArray PayloadSender::buildPayload(const QString &tagString)
{
QByteArray result;
if (tagString.isEmpty()) {
return result;
}
static const QRegularExpression tagRegExp(QStringLiteral("<([^>]*)>"));
QRegularExpressionMatchIterator it = tagRegExp.globalMatch(tagString);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const QStringList parts = match.captured(1).simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (parts.isEmpty()) {
continue;
}
const QString tag = parts.at(0).toLower();
if (tag == QLatin1String("b")) {
if (parts.size() < 2) {
continue;
}
QString hex = parts.at(1);
if (hex.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
hex = hex.mid(2);
}
result.append(QByteArray::fromHex(hex.toLatin1()));
} else if (tag == QLatin1String("r")) {
if (parts.size() < 2) {
continue;
}
bool ok = false;
const int count = parts.at(1).toInt(&ok);
if (ok) {
result.append(randomBytes(count));
}
} else {
qWarning() << "PayloadSender: unknown payload tag" << tag;
}
}
return result;
}
void PayloadSender::sendAll(const QJsonArray &sendPayload)
{
for (int i = 0; i < sendPayload.size(); ++i) {
const QJsonValue value = sendPayload.at(i);
if (!value.isObject()) {
continue;
}
sendEntry(value.toObject(), i);
}
}
void PayloadSender::sendEntry(const QJsonObject &entry, int index)
{
const QString endpoint = entry.value(configKey::sendPayloadEndpoint).toString();
QString host;
quint16 port = 0;
if (!parseEndpoint(endpoint, host, port)) {
qWarning() << "PayloadSender: skipping entry" << index << "- invalid endpoint" << endpoint;
return;
}
const QString protocol = entry.value(configKey::sendPayloadProtocol).toString().toLower();
const int timeoutMs = entry.value(configKey::sendPayloadTimeoutMs).toInt();
const QByteArray payload = buildPayload(entry.value(configKey::sendPayloadData).toString());
const QByteArray expectedResponse = buildPayload(entry.value(configKey::sendPayloadExpectedResponse).toString());
const QString resolvedHost = NetworkUtilities::getIPAddress(host);
const QHostAddress hostAddress(resolvedHost.isEmpty() ? host : resolvedHost);
QUdpSocket udpSocket;
QTcpSocket tcpSocket;
QAbstractSocket *socket = nullptr;
if (protocol == protoUdp) {
socket = &udpSocket;
if (udpSocket.writeDatagram(payload, hostAddress, port) < 0) {
qWarning() << "PayloadSender: entry" << index << "- udp write failed to" << endpoint << udpSocket.errorString();
return;
}
} else if (protocol == protoTcp) {
socket = &tcpSocket;
tcpSocket.connectToHost(hostAddress, port);
if (!tcpSocket.waitForConnected(timeoutMs)) {
qWarning() << "PayloadSender: entry" << index << "- tcp connect failed to" << endpoint << tcpSocket.errorString();
return;
}
if (tcpSocket.write(payload) != payload.size()) {
qWarning() << "PayloadSender: entry" << index << "- tcp write failed to" << endpoint << tcpSocket.errorString();
return;
}
tcpSocket.flush();
} else {
qWarning() << "PayloadSender: skipping entry" << index << "- unsupported protocol" << protocol;
return;
}
switch (waitForResponse(*socket, expectedResponse, timeoutMs)) {
case ExchangeResult::Sent:
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "payload sent to" << endpoint;
break;
case ExchangeResult::Matched:
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "expected response received from" << endpoint;
break;
case ExchangeResult::Mismatch:
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response mismatch from" << endpoint;
break;
case ExchangeResult::Timeout:
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response timeout from" << endpoint;
break;
}
}

View File

@@ -1,18 +0,0 @@
#ifndef PAYLOADSENDER_H
#define PAYLOADSENDER_H
#include <QByteArray>
#include <QJsonArray>
#include <QString>
class PayloadSender
{
public:
static void sendAll(const QJsonArray &sendPayload);
static QByteArray buildPayload(const QString &tagString);
private:
static void sendEntry(const QJsonObject &entry, int index);
};
#endif // PAYLOADSENDER_H

View File

@@ -50,7 +50,6 @@ QString amnezia::scriptName(SharedScriptType type)
switch (type) {
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
case SharedScriptType::install_conntrack: return QLatin1String("install_conntrack.sh");
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
@@ -367,14 +366,6 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
QStringList additionalList;
for (const QString &s : c.additionalSecrets) {
if (!s.isEmpty()) {
additionalList << s;
}
}
vars.append({ { "$TELEMT_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(',')) } });
}
return vars;

View File

@@ -21,7 +21,6 @@ enum SharedScriptType {
// General scripts
prepare_host,
install_docker,
install_conntrack,
build_container,
remove_container,
remove_all_containers,

View File

@@ -48,6 +48,9 @@ namespace libssh {
ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str());
ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity);
long connectTimeoutSec = 30;
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &connectTimeoutSec);
QFutureWatcher<int> watcher;
QFuture<int> future = QtConcurrent::run([this]() {
return ssh_connect(m_session);
@@ -61,7 +64,9 @@ namespace libssh {
int connectionResult = watcher.result();
if (connectionResult != SSH_OK) {
return fromLibsshErrorCode();
ErrorCode errorCode = fromLibsshErrorCode();
disconnectFromHost();
return errorCode;
}
std::string authUsername = credentials.userName.toStdString();
@@ -95,14 +100,20 @@ namespace libssh {
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::SshPrivateKeyFormatError;
}
disconnectFromHost();
return errorCode;
}
} else {
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str());
if (authResult != SSH_OK) {
return fromLibsshErrorCode();
ErrorCode errorCode = fromLibsshErrorCode();
disconnectFromHost();
return errorCode;
}
}
long sessionTimeoutSec = 86400;
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &sessionTimeoutSec);
}
return ErrorCode::NoError;
}

View File

@@ -59,6 +59,7 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc
qDebug() << "SshSession::Run script";
QString totalLine;
QStringList statements;
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
QString currentLine = lines.at(i);
@@ -69,24 +70,31 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc
totalLine = totalLine + "\n" + currentLine;
}
QString lineToExec;
if (currentLine.endsWith("\\")) {
continue;
} else {
lineToExec = totalLine;
totalLine.clear();
}
QString lineToExec = totalLine;
totalLine.clear();
if (lineToExec.startsWith("#")) {
continue;
}
qDebug().noquote() << lineToExec;
statements << lineToExec;
}
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
if (error != ErrorCode::NoError) {
return error;
}
if (statements.isEmpty()) {
qDebug().noquote() << "SshSession::runScript finished (no statements)\n";
return ErrorCode::NoError;
}
const QString batchedScript = statements.join("\n");
qDebug().noquote() << batchedScript;
error = m_sshClient.executeCommand(batchedScript, cbReadStdOut, cbReadStdErr);
if (error != ErrorCode::NoError) {
return error;
}
qDebug().noquote() << "SshSession::runScript finished\n";
@@ -97,30 +105,25 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
{
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
if (e)
return e;
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
const QString shell = useSh ? QStringLiteral("sh") : QStringLiteral("bash");
const QString b64 = QString::fromLatin1(script.toUtf8().toBase64());
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
runScript(credentials, replaceVars(remover, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | sudo docker exec -i $CONTAINER_NAME %2")
.arg(b64, shell);
return e;
return runScript(credentials,
replaceVars(command, amnezia::genBaseVars(credentials, container, QString(), QString())),
cbReadStdOut, cbReadStdErr);
}
ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
const QString &path, libssh::ScpOverwriteMode overwriteMode)
{
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
if (e)
return e;
if (overwriteMode != libssh::ScpOverwriteMode::ScpOverwriteExisting
&& overwriteMode != libssh::ScpOverwriteMode::ScpAppendToExisting) {
return ErrorCode::NotImplementedError;
}
QString stdOut;
auto cbReadStd = [&](const QString &data, libssh::Client &) {
@@ -128,45 +131,26 @@ ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const
return ErrorCode::NoError;
};
// mkdir
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
auto baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
e = runScript(credentials, replaceVars(mkdir, amnezia::genBaseVars(credentials, container, QString(), QString())));
const QString b64 = QString::fromLatin1(file.toUtf8().toBase64());
const QString dir = QFileInfo(path).path();
const QString redirect = (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting)
? QStringLiteral(">>")
: QStringLiteral(">");
const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | "
"sudo docker exec -i $CONTAINER_NAME sh -c 'mkdir -p \"%2\" && cat %3 \"%4\"'")
.arg(b64, dir, redirect, path);
ErrorCode e = runScript(credentials, replaceVars(command, baseVars), cbReadStd, cbReadStd);
if (e)
return e;
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
amnezia::genBaseVars(credentials, container, QString(), QString())),
cbReadStd, cbReadStd);
if (e)
return e;
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
amnezia::genBaseVars(credentials, container, QString(), QString())),
cbReadStd, cbReadStd);
if (e)
return e;
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
amnezia::genBaseVars(credentials, container, QString(), QString())),
cbReadStd, cbReadStd);
if (e)
return e;
} else
return ErrorCode::NotImplementedError;
if (stdOut.contains("Error") && stdOut.contains("No such container")) {
return ErrorCode::ServerContainerMissingError;
}
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), amnezia::genBaseVars(credentials, container, QString(), QString())));
return e;
}
@@ -188,6 +172,38 @@ QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const
return QByteArray::fromHex(stdOut.toUtf8());
}
QList<QByteArray> SshSession::getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QStringList &paths, ErrorCode &errorCode)
{
errorCode = ErrorCode::NoError;
QList<QByteArray> result;
if (paths.isEmpty()) {
return result;
}
const QString sep = QStringLiteral("ZZAMNSEPZZ");
QString inner;
for (const QString &path : paths) {
inner += QStringLiteral("xxd -p '%1'; echo '%2'; ").arg(path, sep);
}
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"%2\"")
.arg(ContainerUtils::containerToString(container), inner);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
errorCode = runScript(credentials, script, cbReadStdOut);
const QStringList parts = stdOut.split(sep);
for (int i = 0; i < paths.size(); ++i) {
const QString hex = (i < parts.size()) ? parts.at(i) : QString();
result.append(QByteArray::fromHex(hex.toUtf8()));
}
return result;
}
ErrorCode SshSession::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::ScpOverwriteMode overwriteMode)
{

View File

@@ -28,6 +28,8 @@ public:
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
ErrorCode &errorCode);
QList<QByteArray> getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QStringList &paths, ErrorCode &errorCode);
static QString replaceVars(const QString &script, const Vars &vars);

View File

@@ -390,55 +390,55 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
if (const auto jc = obj.value("Jc"); !jc.isUndefined()) {
config.m_junkPacketCount = jc.toString();
if (!obj.value("Jc").isNull()) {
config.m_junkPacketCount = obj.value("Jc").toString();
}
if (const auto jmin = obj.value("Jmin"); !jmin.isUndefined()) {
config.m_junkPacketMinSize = jmin.toString();
if (!obj.value("Jmin").isNull()) {
config.m_junkPacketMinSize = obj.value("Jmin").toString();
}
if (const auto jmax = obj.value("Jmax"); !jmax.isUndefined()) {
config.m_junkPacketMaxSize = jmax.toString();
if (!obj.value("Jmax").isNull()) {
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
}
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
config.m_initPacketJunkSize = s1.toString();
if (!obj.value("S1").isNull()) {
config.m_initPacketJunkSize = obj.value("S1").toString();
}
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
config.m_responsePacketJunkSize = s2.toString();
if (!obj.value("S2").isNull()) {
config.m_responsePacketJunkSize = obj.value("S2").toString();
}
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
config.m_cookieReplyPacketJunkSize = s3.toString();
if (!obj.value("S3").isNull()) {
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
}
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
config.m_transportPacketJunkSize = s4.toString();
if (!obj.value("S4").isNull()) {
config.m_transportPacketJunkSize = obj.value("S4").toString();
}
if (const auto h1 = obj.value("H1"); !h1.isUndefined()) {
config.m_initPacketMagicHeader = h1.toString();
if (!obj.value("H1").isNull()) {
config.m_initPacketMagicHeader = obj.value("H1").toString();
}
if (const auto h2 = obj.value("H2"); !h2.isUndefined()) {
config.m_responsePacketMagicHeader = h2.toString();
if (!obj.value("H2").isNull()) {
config.m_responsePacketMagicHeader = obj.value("H2").toString();
}
if (const auto h3 = obj.value("H3"); !h3.isUndefined()) {
config.m_underloadPacketMagicHeader = h3.toString();
if (!obj.value("H3").isNull()) {
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
}
if (const auto h4 = obj.value("H4"); !h4.isUndefined()) {
config.m_transportPacketMagicHeader = h4.toString();
if (!obj.value("H4").isNull()) {
config.m_transportPacketMagicHeader = obj.value("H4").toString();
}
if (const auto i1 = obj.value("I1"); !i1.isUndefined()) {
config.m_specialJunk["I1"] = i1.toString();
if (!obj.value("I1").isNull()) {
config.m_specialJunk["I1"] = obj.value("I1").toString();
}
if (const auto i2 = obj.value("I2"); !i2.isUndefined()) {
config.m_specialJunk["I2"] = i2.toString();
if (!obj.value("I2").isNull()) {
config.m_specialJunk["I2"] = obj.value("I2").toString();
}
if (const auto i3 = obj.value("I3"); !i3.isUndefined()) {
config.m_specialJunk["I3"] = i3.toString();
if (!obj.value("I3").isNull()) {
config.m_specialJunk["I3"] = obj.value("I3").toString();
}
if (const auto i4 = obj.value("I4"); !i4.isUndefined()) {
config.m_specialJunk["I4"] = i4.toString();
if (!obj.value("I4").isNull()) {
config.m_specialJunk["I4"] = obj.value("I4").toString();
}
if (const auto i5 = obj.value("I5"); !i5.isUndefined()) {
config.m_specialJunk["I5"] = i5.toString();
if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString();
}
return true;

View File

@@ -1,6 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 11L21 7L17 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 7H9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 21L3 17L7 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 17H3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 525 B

View File

@@ -10,7 +10,6 @@
<file>controls/app.svg</file>
<file>controls/archive-restore.svg</file>
<file>controls/arrow-left.svg</file>
<file>controls/arrow-left-right.svg</file>
<file>controls/arrow-right.svg</file>
<file>controls/bug.svg</file>
<file>controls/check.svg</file>

View File

@@ -82,32 +82,10 @@
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<dict/>
<key>CFBundleIcons~ipad</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>

View File

@@ -44,20 +44,8 @@
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>

View File

@@ -25,8 +25,6 @@ set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
)
if(DEPLOY)
@@ -120,20 +118,10 @@ target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(openvpnadapter REQUIRED)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::openvpnadapter)
find_package(awg-apple REQUIRED)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::awg-apple)
find_package(hev-socks5-tunnel REQUIRED)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE heiher::hev-socks5-tunnel)

View File

@@ -62,29 +62,12 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr) {
if (config.m_vpnDisabledApps.length() > 0) {
logger.error() << "Split tunnel manager is not initialized";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
}
if (m_splitTunnelManager == nullptr)
return;
}
if (config.m_vpnDisabledApps.length() > 0) {
if (!m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex)) {
logger.error() << "Failed to start split tunnel";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return;
}
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
logger.error() << "Failed to apply split tunnel app exclusions";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
return;
}
if (!m_splitTunnelManager->isRunning()) {
logger.error() << "Split tunnel did not reach running state";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
}
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager->stop();
}
@@ -96,9 +79,7 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
// The Client has sent us a list of disabled apps, but we failed
// to init the the split tunnel driver.
// So let the client know this was not possible
logger.error() << "Split tunnel manager is not initialized";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
return false;
}
return true;
}
@@ -109,20 +90,14 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
}
if (config.m_vpnDisabledApps.length() > 0) {
if (!m_splitTunnelManager->start(m_inetAdapterIndex)) {
logger.error() << "Split tunnel start failed";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return false;
};
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
logger.error() << "Split tunnel app exclusion failed";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
return false;
};
// Now the driver should be running (State == 4)
if (!m_splitTunnelManager->isRunning()) {
logger.error() << "Split tunnel did not reach running state";
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
return false;
}
return true;
}

View File

@@ -36,8 +36,6 @@
// ID for the Firewall Sublayer
DEFINE_GUID(ST_FW_WINFW_BASELINE_SUBLAYER_KEY, 0xc78056ff, 0x2bc1, 0x4211, 0xaa,
0xdd, 0x7f, 0x35, 0x8d, 0xef, 0x20, 0x2d);
DEFINE_GUID(ST_FW_WINFW_DNS_SUBLAYER_KEY, 0x60090787, 0xcca1, 0x4937, 0xaa,
0xce, 0x51, 0x25, 0x6e, 0xf4, 0x81, 0xf3);
// ID for the Mullvad Split-Tunnel Sublayer Provider
DEFINE_GUID(ST_FW_PROVIDER_KEY, 0xe2c114ee, 0xf32a, 0x4264, 0xa6, 0xcb, 0x3f,
0xa7, 0x99, 0x63, 0x56, 0xd9);
@@ -51,53 +49,6 @@ constexpr uint8_t LOW_WEIGHT = 0;
constexpr uint8_t MED_WEIGHT = 7;
constexpr uint8_t HIGH_WEIGHT = 13;
constexpr uint8_t MAX_WEIGHT = 15;
bool ensureSublayer(HANDLE wfp, const GUID& key, const wchar_t* name,
const wchar_t* description) {
FWPM_SUBLAYER0* maybeLayer = nullptr;
DWORD result = FwpmSubLayerGetByKey0(wfp, &key, &maybeLayer);
if (result == ERROR_SUCCESS) {
logger.debug() << "The Sublayer Already Exists:"
<< QString::fromWCharArray(name);
FwpmFreeMemory0(reinterpret_cast<void**>(&maybeLayer));
return true;
}
if (result != FWP_E_SUBLAYER_NOT_FOUND) {
logger.error() << "FwpmSubLayerGetByKey0 failed. Return value:.\n"
<< result;
return false;
}
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
FWPM_SUBLAYER0 subLayer;
memset(&subLayer, 0, sizeof(subLayer));
subLayer.subLayerKey = key;
subLayer.displayData.name = const_cast<PWSTR>(name);
subLayer.displayData.description = const_cast<PWSTR>(description);
subLayer.weight = 0xFFFF;
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(wfp);
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
return false;
}
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
logger.debug() << "Initialised Sublayer:" << QString::fromWCharArray(name);
return true;
}
} // namespace
WindowsFirewall* WindowsFirewall::create(QObject* parent) {
@@ -165,12 +116,47 @@ bool WindowsFirewall::initSublayer() {
}
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
return ensureSublayer(wfp, ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
L"Amnezia-SplitTunnel-Sublayer",
L"Filters that enforce a good baseline") &&
ensureSublayer(wfp, ST_FW_WINFW_DNS_SUBLAYER_KEY,
L"Amnezia-SplitTunnel-DNS-Sublayer",
L"DNS filters for split tunneling");
// Check if the Layer Already Exists
FWPM_SUBLAYER0* maybeLayer;
result = FwpmSubLayerGetByKey0(wfp, &ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
&maybeLayer);
if (result == ERROR_SUCCESS) {
logger.debug() << "The Sublayer Already Exists!";
FwpmFreeMemory0((void**)&maybeLayer);
return true;
}
// Step 1: Start Transaction
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
// Step 3: Add Sublayer
FWPM_SUBLAYER0 subLayer;
memset(&subLayer, 0, sizeof(subLayer));
subLayer.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
subLayer.displayData.name = (PWSTR)L"Amnezia-SplitTunnel-Sublayer";
subLayer.displayData.description =
(PWSTR)L"Filters that enforce a good baseline";
subLayer.weight = 0xFFFF;
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
return false;
}
// Step 4: Commit!
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
logger.debug() << "Initialised Sublayer";
return true;
}
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {

View File

@@ -24,7 +24,6 @@
#include <QFileInfo>
#include <QNetworkInterface>
#include <QScopeGuard>
#include <QUrl>
#pragma region
@@ -149,18 +148,6 @@ ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) {
return pi;
}
QString normalizeExecutablePath(const QString& path) {
QString normalized = path.trimmed();
if (normalized.startsWith("file:", Qt::CaseInsensitive)) {
const QString localPath = QUrl(normalized).toLocalFile();
if (!localPath.isEmpty()) {
normalized = localPath;
}
}
normalized.replace('/', '\\');
return normalized;
}
} // namespace
std::unique_ptr<WindowsSplitTunnel> WindowsSplitTunnel::create(
@@ -300,10 +287,6 @@ bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
auto config = generateAppConfiguration(appPaths);
if (config.empty()) {
logger.error() << "No valid split-tunnel application rules generated";
return false;
}
DWORD bytesReturned;
auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0],
@@ -331,7 +314,7 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr,
0, &bytesReturned, nullptr);
if (!ok) {
logger.error() << "Driver init failed. Error:" << GetLastError();
logger.error() << "Driver init failed";
return false;
}
}
@@ -340,16 +323,11 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
if (getState() == STATE_INITIALIZED) {
logger.debug() << "State is Init, requires process config";
auto config = generateProcessBlob();
if (config.empty()) {
logger.error() << "Process configuration blob is empty";
return false;
}
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_PROCESSES, &config[0],
(DWORD)config.size(), nullptr, 0, &bytesReturned,
nullptr);
if (!ok) {
logger.error() << "Failed to set Process Config. Error:"
<< GetLastError();
logger.error() << "Failed to set Process Config";
return false;
}
logger.debug() << "Set Process Config ok || new State:" << stateString();
@@ -362,16 +340,11 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
logger.debug() << "Driver is ready || new State:" << stateString();
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
if (config.empty()) {
logger.error() << "Network configuration blob is empty. Internet adapter:"
<< inetAdapterIndex << "VPN adapter:" << vpnAdapterIndex;
return false;
}
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
(DWORD)config.size(), nullptr, 0, &bytesReturned,
nullptr);
if (!ok) {
logger.error() << "Failed to set Network Config. Error:" << GetLastError();
logger.error() << "Failed to set Network Config";
return false;
}
logger.debug() << "New Network Config Applied || new State:" << stateString();
@@ -431,22 +404,13 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
size_t cummulated_string_size = 0;
QStringList dosPaths;
for (auto const& path : appPaths) {
const QString normalizedPath = normalizeExecutablePath(path);
auto dosPath = convertPath(normalizedPath);
if (dosPath.isEmpty()) {
logger.error() << "Rejecting split-tunnel app path with empty device "
"conversion:"
<< normalizedPath;
continue;
}
auto dosPath = convertPath(path);
dosPaths.append(dosPath);
cummulated_string_size += dosPath.toStdWString().size() * sizeof(wchar_t);
}
if (dosPaths.isEmpty()) {
return {};
logger.debug() << dosPath;
}
size_t bufferSize = sizeof(CONFIGURATION_HEADER) +
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size()) +
(sizeof(CONFIGURATION_ENTRY) * appPaths.size()) +
cummulated_string_size;
std::vector<uint8_t> outBuffer(bufferSize);
@@ -454,7 +418,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
auto entry = (CONFIGURATION_ENTRY*)(header + 1);
auto stringDest = &outBuffer[0] + sizeof(CONFIGURATION_HEADER) +
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size());
(sizeof(CONFIGURATION_ENTRY) * appPaths.size());
SIZE_T stringOffset = 0;
@@ -473,7 +437,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
stringOffset += stringLength;
}
header->NumEntries = dosPaths.length();
header->NumEntries = appPaths.length();
header->TotalLength = bufferSize;
return outBuffer;
@@ -485,7 +449,9 @@ std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
if (vpnAdapterIndex == 0) {
auto ifaces = QNetworkInterface::allInterfaces();
if (vpnAdapterIndex == 0) {
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
}
// Always the VPN
@@ -554,7 +520,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
auto process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
currentProcess.th32ProcessID);
if (process_handle == nullptr) {
if (process_handle == INVALID_HANDLE_VALUE) {
continue;
}
ProcessInfo info = getProcessInfo(process_handle, currentProcess);
@@ -674,48 +640,23 @@ bool WindowsSplitTunnel::isInstalled() {
}
QString WindowsSplitTunnel::convertPath(const QString& path) {
const QString normalizedPath = normalizeExecutablePath(path);
if (normalizedPath.isEmpty()) {
logger.error() << "Empty executable path for DOS device conversion";
return "";
}
auto parts = normalizedPath.split("\\", Qt::SkipEmptyParts);
if (parts.isEmpty()) {
logger.error() << "Invalid executable path for DOS device conversion:"
<< normalizedPath;
return "";
}
auto parts = path.split("/");
QString driveLetter = parts.takeFirst();
if (!driveLetter.contains(":") || parts.size() == 0) {
// device should contain : for e.g C:
logger.error() << "Invalid executable path for DOS device conversion:"
<< normalizedPath;
return "";
}
QByteArray buffer(2048 * sizeof(wchar_t), 0);
DWORD ok = 0;
DWORD err = ERROR_SUCCESS;
for (int attempt = 0; attempt < 4; ++attempt) {
ok = QueryDosDeviceW(reinterpret_cast<LPCWSTR>(driveLetter.utf16()),
reinterpret_cast<LPWSTR>(buffer.data()),
buffer.size() / sizeof(wchar_t));
if (ok != 0) {
break;
}
err = GetLastError();
if (err != ERROR_INSUFFICIENT_BUFFER) {
WindowsUtils::windowsLog("Err fetching dos path");
logger.error() << "QueryDosDeviceW failed for" << driveLetter
<< "error:" << err;
return "";
}
QByteArray buffer(2048, 0xFFu);
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
(wchar_t*)buffer.data(), buffer.size() / 2);
if (ok == ERROR_INSUFFICIENT_BUFFER) {
buffer.resize(buffer.size() * 2);
buffer.fill(0);
ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(),
buffer.size() / 2);
}
if (ok == 0) {
WindowsUtils::windowsLog("Err fetching dos path");
logger.error() << "QueryDosDeviceW failed after buffer growth for"
<< driveLetter << "error:" << err;
return "";
}
QString deviceName;

View File

@@ -1,7 +1,8 @@
#include "secureQSettings.h"
#include "../client/3rd/QSimpleCrypto/src/include/QAead.h"
#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h"
#include "core/utils/utilities.h"
#include "cryptoUtils.h"
#include <QDataStream>
#include <QDebug>
#include <QEventLoop>
@@ -179,8 +180,11 @@ void SecureQSettings::clearSettings()
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QByteArray result = CryptoUtils::encryptAes256Cbc(value, getEncKey(), getEncIv());
if (result.isEmpty() && !value.isEmpty()) {
QSimpleCrypto::QBlockCipher cipher;
QByteArray result;
try {
result = cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when encrypting the settings value";
}
return result;
@@ -188,8 +192,11 @@ QByteArray SecureQSettings::encryptText(const QByteArray &value) const
QByteArray SecureQSettings::decryptText(const QByteArray &ba) const
{
QByteArray result = CryptoUtils::decryptAes256Cbc(ba, getEncKey(), getEncIv());
if (result.isEmpty() && !ba.isEmpty()) {
QSimpleCrypto::QBlockCipher cipher;
QByteArray result;
try {
result = cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when decrypting the settings value";
}
return result;
@@ -218,7 +225,8 @@ QByteArray SecureQSettings::getEncKey() const
if (m_key.isEmpty()) {
// Create new key
QByteArray key = CryptoUtils::generateRandomBytes(32);
QSimpleCrypto::QBlockCipher cipher;
QByteArray key = cipher.generatePrivateSalt(32);
if (key.isEmpty()) {
qCritical() << "SecureQSettings::getEncKey Unable to generate new enc key";
}
@@ -246,7 +254,8 @@ QByteArray SecureQSettings::getEncIv() const
if (m_iv.isEmpty()) {
// Create new IV
QByteArray iv = CryptoUtils::generateRandomBytes(32);
QSimpleCrypto::QBlockCipher cipher;
QByteArray iv = cipher.generatePrivateSalt(32);
if (iv.isEmpty()) {
qCritical() << "SecureQSettings::getEncIv Unable to generate new enc IV";
}

View File

@@ -1,8 +1,7 @@
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
fi;\
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi

View File

@@ -1,8 +1,8 @@
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\

View File

@@ -1,10 +0,0 @@
if command -v conntrack > /dev/null 2>&1; then echo "conntrack already installed"; exit 0; fi;\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; conntrack_pkg="conntrack"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; conntrack_pkg="conntrack-tools"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; conntrack_pkg="conntrack-tools"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; conntrack_pkg="conntrack-tools"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; conntrack_pkg="conntrack-tools"; dist="archlinux";\
else echo "Packet manager not found"; exit 0; fi;\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
sudo $pm $check_pkgs; sudo $pm $silent_inst $conntrack_pkg;\
command -v conntrack > /dev/null 2>&1 && echo "conntrack installed" || echo "conntrack install failed"

View File

@@ -1,34 +1,25 @@
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs;\
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo -n $pm $silent_inst $docker_pkg;\
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
else \
echo "Container runtime is not supported";\
exit 1;\
fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
fi;\
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
fi;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo -n systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\
fi;\
sudo -n docker --version || docker --version;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version;\
uname -sr

View File

@@ -5,7 +5,8 @@ dev tun
ca /opt/amnezia/openvpn/ca.crt
cert /opt/amnezia/openvpn/AmneziaReq.crt
key /opt/amnezia/openvpn/AmneziaReq.key
dh /opt/amnezia/openvpn/dh.pem
dh none
ecdh-curve secp384r1
server $OPENVPN_SUBNET_IP $OPENVPN_SUBNET_MASK
ifconfig-pool-persist ipp.txt
duplicate-cn

View File

@@ -7,6 +7,8 @@ sudo docker run -d \
-p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO \
--name $CONTAINER_NAME $CONTAINER_NAME
amn_i=0; while [ "$(sudo docker inspect -f '{{.State.Running}}' $CONTAINER_NAME 2>/dev/null)" != "true" ] && [ $amn_i -lt 30 ]; do sleep 0.5; amn_i=$((amn_i+1)); done
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
# Create tun device if not exist
@@ -18,8 +20,7 @@ sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS ne
# OpenVPN config
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
cd /opt/amnezia/openvpn && easyrsa init-pki; \
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\

View File

@@ -1,6 +1,6 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia

View File

@@ -1,3 +1,4 @@
sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME;
sudo docker rmi $CONTAINER_NAME;\
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true

View File

@@ -18,7 +18,6 @@
<file>dns/Dockerfile</file>
<file>dns/run_container.sh</file>
<file>install_docker.sh</file>
<file>install_conntrack.sh</file>
<file>ipsec/configure_container.sh</file>
<file>ipsec/Dockerfile</file>
<file>ipsec/mobileconfig.plist</file>

View File

@@ -61,12 +61,6 @@ rm -f /data/config.toml
echo ""
echo "[access.users]"
echo "$TELEMT_USER_NAME = \"$SECRET\""
i=1
for EXTRA in $(echo "$TELEMT_ADDITIONAL_SECRETS" | tr ',' ' '); do
echo "$EXTRA" | grep -qE '^[0-9a-fA-F]{32}$' || continue
echo "extra_$i = \"$EXTRA\""
i=$((i + 1))
done
} > /data/config.toml
echo "$SECRET" > /data/secret

View File

@@ -303,8 +303,6 @@ bool SubscriptionUiController::importFreeFromGateway()
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
return true;
} else if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
m_captchaState = CaptchaState{};
m_captchaState.flow = CaptchaFlow::Import;
m_captchaState.userCountryCode = userCountryCode;
m_captchaState.serviceType = serviceType;
m_captchaState.serviceProtocol = serviceProtocol;
@@ -329,11 +327,6 @@ void SubscriptionUiController::onCaptchaSolved(const QString &captchaId, const Q
return;
}
if (m_captchaState.flow == CaptchaFlow::Update) {
resolveUpdateCaptcha(captchaId, solution);
return;
}
SubscriptionController::ProtocolData protocolData;
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
@@ -375,33 +368,6 @@ void SubscriptionUiController::onRefreshCaptchaRequested()
return;
}
if (m_captchaState.flow == CaptchaFlow::Update) {
SubscriptionController::CaptchaInfo captchaInfo;
SubscriptionController::ProtocolData usedProtocolData;
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(
m_captchaState.serverId,
m_captchaState.newCountryCode,
m_captchaState.isConnectEvent,
&captchaInfo,
&usedProtocolData);
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
m_captchaState.updateProtocolData = usedProtocolData;
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
} else if (errorCode == ErrorCode::NoError) {
emitCaptchaUpdateSuccess();
} else {
m_captchaState.isPending = false;
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
emit subscriptionExpiredOnServer();
} else {
emit errorOccurred(errorCode);
}
}
return;
}
SubscriptionController::ProtocolData protocolData;
protocolData.certPrivKey = m_captchaState.openvpnPrivKey;
protocolData.wireGuardClientPrivKey = m_captchaState.wireguardClientPrivKey;
@@ -426,39 +392,6 @@ void SubscriptionUiController::onRefreshCaptchaRequested()
}
}
void SubscriptionUiController::resolveUpdateCaptcha(const QString &captchaId, const QString &solution)
{
SubscriptionController::CaptchaInfo retryCaptcha;
ErrorCode errorCode = m_subscriptionController->resolveUpdateServiceCaptcha(
m_captchaState.serverId,
m_captchaState.newCountryCode,
m_captchaState.isConnectEvent,
m_captchaState.updateProtocolData,
captchaId,
solution,
&retryCaptcha);
if (errorCode == ErrorCode::NoError) {
emitCaptchaUpdateSuccess();
return;
}
if ((errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|| errorCode == ErrorCode::ApiCaptchaRequiredError)
&& retryCaptcha.isRequired) {
emit captchaRequired(retryCaptcha.captchaId, retryCaptcha.captchaImageBase64,
retryCaptcha.hint.isEmpty() ? tr("Enter the digits from the image to continue") : retryCaptcha.hint);
return;
}
m_captchaState.isPending = false;
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
emit subscriptionExpiredOnServer();
} else {
emit errorOccurred(errorCode);
}
}
bool SubscriptionUiController::importTrialFromGateway(const QString &email)
{
emit trialEmailError(QString());
@@ -490,29 +423,20 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|| oldApiV2->apiConfig.isSubscriptionExpired();
}
SubscriptionController::CaptchaInfo captchaInfo;
SubscriptionController::ProtocolData usedProtocolData;
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent,
&captchaInfo, &usedProtocolData);
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent);
if (errorCode == ErrorCode::NoError) {
emitUpdateSuccess(wasSubscriptionExpired, reloadServiceConfig, newCountryName);
if (wasSubscriptionExpired) {
emit subscriptionRefreshNeeded();
}
if (reloadServiceConfig) {
emit reloadServerFromApiFinished(tr("API config reloaded"));
} else if (newCountryName.isEmpty()) {
emit updateServerFromApiFinished();
} else {
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
}
return true;
} else if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
m_captchaState = CaptchaState{};
m_captchaState.flow = CaptchaFlow::Update;
m_captchaState.serverId = serverId;
m_captchaState.newCountryCode = newCountryCode;
m_captchaState.newCountryName = newCountryName;
m_captchaState.isConnectEvent = isConnectEvent;
m_captchaState.reloadServiceConfig = reloadServiceConfig;
m_captchaState.wasSubscriptionExpired = wasSubscriptionExpired;
m_captchaState.updateProtocolData = usedProtocolData;
m_captchaState.isPending = true;
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
return false;
} else {
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
emit subscriptionExpiredOnServer();
@@ -523,37 +447,6 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
}
}
void SubscriptionUiController::emitUpdateSuccess(bool wasSubscriptionExpired, bool reloadServiceConfig, const QString &newCountryName)
{
if (wasSubscriptionExpired) {
emit subscriptionRefreshNeeded();
}
if (reloadServiceConfig) {
emit reloadServerFromApiFinished(tr("API config reloaded"));
} else if (newCountryName.isEmpty()) {
emit updateServerFromApiFinished();
} else {
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
}
}
void SubscriptionUiController::emitCaptchaUpdateSuccess()
{
const bool fromValidateConfig = m_captchaState.fromValidateConfig;
const bool wasSubscriptionExpired = m_captchaState.wasSubscriptionExpired;
const bool reloadServiceConfig = m_captchaState.reloadServiceConfig;
const QString newCountryName = m_captchaState.newCountryName;
m_captchaState.isPending = false;
emit captchaFlowDismissRequested();
if (fromValidateConfig) {
emit configValidated(true);
return;
}
emitUpdateSuccess(wasSubscriptionExpired, reloadServiceConfig, newCountryName);
}
bool SubscriptionUiController::deactivateDevice(const QString &serverId)
{
@@ -582,32 +475,15 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
void SubscriptionUiController::validateConfig()
{
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
emit unsupportedConnectDrawerRequested();
emit configValidated(false);
return;
}
bool hasInstalledContainers = m_serversController->hasInstalledContainers(serverId);
SubscriptionController::CaptchaInfo captchaInfo;
SubscriptionController::ProtocolData usedProtocolData;
ErrorCode errorCode = m_subscriptionController->validateAndUpdateConfig(serverId, hasInstalledContainers,
&captchaInfo, &usedProtocolData);
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfo.isRequired) {
m_captchaState = CaptchaState{};
m_captchaState.flow = CaptchaFlow::Update;
m_captchaState.fromValidateConfig = true;
m_captchaState.serverId = serverId;
m_captchaState.isConnectEvent = true;
m_captchaState.updateProtocolData = usedProtocolData;
m_captchaState.isPending = true;
emit captchaRequired(captchaInfo.captchaId, captchaInfo.captchaImageBase64,
captchaInfo.hint.isEmpty() ? tr("Enter the digits from the image to continue") : captchaInfo.hint);
emit configValidated(false);
return;
}
ErrorCode errorCode = m_subscriptionController->validateAndUpdateConfig(serverId, hasInstalledContainers);
if (errorCode != ErrorCode::NoError) {
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
@@ -633,18 +509,6 @@ bool SubscriptionUiController::isVlessProtocol(const QString &serverId)
}
QString SubscriptionUiController::currentProtocol(const QString &serverId)
{
return m_subscriptionController->currentProtocol(serverId);
}
QStringList SubscriptionUiController::availableProtocols(const QString &serverId)
{
return m_subscriptionController->availableProtocols(serverId);
}
void SubscriptionUiController::removeApiConfig(const QString &serverId)
{
m_subscriptionController->removeApiConfig(serverId);

View File

@@ -57,8 +57,6 @@ public slots:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId);
QString currentProtocol(const QString &serverId);
QStringList availableProtocols(const QString &serverId);
bool isCaptchaAwaitingUser() const;
void onCaptchaSolved(const QString &captchaId, const QString &solution);
@@ -97,15 +95,7 @@ signals:
void unsupportedConnectDrawerRequested();
private:
enum class CaptchaFlow {
Import,
Update
};
struct CaptchaState {
CaptchaFlow flow = CaptchaFlow::Import;
// Import flow
QString userCountryCode;
QString serviceType;
QString serviceProtocol;
@@ -113,25 +103,10 @@ private:
QString wireguardClientPrivKey;
QString wireguardClientPubKey;
QString xrayUuid;
// Update flow
QString serverId;
QString newCountryCode;
QString newCountryName;
bool isConnectEvent = false;
bool reloadServiceConfig = false;
bool wasSubscriptionExpired = false;
bool fromValidateConfig = false;
SubscriptionController::ProtocolData updateProtocolData;
bool isPending = false;
} m_captchaState;
private:
void emitUpdateSuccess(bool wasSubscriptionExpired, bool reloadServiceConfig, const QString &newCountryName);
void emitCaptchaUpdateSuccess();
void resolveUpdateCaptcha(const QString &captchaId, const QString &solution);
QList<QString> getQrCodes();
int getQrCodesCount();
QString getVpnKey();

View File

@@ -8,8 +8,6 @@
#include "amneziaApplication.h"
#include "core/controllers/serversController.h"
#include "core/models/containerConfig.h"
#include "core/utils/containerEnum.h"
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
ServersController* serversController,
@@ -29,14 +27,13 @@ void ConnectionUiController::openConnection()
{
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
ErrorCode errorCode = m_connectionController->openConnection(serverId);
if (errorCode != ErrorCode::NoError) {
notifyConnectionBlocked(errorCode);
emit connectionErrorOccurred(errorCode);
return;
}
}
@@ -133,36 +130,10 @@ void ConnectionUiController::toggleConnection()
} else if (isConnected()) {
closeConnection();
} else {
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
return;
}
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
if (errorCode != ErrorCode::NoError) {
notifyConnectionBlocked(errorCode);
return;
}
emit prepareConfig();
}
}
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
{
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
emit unsupportedConnectDrawerRequested();
return;
}
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
return;
}
emit connectionErrorOccurred(errorCode);
}
bool ConnectionUiController::isConnectionInProgress() const
{
return m_isConnectionInProgress;
@@ -172,32 +143,3 @@ bool ConnectionUiController::isConnected() const
{
return m_isConnected;
}
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
const QString &clientId) const
{
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
return false;
}
if (m_serversController->getDefaultServerId() != serverId) {
return false;
}
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
return false;
}
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return false;
}
const QString connectionClientId =
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
if (connectionClientId.isEmpty()) {
return false;
}
return connectionClientId == clientId || connectionClientId.contains(clientId);
}

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