mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-07-09 02:17:08 +03:00
Compare commits
28 Commits
server_scr
...
chore/up_v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86cc22f5d9 | ||
|
|
25df36ad73 | ||
|
|
ea8e3fda56 | ||
|
|
31ae949ba0 | ||
|
|
a17847a90b | ||
|
|
2ca497cd06 | ||
|
|
bd003b149d | ||
|
|
35c1014a44 | ||
|
|
27857cde8a | ||
|
|
b39f583097 | ||
|
|
6c7b65cac6 | ||
|
|
0f6847219b | ||
|
|
5d16645b84 | ||
|
|
203a092dc9 | ||
|
|
d8b8590bc4 | ||
|
|
9b8bfaa6f8 | ||
|
|
890103a16a | ||
|
|
56ab82f87f | ||
|
|
3984acbb44 | ||
|
|
cc404378f9 | ||
|
|
594635e5cf | ||
|
|
f9b106cf5b | ||
|
|
a9861d18b7 | ||
|
|
c14138f031 | ||
|
|
60686fde24 | ||
|
|
bd0747296e | ||
|
|
ba61019a50 | ||
|
|
113f967006 |
5
.github/workflows/deploy.yml
vendored
5
.github/workflows/deploy.yml
vendored
@@ -23,6 +23,9 @@ jobs:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -154,7 +157,7 @@ jobs:
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
|
||||
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.1)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.3)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -18,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -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 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2132)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -212,11 +212,32 @@ endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET client_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#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"
|
||||
@@ -119,7 +120,13 @@ 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);
|
||||
@@ -215,6 +222,9 @@ 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();
|
||||
|
||||
10
client/android/res/drawable/ic_launcher_background.xml
Normal file
10
client/android/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?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>
|
||||
7
client/android/res/drawable/ic_launcher_monochrome.xml
Normal file
7
client/android/res/drawable/ic_launcher_monochrome.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?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%" />
|
||||
6
client/android/res/mipmap-anydpi-v26/icon.xml
Normal file
6
client/android/res/mipmap-anydpi-v26/icon.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
6
client/android/res/mipmap-anydpi-v26/icon_round.xml
Normal file
6
client/android/res/mipmap-anydpi-v26/icon_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
BIN
client/android/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
BIN
client/android/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
client/android/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
BIN
client/android/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
BIN
client/android/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
BIN
client/android/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
client/android/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
BIN
client/android/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
client/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
BIN
client/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
16
client/android/res/values-v31/styles.xml
Normal file
16
client/android/res/values-v31/styles.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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>
|
||||
4
client/android/res/values/colors.xml
Normal file
4
client/android/res/values/colors.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0E0E11</color>
|
||||
</resources>
|
||||
@@ -42,6 +42,7 @@ 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
|
||||
@@ -763,7 +764,13 @@ class AmneziaActivity : QtActivity() {
|
||||
fun openFile(filter: String?) {
|
||||
Log.v(TAG, "Open file with filter: $filter")
|
||||
mainScope.launch {
|
||||
val intent = if (!isOnTv()) {
|
||||
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 mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
@@ -789,6 +796,7 @@ class AmneziaActivity : QtActivity() {
|
||||
else -> type = "*/*"
|
||||
}
|
||||
}
|
||||
`package` = systemPickerPackage
|
||||
}
|
||||
} else {
|
||||
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||
@@ -800,8 +808,11 @@ class AmneziaActivity : QtActivity() {
|
||||
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||
showNoFileBrowserAlertDialog()
|
||||
}
|
||||
val uri = it?.data?.apply {
|
||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
val uri = it?.data?.let { u ->
|
||||
if (u.scheme == "content") {
|
||||
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
|
||||
}
|
||||
u
|
||||
}?.toString() ?: ""
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
if (uri.isNotEmpty()) {
|
||||
@@ -841,7 +852,12 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.v(TAG, "Get fd for $fileName")
|
||||
return blockingCall(Dispatchers.IO) {
|
||||
try {
|
||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||
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?.fd ?: -1
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get fd: $e")
|
||||
@@ -1061,12 +1077,10 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun sendTouch(x: Float, y: Float) {
|
||||
Log.v(TAG, "Send touch: $x, $y")
|
||||
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))
|
||||
}
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
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() {
|
||||
|
||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
// SAF launcher for Android 10+ where File API is blocked by scoped storage
|
||||
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
|
||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
}
|
||||
if (activitiesToResolveIntent.all {
|
||||
if (activities.all {
|
||||
val name = it.activityInfo.packageName
|
||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||
}) {
|
||||
@@ -32,38 +38,140 @@ class TvFilePicker : ComponentActivity() {
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}) {
|
||||
}) { uri ->
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = it
|
||||
data = uri
|
||||
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")
|
||||
getFile()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
launchSaf()
|
||||
} else {
|
||||
checkPermissionAndBrowse()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent")
|
||||
getFile()
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
private fun getFile() {
|
||||
private fun launchSaf() {
|
||||
try {
|
||||
Log.v(TAG, "getFile")
|
||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
||||
safLauncher.launch(arrayOf("*/*"))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Log.w(TAG, "Activity not found")
|
||||
Log.w(TAG, "No SAF activity found")
|
||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get file: $e")
|
||||
Log.e(TAG, "SAF launch failed: $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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ 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)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel ${CMAKE_BINARY_DIR}/3rd/SortFilterProxyModel)
|
||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
@@ -12,20 +12,24 @@ add_compile_definitions(_WINSOCKAPI_)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CMAKE_BINARY_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)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad ${CMAKE_BINARY_DIR}/3rd/qtgamepad)
|
||||
# Link both the C++ module and QML plugin
|
||||
if(TARGET GamepadLegacy)
|
||||
if(TARGET ${PROJECT})
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacy)
|
||||
endif()
|
||||
if(TARGET GamepadLegacyQuickPrivate)
|
||||
if(TARGET ${PROJECT})
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacyQuickPrivate)
|
||||
endif()
|
||||
message(STATUS "Gamepad support enabled for Android")
|
||||
else()
|
||||
|
||||
@@ -54,7 +54,6 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
|
||||
@@ -152,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}
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ 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
|
||||
@@ -127,6 +128,7 @@ 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
|
||||
|
||||
@@ -244,11 +244,7 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
|
||||
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
|
||||
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
|
||||
<< "appendClient=" << appendNewClient;
|
||||
QString flowValue = srv.flow;
|
||||
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
|
||||
flowValue = QStringLiteral("xtls-rprx-vision");
|
||||
}
|
||||
|
||||
const QString flowValue = srv.flow;
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
@@ -563,9 +559,12 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
if (pad.obfsMode) {
|
||||
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
|
||||
QJsonObject br;
|
||||
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();
|
||||
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;
|
||||
xo[QStringLiteral("xPaddingBytes")] = br;
|
||||
}
|
||||
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
|
||||
|
||||
@@ -78,7 +78,8 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
|
||||
|
||||
@@ -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->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
|
||||
@@ -212,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->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
@@ -949,7 +949,8 @@ 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->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
|
||||
auto *watcher = new QFutureWatcher<QPair<ErrorCode, QByteArray>>();
|
||||
QObject::connect(watcher, &QFutureWatcher<QPair<ErrorCode, QByteArray>>::finished,
|
||||
|
||||
@@ -49,14 +49,96 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -120,10 +202,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -73,6 +75,8 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
#endif
|
||||
|
||||
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent)
|
||||
QQmlApplicationEngine *engine, QObject *parent,
|
||||
bool skipPlatformControllerInit)
|
||||
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||
{
|
||||
initRepositories();
|
||||
@@ -31,8 +32,10 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
if (!skipPlatformControllerInit) {
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
}
|
||||
initLogging();
|
||||
|
||||
m_translator = new QTranslator(this);
|
||||
@@ -191,7 +194,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, m_languageUiController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
|
||||
@@ -90,7 +90,8 @@ class CoreController : public QObject
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr,
|
||||
bool skipPlatformControllerInit = false);
|
||||
|
||||
PageController* pageController() const;
|
||||
void setQmlRoot();
|
||||
@@ -114,7 +115,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; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "coreSignalHandlers.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -33,7 +34,6 @@
|
||||
#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,7 +145,9 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
QtConcurrent::run([this, 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) {
|
||||
@@ -156,15 +158,17 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
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_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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,17 +180,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,13 +205,15 @@ 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);
|
||||
QtConcurrent::run([this, serverId, containerConfig, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -237,13 +240,16 @@ 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->openConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +290,8 @@ 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()
|
||||
@@ -348,6 +356,9 @@ 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()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#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"
|
||||
@@ -45,15 +46,78 @@ 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)
|
||||
{
|
||||
try {
|
||||
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);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
decryptedPayload = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
decryptedPayload = encryptedPayload;
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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, QObject *parent)
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isDevEnvironment(isDevEnvironment),
|
||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -308,8 +372,9 @@ 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, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, proxyUrlsCacheKey, [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,
|
||||
@@ -355,8 +420,6 @@ 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) {
|
||||
@@ -372,10 +435,12 @@ 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 {};
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
@@ -390,26 +455,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
if (!decryptProxyUrlsPayload(encryptedResponseBody, m_isDevEnvironment, responseBody)) {
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||
continue;
|
||||
}
|
||||
@@ -420,6 +467,8 @@ 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();
|
||||
@@ -431,7 +480,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||
@@ -573,10 +622,12 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete({});
|
||||
onComplete(shuffledProxyUrls(readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -589,33 +640,17 @@ 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, onComplete, reply]() {
|
||||
connect(reply, &QNetworkReply::finished, this,
|
||||
[this, proxyStorageUrls, currentProxyStorageIndex, proxyUrlsCacheKey, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
responseBody = encrypted;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
if (!decryptProxyUrlsPayload(encrypted, m_isDevEnvironment, responseBody)) {
|
||||
qCritical() << "error decrypting payload";
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -623,13 +658,9 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encrypted);
|
||||
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
onComplete(shuffledProxyUrls(endpoints));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -638,7 +669,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
#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, QObject *parent = nullptr);
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
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);
|
||||
@@ -55,7 +58,7 @@ private:
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete);
|
||||
const QString &proxyUrlsCacheKey, 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,
|
||||
@@ -65,6 +68,7 @@ private:
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ 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:
|
||||
|
||||
@@ -486,7 +486,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
@@ -645,7 +645,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
@@ -699,7 +699,7 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,16 @@ 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,
|
||||
@@ -93,7 +103,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
bool isUpdate)
|
||||
{
|
||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, sshSession);
|
||||
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -152,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -162,11 +168,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
}
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
@@ -182,10 +188,10 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -205,6 +211,12 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
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 =
|
||||
@@ -213,11 +225,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
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="
|
||||
qDebug() << "InstallController::updateServerConfig 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="
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +240,9 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
clearCachedProfile(serverId, container);
|
||||
if (reinstallRequired) {
|
||||
clearCachedProfile(serverId, container);
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
@@ -236,6 +250,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
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) {
|
||||
@@ -358,7 +407,7 @@ void InstallController::addEmptyServer(const ServerCredentials &credentials)
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
@@ -696,18 +745,6 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,39 +759,6 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,9 +798,23 @@ 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 regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = regex.match(stdOut);
|
||||
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
|
||||
if (match.hasMatch()) {
|
||||
int majorVersion = match.captured(1).toInt();
|
||||
int minorVersion = match.captured(2).toInt();
|
||||
@@ -809,8 +827,19 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
|
||||
if (stdOut.contains("lock"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("command not found"))
|
||||
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()) {
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
}
|
||||
|
||||
if (stdOut.contains("Container runtime service not running"))
|
||||
return ErrorCode::ContainerRuntimeServiceNotRunning;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -847,7 +876,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("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
@@ -929,7 +958,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QString script = QString("sudo reboot");
|
||||
|
||||
@@ -957,7 +986,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -979,13 +1008,12 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
SshSession sshSession;
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1089,7 +1117,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1132,7 +1160,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||
TransportProto transportProto, bool &wasContainerInstalled)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
if (errorCode) {
|
||||
@@ -1170,7 +1198,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
||||
serverConfig.containers.insert(iterator.key(), iterator.value());
|
||||
@@ -1201,7 +1229,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1240,28 +1268,26 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output,
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
ServerCredentials processedCredentials = credentials;
|
||||
|
||||
if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (!passphraseCallback) {
|
||||
return ErrorCode::SshPrivateKeyError;
|
||||
}
|
||||
|
||||
QString decryptedPrivateKey;
|
||||
errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback);
|
||||
errorCode = sshSession.getDecryptedPrivateKey(credentials, decryptedPrivateKey, passphraseCallback);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
processedCredentials.secretData = decryptedPrivateKey;
|
||||
credentials.secretData = decryptedPrivateKey;
|
||||
}
|
||||
|
||||
output = sshSession.checkSshConnection(processedCredentials, errorCode);
|
||||
output = sshSession.checkSshConnection(credentials, errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -1465,7 +1491,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1490,7 +1516,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1526,7 +1552,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
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);
|
||||
@@ -1566,7 +1592,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
const QString script = QStringLiteral(
|
||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||
.arg(containerName);
|
||||
@@ -1600,7 +1626,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
||||
}
|
||||
|
||||
@@ -1623,7 +1649,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession(this);
|
||||
SshSession sshSession;
|
||||
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);
|
||||
|
||||
@@ -34,7 +34,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// 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 rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
@@ -64,7 +69,8 @@ public:
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
|
||||
ErrorCode checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback = nullptr);
|
||||
|
||||
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
|
||||
|
||||
|
||||
@@ -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,14 +758,17 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const int index
|
||||
ContainerConfig containerCfg = adminConfig->containerConfig(container);
|
||||
QString containerClientId = containerCfg.protocolConfig.clientId();
|
||||
|
||||
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
|
||||
const bool isAdminMatch = !clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId);
|
||||
if (isAdminMatch) {
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
}
|
||||
|
||||
emit clientRevoked(index);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
emit revokeFinished(errorCode);
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ 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:
|
||||
|
||||
@@ -363,6 +363,6 @@ void SettingsController::disablePremV1MigrationReminder()
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
{
|
||||
return m_appSettingsRepository->nextAvailableServerName();
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
@@ -96,7 +95,8 @@ void UpdateController::fetchGatewayUrl()
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
@@ -109,7 +109,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
logger.error() << "Gateway request failed, error code:" << static_cast<int>(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
@@ -250,17 +250,9 @@ void UpdateController::runInstaller()
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
logger.error() << "Installer download failed, network error:" << static_cast<int>(reply->error())
|
||||
<< reply->errorString();
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
@@ -71,48 +71,62 @@ ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const Serve
|
||||
DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out)
|
||||
{
|
||||
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);
|
||||
out = { };
|
||||
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
const bool isTelemt = container == DockerContainer::Telemt;
|
||||
|
||||
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();
|
||||
const QString sportFilter = QString::number(listenPort);
|
||||
const QString peersCmd = QStringLiteral("sudo conntrack -L -p tcp --dport ") + sportFilter
|
||||
+ QStringLiteral(" 2>/dev/null | grep ESTABLISHED | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){print "
|
||||
"substr($i,5); break}}'");
|
||||
const QString publicFilter = QStringLiteral(" | grep -vE "
|
||||
"'^(10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3["
|
||||
"01])\\.|::1$|fe80:|f[cd][0-9a-f][0-9a-f]:)'");
|
||||
const QString clientsCmd =
|
||||
QStringLiteral("CLIENTS=$(") + peersCmd + publicFilter + QStringLiteral(" | sort -u | grep -c .); ");
|
||||
const QString confFile =
|
||||
isTelemt ? QStringLiteral("/data/config.toml") : QStringLiteral("/data/proxy-multi.conf");
|
||||
const QString statsUrl = QString();
|
||||
|
||||
const QString script = QStringLiteral("CN=") + containerName + QStringLiteral("; ")
|
||||
+ QStringLiteral("PORT_OK=$(sudo ss -tlnp 2>/dev/null | grep -q :") + QString::number(listenPort)
|
||||
+ QStringLiteral(" && echo yes || echo no); ")
|
||||
+ QStringLiteral("TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "
|
||||
"https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || "
|
||||
"echo no); ")
|
||||
+ clientsCmd + QStringLiteral("CONF_TIME=$(sudo docker exec \"$CN\" sh -c 'stat -c \"%y\" ") + confFile
|
||||
+ QStringLiteral(" 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); ")
|
||||
+ QStringLiteral("echo \"PORT_OK=${PORT_OK}\"; ") + QStringLiteral("echo \"TG_OK=${TG_OK}\"; ")
|
||||
+ QStringLiteral("echo \"CLIENTS=${CLIENTS:-0}\"; ") + QStringLiteral("echo \"CONF_TIME=${CONF_TIME}\"; ")
|
||||
+ QStringLiteral("echo \"STATS=") + statsUrl + QStringLiteral("\";");
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith(QLatin1String("PORT_OK="))) {
|
||||
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("TG_OK="))) {
|
||||
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
|
||||
out.clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
|
||||
out.lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith(QLatin1String("STATS="))) {
|
||||
out.statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
|
||||
@@ -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(protocols::xray::defaultSite);
|
||||
c.key = json.value(configKey::xPaddingKey).toString();
|
||||
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,6 +365,8 @@ 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
|
||||
@@ -466,6 +468,17 @@ 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);
|
||||
|
||||
@@ -29,6 +29,11 @@ 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;
|
||||
|
||||
@@ -27,6 +27,8 @@ 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;
|
||||
|
||||
@@ -43,6 +43,11 @@ 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
|
||||
{
|
||||
|
||||
@@ -32,6 +32,8 @@ 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;
|
||||
|
||||
@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
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 (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
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::stop(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,6 +29,7 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -280,6 +280,24 @@ 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();
|
||||
@@ -426,26 +444,6 @@ void SecureAppSettingsRepository::clearSettings()
|
||||
emit settingsCleared();
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::nextAvailableServerName() const
|
||||
{
|
||||
int i = 0;
|
||||
bool nameExist = false;
|
||||
|
||||
do {
|
||||
i++;
|
||||
nameExist = false;
|
||||
QJsonArray servers = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &server : servers) {
|
||||
if (server.toObject().value(configKey::description).toString() == QString("Server") + " " + QString::number(i)) {
|
||||
nameExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (nameExist);
|
||||
|
||||
return QString("Server") + " " + QString::number(i);
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setInstallationUuid(const QString &uuid)
|
||||
{
|
||||
m_settings->setValue("Conf/installationUuid", uuid);
|
||||
|
||||
@@ -59,7 +59,9 @@ 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;
|
||||
@@ -90,8 +92,6 @@ public:
|
||||
bool restoreAppConfig(const QByteArray &cfg);
|
||||
void clearSettings();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
@@ -32,6 +33,45 @@ QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &pay
|
||||
return o;
|
||||
}
|
||||
|
||||
QString storedServerDisplayName(const SecureServersRepository *repository, const QString &serverId)
|
||||
{
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (repository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin:
|
||||
if (const auto cfg = repository->selfHostedAdminConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::SelfHostedUser:
|
||||
if (const auto cfg = repository->selfHostedUserConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Native:
|
||||
if (const auto cfg = repository->nativeConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium:
|
||||
if (const auto cfg = repository->apiV2Config(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2:
|
||||
if (const auto cfg = repository->legacyApiConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
|
||||
@@ -153,6 +193,28 @@ void SecureServersRepository::clearServers()
|
||||
syncToStorage();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::nextAvailableServerName() const
|
||||
{
|
||||
QSet<QString> usedNames;
|
||||
usedNames.reserve(m_orderedServerIds.size());
|
||||
|
||||
for (const QString &serverId : m_orderedServerIds) {
|
||||
const QString displayName = storedServerDisplayName(this, serverId);
|
||||
if (!displayName.isEmpty()) {
|
||||
usedNames.insert(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
QString candidate;
|
||||
do {
|
||||
++i;
|
||||
candidate = tr("Server") + QLatin1Char(' ') + QString::number(i);
|
||||
} while (usedNames.contains(candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
const QString id = normalizedOrGeneratedServerId(serverId);
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
void clearServers();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -271,6 +271,7 @@ namespace amnezia
|
||||
constexpr char workersModeAuto[] = "auto";
|
||||
constexpr char workersModeManual[] = "manual";
|
||||
constexpr int maxWorkers = 32;
|
||||
constexpr int botTagHexLength = 32;
|
||||
}
|
||||
|
||||
} // namespace protocols
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,6 +21,8 @@ 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)
|
||||
@@ -62,6 +64,8 @@ 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" },
|
||||
@@ -83,6 +87,10 @@ 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.") },
|
||||
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -202,6 +213,8 @@ 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;
|
||||
@@ -252,6 +265,8 @@ 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;
|
||||
@@ -336,6 +351,10 @@ 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;
|
||||
@@ -352,6 +371,11 @@ 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))
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace amnezia
|
||||
XrayServerConfigInvalid = 215,
|
||||
XrayServerNoVlessClients = 216,
|
||||
XrayRealityKeysReadFailed = 217,
|
||||
ServerContainerRuntimeNotSupported = 218,
|
||||
ContainerRuntimeServiceNotRunning = 219,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -79,6 +81,7 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
@@ -123,5 +126,3 @@ namespace amnezia
|
||||
Q_DECLARE_METATYPE(amnezia::ErrorCode)
|
||||
|
||||
#endif // ERRORCODES_H
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ 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;
|
||||
@@ -69,6 +71,7 @@ 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;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ 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");
|
||||
@@ -366,6 +367,14 @@ 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;
|
||||
|
||||
@@ -21,6 +21,7 @@ enum SharedScriptType {
|
||||
// General scripts
|
||||
prepare_host,
|
||||
install_docker,
|
||||
install_conntrack,
|
||||
build_container,
|
||||
remove_container,
|
||||
remove_all_containers,
|
||||
|
||||
@@ -390,55 +390,55 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
if (!obj.value("Jc").isNull()) {
|
||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
||||
if (const auto jc = obj.value("Jc"); !jc.isUndefined()) {
|
||||
config.m_junkPacketCount = jc.toString();
|
||||
}
|
||||
if (!obj.value("Jmin").isNull()) {
|
||||
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
||||
if (const auto jmin = obj.value("Jmin"); !jmin.isUndefined()) {
|
||||
config.m_junkPacketMinSize = jmin.toString();
|
||||
}
|
||||
if (!obj.value("Jmax").isNull()) {
|
||||
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
||||
if (const auto jmax = obj.value("Jmax"); !jmax.isUndefined()) {
|
||||
config.m_junkPacketMaxSize = jmax.toString();
|
||||
}
|
||||
if (!obj.value("S1").isNull()) {
|
||||
config.m_initPacketJunkSize = obj.value("S1").toString();
|
||||
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
|
||||
config.m_initPacketJunkSize = s1.toString();
|
||||
}
|
||||
if (!obj.value("S2").isNull()) {
|
||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
|
||||
config.m_responsePacketJunkSize = s2.toString();
|
||||
}
|
||||
if (!obj.value("S3").isNull()) {
|
||||
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
||||
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
|
||||
config.m_cookieReplyPacketJunkSize = s3.toString();
|
||||
}
|
||||
if (!obj.value("S4").isNull()) {
|
||||
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
||||
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
|
||||
config.m_transportPacketJunkSize = s4.toString();
|
||||
}
|
||||
|
||||
if (!obj.value("H1").isNull()) {
|
||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||
if (const auto h1 = obj.value("H1"); !h1.isUndefined()) {
|
||||
config.m_initPacketMagicHeader = h1.toString();
|
||||
}
|
||||
if (!obj.value("H2").isNull()) {
|
||||
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
||||
if (const auto h2 = obj.value("H2"); !h2.isUndefined()) {
|
||||
config.m_responsePacketMagicHeader = h2.toString();
|
||||
}
|
||||
if (!obj.value("H3").isNull()) {
|
||||
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
||||
if (const auto h3 = obj.value("H3"); !h3.isUndefined()) {
|
||||
config.m_underloadPacketMagicHeader = h3.toString();
|
||||
}
|
||||
if (!obj.value("H4").isNull()) {
|
||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||
if (const auto h4 = obj.value("H4"); !h4.isUndefined()) {
|
||||
config.m_transportPacketMagicHeader = h4.toString();
|
||||
}
|
||||
|
||||
if (!obj.value("I1").isNull()) {
|
||||
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
||||
if (const auto i1 = obj.value("I1"); !i1.isUndefined()) {
|
||||
config.m_specialJunk["I1"] = i1.toString();
|
||||
}
|
||||
if (!obj.value("I2").isNull()) {
|
||||
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
||||
if (const auto i2 = obj.value("I2"); !i2.isUndefined()) {
|
||||
config.m_specialJunk["I2"] = i2.toString();
|
||||
}
|
||||
if (!obj.value("I3").isNull()) {
|
||||
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
||||
if (const auto i3 = obj.value("I3"); !i3.isUndefined()) {
|
||||
config.m_specialJunk["I3"] = i3.toString();
|
||||
}
|
||||
if (!obj.value("I4").isNull()) {
|
||||
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
||||
if (const auto i4 = obj.value("I4"); !i4.isUndefined()) {
|
||||
config.m_specialJunk["I4"] = i4.toString();
|
||||
}
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
if (const auto i5 = obj.value("I5"); !i5.isUndefined()) {
|
||||
config.m_specialJunk["I5"] = i5.toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -26,6 +26,8 @@ set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -114,10 +116,20 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension 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(networkextension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(networkextension 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(networkextension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -25,6 +25,8 @@ 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)
|
||||
@@ -118,10 +120,20 @@ 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)
|
||||
|
||||
@@ -62,12 +62,29 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
|
||||
}
|
||||
|
||||
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
||||
if (m_splitTunnelManager == nullptr)
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.m_vpnDisabledApps.length() > 0) {
|
||||
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
|
||||
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
m_splitTunnelManager->stop();
|
||||
}
|
||||
@@ -79,7 +96,9 @@ 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;
|
||||
}
|
||||
@@ -90,14 +109,20 @@ 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;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
// 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);
|
||||
@@ -49,6 +51,53 @@ 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) {
|
||||
@@ -116,47 +165,12 @@ bool WindowsFirewall::initSublayer() {
|
||||
}
|
||||
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
|
||||
|
||||
// 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;
|
||||
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");
|
||||
}
|
||||
|
||||
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QScopeGuard>
|
||||
#include <QUrl>
|
||||
|
||||
#pragma region
|
||||
|
||||
@@ -148,6 +149,18 @@ 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(
|
||||
@@ -287,6 +300,10 @@ 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],
|
||||
@@ -314,7 +331,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";
|
||||
logger.error() << "Driver init failed. Error:" << GetLastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -323,11 +340,16 @@ 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";
|
||||
logger.error() << "Failed to set Process Config. Error:"
|
||||
<< GetLastError();
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Set Process Config ok || new State:" << stateString();
|
||||
@@ -340,11 +362,16 @@ 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";
|
||||
logger.error() << "Failed to set Network Config. Error:" << GetLastError();
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "New Network Config Applied || new State:" << stateString();
|
||||
@@ -404,13 +431,22 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||
size_t cummulated_string_size = 0;
|
||||
QStringList dosPaths;
|
||||
for (auto const& path : appPaths) {
|
||||
auto dosPath = convertPath(path);
|
||||
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;
|
||||
}
|
||||
dosPaths.append(dosPath);
|
||||
cummulated_string_size += dosPath.toStdWString().size() * sizeof(wchar_t);
|
||||
logger.debug() << dosPath;
|
||||
}
|
||||
if (dosPaths.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
size_t bufferSize = sizeof(CONFIGURATION_HEADER) +
|
||||
(sizeof(CONFIGURATION_ENTRY) * appPaths.size()) +
|
||||
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size()) +
|
||||
cummulated_string_size;
|
||||
std::vector<uint8_t> outBuffer(bufferSize);
|
||||
|
||||
@@ -418,7 +454,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||
auto entry = (CONFIGURATION_ENTRY*)(header + 1);
|
||||
|
||||
auto stringDest = &outBuffer[0] + sizeof(CONFIGURATION_HEADER) +
|
||||
(sizeof(CONFIGURATION_ENTRY) * appPaths.size());
|
||||
(sizeof(CONFIGURATION_ENTRY) * dosPaths.size());
|
||||
|
||||
SIZE_T stringOffset = 0;
|
||||
|
||||
@@ -437,7 +473,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||
stringOffset += stringLength;
|
||||
}
|
||||
|
||||
header->NumEntries = appPaths.length();
|
||||
header->NumEntries = dosPaths.length();
|
||||
header->TotalLength = bufferSize;
|
||||
|
||||
return outBuffer;
|
||||
@@ -449,9 +485,7 @@ std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
|
||||
|
||||
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
|
||||
|
||||
auto ifaces = QNetworkInterface::allInterfaces();
|
||||
|
||||
if (vpnAdapterIndex == 0) {
|
||||
if (vpnAdapterIndex == 0) {
|
||||
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
|
||||
}
|
||||
// Always the VPN
|
||||
@@ -520,7 +554,7 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
||||
auto process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
|
||||
currentProcess.th32ProcessID);
|
||||
|
||||
if (process_handle == INVALID_HANDLE_VALUE) {
|
||||
if (process_handle == nullptr) {
|
||||
continue;
|
||||
}
|
||||
ProcessInfo info = getProcessInfo(process_handle, currentProcess);
|
||||
@@ -640,23 +674,48 @@ bool WindowsSplitTunnel::isInstalled() {
|
||||
}
|
||||
|
||||
QString WindowsSplitTunnel::convertPath(const QString& path) {
|
||||
auto parts = path.split("/");
|
||||
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 "";
|
||||
}
|
||||
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, 0xFFu);
|
||||
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
|
||||
(wchar_t*)buffer.data(), buffer.size() / 2);
|
||||
|
||||
if (ok == ERROR_INSUFFICIENT_BUFFER) {
|
||||
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 "";
|
||||
}
|
||||
buffer.resize(buffer.size() * 2);
|
||||
ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(),
|
||||
buffer.size() / 2);
|
||||
buffer.fill(0);
|
||||
}
|
||||
if (ok == 0) {
|
||||
WindowsUtils::windowsLog("Err fetching dos path");
|
||||
logger.error() << "QueryDosDeviceW failed after buffer growth for"
|
||||
<< driveLetter << "error:" << err;
|
||||
return "";
|
||||
}
|
||||
QString deviceName;
|
||||
|
||||
@@ -5,4 +5,4 @@ elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then L
|
||||
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 $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; 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
|
||||
|
||||
10
client/server_scripts/install_conntrack.sh
Normal file
10
client/server_scripts/install_conntrack.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
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"
|
||||
@@ -1,25 +1,34 @@
|
||||
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 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 [ "$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 ! 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;\
|
||||
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;\
|
||||
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;\
|
||||
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;\
|
||||
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;\
|
||||
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;\
|
||||
fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
||||
docker --version;\
|
||||
sudo -n docker --version || docker --version;\
|
||||
uname -sr
|
||||
|
||||
@@ -1,5 +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 network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<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>
|
||||
|
||||
@@ -61,6 +61,12 @@ 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
|
||||
|
||||
@@ -475,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
if (serverId.isEmpty()) {
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +132,36 @@ 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;
|
||||
@@ -143,3 +171,32 @@ 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);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -48,9 +50,12 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -128,6 +128,11 @@ void PageController::showOnStartup()
|
||||
}
|
||||
}
|
||||
|
||||
bool PageController::shouldStartMinimized() const
|
||||
{
|
||||
return m_settingsController->isStartMinimizedEnabled();
|
||||
}
|
||||
|
||||
bool PageController::isTriggeredByConnectButton()
|
||||
{
|
||||
return m_isTriggeredByConnectButton;
|
||||
|
||||
@@ -123,6 +123,7 @@ public slots:
|
||||
void updateNavigationBarColor(const int color);
|
||||
|
||||
void showOnStartup();
|
||||
bool shouldStartMinimized() const;
|
||||
|
||||
bool isTriggeredByConnectButton();
|
||||
void setTriggeredByConnectButton(bool trigger);
|
||||
|
||||
@@ -9,6 +9,13 @@ ExportUiController::ExportUiController(ExportController* exportController, QObje
|
||||
: QObject(parent),
|
||||
m_exportController(exportController)
|
||||
{
|
||||
connect(m_exportController, &ExportController::revokeFinished, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit revokeConfigFinished();
|
||||
} else {
|
||||
emit exportErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ExportUiController::generateFullAccessConfig(const QString &serverId)
|
||||
@@ -92,7 +99,6 @@ void ExportUiController::updateClientManagementModel(const QString &serverId, in
|
||||
void ExportUiController::revokeConfig(int row, const QString &serverId, int containerIndex)
|
||||
{
|
||||
m_exportController->revokeConfig(row, serverId, containerIndex);
|
||||
emit revokeConfigFinished();
|
||||
}
|
||||
|
||||
void ExportUiController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent>
|
||||
#include <utility>
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
@@ -75,13 +76,7 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -217,15 +212,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -271,6 +264,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -279,14 +307,17 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
|
||||
|
||||
if (asyncUpdate) {
|
||||
emit serverIsBusy(true);
|
||||
const bool emitBusy = container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
|
||||
if (emitBusy)
|
||||
emit serverIsBusy(true);
|
||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||
const Proto protocolTypeCopy = protocolType;
|
||||
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
|
||||
[this, watcher, serverId, container, closePage, protocolTypeCopy, emitBusy]() {
|
||||
const ErrorCode errorCode = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit serverIsBusy(false);
|
||||
if (emitBusy)
|
||||
emit serverIsBusy(false);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
const ContainerConfig updatedConfig =
|
||||
@@ -305,13 +336,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -332,17 +363,27 @@ void InstallUiController::setContainerEnabled(const QString &serverId, int conta
|
||||
}
|
||||
|
||||
emit serverIsBusy(true);
|
||||
const ErrorCode errorCode = m_installController->setDockerContainerEnabledState(serverId, container, enabled);
|
||||
emit serverIsBusy(false);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
const ContainerConfig currentConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(currentConfig);
|
||||
emit setContainerEnabledFinished(enabled);
|
||||
return;
|
||||
}
|
||||
InstallController *installController = m_installController;
|
||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||
[this, watcher, serverId, container, enabled]() {
|
||||
const ErrorCode errorCode = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit serverIsBusy(false);
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
const ContainerConfig currentConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(currentConfig);
|
||||
emit setContainerEnabledFinished(enabled);
|
||||
return;
|
||||
}
|
||||
emit installationErrorOccurred(errorCode);
|
||||
});
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([installController, serverId, container, enabled]() -> ErrorCode {
|
||||
return installController->setDockerContainerEnabledState(serverId, container, enabled);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex)
|
||||
@@ -352,13 +393,23 @@ void InstallUiController::refreshContainerStatus(const QString &serverId, int co
|
||||
return;
|
||||
}
|
||||
|
||||
int status = 3;
|
||||
const ErrorCode errorCode = m_installController->queryDockerContainerStatus(serverId, container, status);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit containerStatusRefreshed(3);
|
||||
return;
|
||||
}
|
||||
emit containerStatusRefreshed(status);
|
||||
using StatusResult = std::pair<int, int>; // {status, errorCode}
|
||||
InstallController *installController = m_installController;
|
||||
auto *watcher = new QFutureWatcher<StatusResult>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<StatusResult>::finished, this, [this, watcher]() {
|
||||
const StatusResult result = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit containerStatusRefreshed(result.first, result.second);
|
||||
});
|
||||
QFuture<StatusResult> future = QtConcurrent::run([installController, serverId, container]() -> StatusResult {
|
||||
int status = 3;
|
||||
const ErrorCode errorCode = installController->queryDockerContainerStatus(serverId, container, status);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return { 3, static_cast<int>(errorCode) };
|
||||
}
|
||||
return { status, static_cast<int>(ErrorCode::NoError) };
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port)
|
||||
@@ -368,14 +419,27 @@ void InstallUiController::refreshContainerDiagnostics(const QString &serverId, i
|
||||
return;
|
||||
}
|
||||
|
||||
MtProxyContainerDiagnostics diag;
|
||||
const ErrorCode errorCode = m_installController->queryMtProxyDiagnostics(serverId, container, port, diag);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
|
||||
return;
|
||||
}
|
||||
emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected,
|
||||
diag.lastConfigRefresh, diag.statsEndpoint);
|
||||
using DiagResult = std::pair<bool, MtProxyContainerDiagnostics>;
|
||||
InstallController *installController = m_installController;
|
||||
auto *watcher = new QFutureWatcher<DiagResult>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<DiagResult>::finished, this, [this, watcher]() {
|
||||
const DiagResult result = watcher->result();
|
||||
watcher->deleteLater();
|
||||
if (!result.first) {
|
||||
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
|
||||
return;
|
||||
}
|
||||
const MtProxyContainerDiagnostics &diag = result.second;
|
||||
emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected,
|
||||
diag.lastConfigRefresh, diag.statsEndpoint);
|
||||
});
|
||||
QFuture<DiagResult> future =
|
||||
QtConcurrent::run([installController, serverId, container, port]() -> DiagResult {
|
||||
MtProxyContainerDiagnostics diag;
|
||||
const ErrorCode errorCode = installController->queryMtProxyDiagnostics(serverId, container, port, diag);
|
||||
return { errorCode == ErrorCode::NoError, diag };
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex)
|
||||
@@ -385,8 +449,17 @@ void InstallUiController::fetchContainerSecret(const QString &serverId, int cont
|
||||
return;
|
||||
}
|
||||
|
||||
const QString secret = m_installController->fetchDockerContainerSecret(serverId, container);
|
||||
emit containerSecretFetched(secret);
|
||||
InstallController *installController = m_installController;
|
||||
auto *watcher = new QFutureWatcher<QString>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [this, watcher]() {
|
||||
const QString secret = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit containerSecretFetched(secret);
|
||||
});
|
||||
QFuture<QString> future = QtConcurrent::run([installController, serverId, container]() -> QString {
|
||||
return installController->fetchDockerContainerSecret(serverId, container);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void InstallUiController::rebootServer(const QString &serverId)
|
||||
|
||||
@@ -64,7 +64,8 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -113,7 +114,7 @@ signals:
|
||||
void removeAllContainersFinished(const QString &finishedMessage);
|
||||
void removeContainerFinished(const QString &finishedMessage);
|
||||
void setContainerEnabledFinished(bool enabled);
|
||||
void containerStatusRefreshed(int status);
|
||||
void containerStatusRefreshed(int status, int errorCode);
|
||||
void containerDiagnosticsRefreshed(bool portReachable, bool upstreamReachable, int clientsConnected,
|
||||
const QString &lastConfigRefresh, const QString &statsEndpoint);
|
||||
void containerSecretFetched(const QString &secret);
|
||||
@@ -132,7 +133,6 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,6 +162,8 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
updateContainersModel();
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
m_serversController(serversController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
emit appLanguageChanged();
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -32,6 +29,7 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
@@ -122,7 +120,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void appLanguageChanged();
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -135,12 +133,12 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return tr("Active");
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg;
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
@@ -272,7 +276,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
|
||||
}
|
||||
|
||||
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
|
||||
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
|
||||
applyDefaultsToServerConfig(m_protocolConfig.serverConfig, false);
|
||||
}
|
||||
|
||||
m_originalProtocolConfig = m_protocolConfig;
|
||||
@@ -283,7 +287,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
|
||||
}
|
||||
}
|
||||
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config)
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config, bool fillFlowDefault)
|
||||
{
|
||||
if (config.port.isEmpty()) {
|
||||
config.port = protocols::xray::defaultPort;
|
||||
@@ -306,7 +310,7 @@ void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &con
|
||||
config.security = protocols::xray::defaultSecurity;
|
||||
}
|
||||
|
||||
if (config.flow.isEmpty()) {
|
||||
if (fillFlowDefault && config.flow.isEmpty()) {
|
||||
config.flow = protocols::xray::defaultFlow;
|
||||
}
|
||||
|
||||
@@ -585,3 +589,87 @@ QString XrayConfigModel::mkcpDefaultWriteBufferSize()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool isValidSingleHost(const QString &t)
|
||||
{
|
||||
if (t.isEmpty() || t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv4Protocol) {
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
QRegExp re = NetworkUtilities::domainRegExp();
|
||||
re.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
return re.exactMatch(t);
|
||||
}
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidHost(const QString &host)
|
||||
{
|
||||
const QString t = host.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return isValidSingleHost(t);
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidSni(const QString &sni)
|
||||
{
|
||||
const QString t = sni.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.startsWith(QLatin1String("*."))) {
|
||||
return isValidSingleHost(t.mid(2));
|
||||
}
|
||||
return isValidSingleHost(t);
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidPath(const QString &path)
|
||||
{
|
||||
const QString t = path.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return t.startsWith(QLatin1Char('/'));
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::validationErrors() const
|
||||
{
|
||||
QStringList errs;
|
||||
const auto &srv = m_protocolConfig.serverConfig;
|
||||
|
||||
if (!srv.port.isEmpty()) {
|
||||
bool ok = false;
|
||||
const int p = srv.port.toInt(&ok);
|
||||
if (!ok || p < 1 || p > 65535) {
|
||||
errs << tr("Port must be in the range of 1 to 65535");
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality")) {
|
||||
if (!isValidSni(srv.sni)) {
|
||||
errs << tr("SNI: enter a valid IP address or domain name");
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.transport == QLatin1String("xhttp")) {
|
||||
if (!isValidHost(srv.xhttp.host)) {
|
||||
errs << tr("Host: enter a valid IP address or domain name");
|
||||
}
|
||||
if (!isValidPath(srv.xhttp.path)) {
|
||||
errs << tr("Path must start with \"/\"");
|
||||
}
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,11 @@ public:
|
||||
Q_INVOKABLE static QString mkcpDefaultReadBufferSize();
|
||||
Q_INVOKABLE static QString mkcpDefaultWriteBufferSize();
|
||||
|
||||
Q_INVOKABLE static bool isValidHost(const QString &host);
|
||||
Q_INVOKABLE static bool isValidSni(const QString &sni);
|
||||
Q_INVOKABLE static bool isValidPath(const QString &path);
|
||||
Q_INVOKABLE QStringList validationErrors() const;
|
||||
|
||||
public slots:
|
||||
void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig);
|
||||
amnezia::XrayProtocolConfig getProtocolConfig();
|
||||
@@ -137,7 +142,7 @@ private:
|
||||
amnezia::XrayProtocolConfig m_protocolConfig;
|
||||
amnezia::XrayProtocolConfig m_originalProtocolConfig;
|
||||
|
||||
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config);
|
||||
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config, bool fillFlowDefault = true);
|
||||
};
|
||||
|
||||
#endif // XRAYCONFIGMODEL_H
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "mtProxyConfigModel.h"
|
||||
|
||||
#include "ui/models/utils/mtproxy_public_host_input.h"
|
||||
#include "ui/utils/mtProxyPublicHostInput.h"
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
@@ -8,19 +8,14 @@
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QtGlobal>
|
||||
#include <qqml.h>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
MtProxyConfigModel::MtProxyConfigModel(QObject *parent) : QAbstractListModel(parent) {
|
||||
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
|
||||
}
|
||||
MtProxyConfigModel::MtProxyConfigModel(QObject *parent) : QAbstractListModel(parent) {}
|
||||
|
||||
int MtProxyConfigModel::rowCount(const QModelIndex &parent) const {
|
||||
Q_UNUSED(parent);
|
||||
@@ -332,7 +327,7 @@ void MtProxyConfigModel::removeAdditionalSecret(int idx) {
|
||||
QVariantList MtProxyConfigModel::additionalSecretsList() const {
|
||||
QVariantList out;
|
||||
out.reserve(m_protocolConfig.additionalSecrets.size());
|
||||
for (const auto &s: m_protocolConfig.additionalSecrets) {
|
||||
for (const auto &s : m_protocolConfig.additionalSecrets) {
|
||||
if (!s.isEmpty()) {
|
||||
out.append(s);
|
||||
}
|
||||
@@ -398,6 +393,9 @@ bool MtProxyConfigModel::isValidPublicHost(const QString &host) const {
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
if (a.isNull() || a.isLoopback() || a == QHostAddress(QHostAddress::AnyIPv6)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||
@@ -504,20 +502,12 @@ bool MtProxyConfigModel::isValidFakeTlsDomain(const QString &domain) const {
|
||||
if (!re.exactMatch(t)) {
|
||||
return false;
|
||||
}
|
||||
// ee + 32 hex (base secret) + hex(UTF-8 domain); keep headroom under typical client limits.
|
||||
if (t.toUtf8().size() > 111) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::clipboardText() const {
|
||||
if (QClipboard *c = QGuiApplication::clipboard()) {
|
||||
return c->text();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
||||
const QString t = normalizeFakeTlsDomainInput(input);
|
||||
QString out;
|
||||
@@ -578,7 +568,6 @@ QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) co
|
||||
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||
trimmed = trimmed.mid(2).trimmed();
|
||||
}
|
||||
// Prefer a contiguous 32-hex run (paste from bot message with extra text).
|
||||
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
||||
const QRegularExpressionMatch m = runHex.match(trimmed);
|
||||
if (m.hasMatch()) {
|
||||
@@ -599,18 +588,6 @@ QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) co
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeWorkersFieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 3));
|
||||
for (const QChar &c: input) {
|
||||
const ushort u = c.unicode();
|
||||
if (u >= '0' && u <= '9' && out.size() < 3) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString MtProxyConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 15));
|
||||
|
||||
@@ -128,16 +128,12 @@ public slots:
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE QString clipboardText() const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeWorkersFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#include "telemtConfigModel.h"
|
||||
|
||||
#include "ui/utils/mtProxyPublicHostInput.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
@@ -49,7 +54,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
break;
|
||||
}
|
||||
case Roles::TagRole: {
|
||||
m_protocolConfig.tag = value.toString();
|
||||
const QString tag = sanitizeMtProxyTagFieldText(value.toString());
|
||||
if (!isValidMtProxyTag(tag)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.tag = tag;
|
||||
break;
|
||||
}
|
||||
case Roles::IsEnabledRole: {
|
||||
@@ -57,7 +66,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
break;
|
||||
}
|
||||
case Roles::PublicHostRole: {
|
||||
m_protocolConfig.publicHost = value.toString();
|
||||
const QString h = value.toString().trimmed();
|
||||
if (!isValidPublicHost(h)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.publicHost = h;
|
||||
break;
|
||||
}
|
||||
case Roles::TransportModeRole: {
|
||||
@@ -65,7 +78,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
break;
|
||||
}
|
||||
case Roles::TlsDomainRole: {
|
||||
m_protocolConfig.tlsDomain = value.toString();
|
||||
const QString d = value.toString().trimmed();
|
||||
if (!isValidFakeTlsDomain(d)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.tlsDomain = d;
|
||||
break;
|
||||
}
|
||||
case Roles::AdditionalSecretsRole: {
|
||||
@@ -85,11 +102,19 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
break;
|
||||
}
|
||||
case Roles::NatInternalIpRole: {
|
||||
m_protocolConfig.natInternalIp = value.toString();
|
||||
const QString ip = value.toString().trimmed();
|
||||
if (!isValidOptionalIpv4(ip)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.natInternalIp = ip;
|
||||
break;
|
||||
}
|
||||
case Roles::NatExternalIpRole: {
|
||||
m_protocolConfig.natExternalIp = value.toString();
|
||||
const QString ip = value.toString().trimmed();
|
||||
if (!isValidOptionalIpv4(ip)) {
|
||||
return false;
|
||||
}
|
||||
m_protocolConfig.natExternalIp = ip;
|
||||
break;
|
||||
}
|
||||
case Roles::MaskEnabledRole: {
|
||||
@@ -238,7 +263,7 @@ void TelemtConfigModel::setSecret(const QString &secret) {
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::validateAndSetSecret(const QString &rawSecret) {
|
||||
if (!QRegularExpression(QStringLiteral("^[0-9a-fA-F]{32}$")).match(rawSecret).hasMatch()) {
|
||||
if (!QRegularExpression("^[0-9a-fA-F]{32}$").match(rawSecret).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
setData(index(0), rawSecret, SecretRole);
|
||||
@@ -254,7 +279,11 @@ void TelemtConfigModel::setTag(const QString &tag) {
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setPublicHost(const QString &host) {
|
||||
setData(index(0), host, PublicHostRole);
|
||||
const QString t = host.trimmed();
|
||||
if (!isValidPublicHost(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, PublicHostRole);
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setTransportMode(const QString &mode) {
|
||||
@@ -262,13 +291,15 @@ void TelemtConfigModel::setTransportMode(const QString &mode) {
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::getTransportMode() const {
|
||||
return m_protocolConfig.transportMode.isEmpty() ? QString::fromUtf8(protocols::telemt::transportModeStandard)
|
||||
: m_protocolConfig.transportMode;
|
||||
return m_protocolConfig.transportMode.isEmpty()
|
||||
? QString(protocols::telemt::transportModeStandard)
|
||||
: m_protocolConfig.transportMode;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::getTlsDomain() const {
|
||||
return m_protocolConfig.tlsDomain.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultTlsDomain)
|
||||
: m_protocolConfig.tlsDomain;
|
||||
return m_protocolConfig.tlsDomain.isEmpty()
|
||||
? QString(protocols::telemt::defaultTlsDomain)
|
||||
: m_protocolConfig.tlsDomain;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::getPublicHost() const {
|
||||
@@ -276,7 +307,11 @@ QString TelemtConfigModel::getPublicHost() const {
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setTlsDomain(const QString &domain) {
|
||||
setData(index(0), domain, TlsDomainRole);
|
||||
const QString t = domain.trimmed();
|
||||
if (!isValidFakeTlsDomain(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, TlsDomainRole);
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setWorkersMode(const QString &mode) {
|
||||
@@ -292,11 +327,19 @@ void TelemtConfigModel::setNatEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setNatInternalIp(const QString &ip) {
|
||||
setData(index(0), ip, NatInternalIpRole);
|
||||
const QString t = ip.trimmed();
|
||||
if (!isValidOptionalIpv4(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, NatInternalIpRole);
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setNatExternalIp(const QString &ip) {
|
||||
setData(index(0), ip, NatExternalIpRole);
|
||||
const QString t = ip.trimmed();
|
||||
if (!isValidOptionalIpv4(t)) {
|
||||
return;
|
||||
}
|
||||
setData(index(0), t, NatExternalIpRole);
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setMaskEnabled(bool enabled) {
|
||||
@@ -334,6 +377,17 @@ void TelemtConfigModel::removeAdditionalSecret(int idx) {
|
||||
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
|
||||
}
|
||||
|
||||
QVariantList TelemtConfigModel::additionalSecretsList() const {
|
||||
QVariantList out;
|
||||
out.reserve(m_protocolConfig.additionalSecrets.size());
|
||||
for (const auto &s : m_protocolConfig.additionalSecrets) {
|
||||
if (!s.isEmpty()) {
|
||||
out.append(s);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void TelemtConfigModel::setEnabled(bool enabled) {
|
||||
m_protocolConfig.isEnabled = enabled;
|
||||
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
|
||||
@@ -348,15 +402,15 @@ QString TelemtConfigModel::generateQrCode(const QString &text) {
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::defaultTlsDomain() const {
|
||||
return QString::fromUtf8(protocols::telemt::defaultTlsDomain);
|
||||
return protocols::telemt::defaultTlsDomain;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::defaultPort() const {
|
||||
return QString::fromUtf8(protocols::telemt::defaultPort);
|
||||
return protocols::telemt::defaultPort;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::defaultWorkers() const {
|
||||
return QString::fromUtf8(protocols::telemt::defaultWorkers);
|
||||
return protocols::telemt::defaultWorkers;
|
||||
}
|
||||
|
||||
int TelemtConfigModel::maxWorkers() const {
|
||||
@@ -364,19 +418,303 @@ int TelemtConfigModel::maxWorkers() const {
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::transportModeStandard() const {
|
||||
return QString::fromUtf8(protocols::telemt::transportModeStandard);
|
||||
return protocols::telemt::transportModeStandard;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::transportModeFakeTLS() const {
|
||||
return QString::fromUtf8(protocols::telemt::transportModeFakeTLS);
|
||||
return protocols::telemt::transportModeFakeTLS;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::workersModeAuto() const {
|
||||
return QString::fromUtf8(protocols::telemt::workersModeAuto);
|
||||
return protocols::telemt::workersModeAuto;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::workersModeManual() const {
|
||||
return QString::fromUtf8(protocols::telemt::workersModeManual);
|
||||
return protocols::telemt::workersModeManual;
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isValidPublicHost(const QString &host) const {
|
||||
const QString t = host.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv4Protocol) {
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
if (a.isNull() || a.isLoopback() || a == QHostAddress(QHostAddress::AnyIPv6)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return NetworkUtilities::domainRegExp().exactMatch(t);
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isPublicHostInputAllowed(const QString &text) const {
|
||||
return mtproxyPublicHostInputAllowed(text);
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isPublicHostTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (isValidPublicHost(t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
|
||||
if (onlyDigitDot.match(t).hasMatch()) {
|
||||
if (t.endsWith(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
const QStringList parts = t.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||
if (parts.size() < 4) {
|
||||
return true;
|
||||
}
|
||||
for (const QString &part: parts) {
|
||||
if (part.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (t.contains(QLatin1Char(':'))) {
|
||||
if (t.contains(QLatin1String(":::"))) {
|
||||
return false;
|
||||
}
|
||||
if (t.endsWith(QLatin1Char(':'))) {
|
||||
return true;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
return false;
|
||||
}
|
||||
if (!t.contains(QLatin1String("::")) && t.count(QLatin1Char(':')) < 7 && !t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isValidMtProxyTag(const QString &tag) const {
|
||||
if (tag.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression re(
|
||||
QStringLiteral("^([0-9a-fA-F]{%1})$").arg(protocols::telemt::botTagHexLength));
|
||||
return re.match(tag).hasMatch();
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isMtProxyTagTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression hexOnly(QStringLiteral(R"(^[0-9a-fA-F]*$)"));
|
||||
if (!hexOnly.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return t.size() < protocols::telemt::botTagHexLength;
|
||||
}
|
||||
|
||||
int TelemtConfigModel::mtProxyBotTagHexLength() const {
|
||||
return protocols::telemt::botTagHexLength;
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isValidFakeTlsDomain(const QString &domain) const {
|
||||
const QString t = domain.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress addr;
|
||||
if (addr.setAddress(t)) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
QRegExp re(NetworkUtilities::domainRegExp());
|
||||
re.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
if (!re.exactMatch(t)) {
|
||||
return false;
|
||||
}
|
||||
if (t.toUtf8().size() > 111) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::normalizeFakeTlsDomainInput(const QString &input) const {
|
||||
QString t = input.trimmed();
|
||||
if (t.startsWith(QLatin1String("https://"), Qt::CaseInsensitive)) {
|
||||
t = t.mid(8);
|
||||
} else if (t.startsWith(QLatin1String("http://"), Qt::CaseInsensitive)) {
|
||||
t = t.mid(7);
|
||||
}
|
||||
if (const int slash = t.indexOf(QLatin1Char('/')); slash >= 0) {
|
||||
t = t.left(slash);
|
||||
}
|
||||
if (const int at = t.indexOf(QLatin1Char('@')); at >= 0) {
|
||||
t = t.mid(at + 1);
|
||||
}
|
||||
if (const int colon = t.indexOf(QLatin1Char(':')); colon >= 0) {
|
||||
t = t.left(colon);
|
||||
}
|
||||
if (t.startsWith(QLatin1String("www."), Qt::CaseInsensitive)) {
|
||||
const QString rest = t.mid(4);
|
||||
if (rest.contains(QLatin1Char('.'))) {
|
||||
t = rest;
|
||||
}
|
||||
}
|
||||
return t.trimmed();
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isFakeTlsDomainTypingIncomplete(const QString &text) const {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (isValidFakeTlsDomain(t)) {
|
||||
return false;
|
||||
}
|
||||
if (t.contains(QLatin1Char('/')) || t.contains(QLatin1Char(':')) || t.contains(QLatin1Char('@'))
|
||||
|| t.contains(QLatin1Char(' '))) {
|
||||
return false;
|
||||
}
|
||||
if (t.contains(QLatin1String(".."))) {
|
||||
return false;
|
||||
}
|
||||
if (!t.contains(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
if (t.endsWith(QLatin1Char('.'))) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression legalPartial(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||
if (!legalPartial.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isFakeTlsDomainInputAllowed(const QString &text) const {
|
||||
if (text.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression re(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||
return re.match(text).hasMatch();
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
||||
const QString t = normalizeFakeTlsDomainInput(input);
|
||||
QString out;
|
||||
out.reserve(t.size());
|
||||
for (const QChar &c: t) {
|
||||
const ushort u = c.unicode();
|
||||
const bool letter = (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z');
|
||||
const bool digit = (u >= '0' && u <= '9');
|
||||
if (letter || digit || u == '.' || u == '-') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
if (out.size() > 253) {
|
||||
out.truncate(253);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::sanitizePublicHostFieldText(const QString &input) const {
|
||||
QString out;
|
||||
const int cap = qMin(input.size(), 253);
|
||||
out.reserve(cap);
|
||||
for (const QChar &c: input) {
|
||||
if (out.size() >= 253) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '.' || u == ':' ||
|
||||
u == '-') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::sanitizePortFieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 5));
|
||||
for (const QChar &c: input) {
|
||||
const ushort u = c.unicode();
|
||||
if (u >= '0' && u <= '9' && out.size() < 5) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::sanitizeMtProxyTagFieldText(const QString &input) const {
|
||||
QString trimmed = input.trimmed();
|
||||
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||
trimmed = trimmed.mid(2).trimmed();
|
||||
}
|
||||
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
||||
const QRegularExpressionMatch m = runHex.match(trimmed);
|
||||
if (m.hasMatch()) {
|
||||
return m.captured(1);
|
||||
}
|
||||
const int cap = protocols::telemt::botTagHexLength;
|
||||
QString out;
|
||||
out.reserve(qMin(trimmed.size(), cap));
|
||||
for (const QChar &c: trimmed) {
|
||||
if (out.size() >= cap) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'f') || (u >= 'A' && u <= 'F')) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString TelemtConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
||||
QString out;
|
||||
out.reserve(qMin(input.size(), 15));
|
||||
for (const QChar &c: input) {
|
||||
if (out.size() >= 15) {
|
||||
break;
|
||||
}
|
||||
const ushort u = c.unicode();
|
||||
if ((u >= '0' && u <= '9') || u == '.') {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool TelemtConfigModel::isValidOptionalIpv4(const QString &ip) const {
|
||||
const QString t = ip.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TelemtConfigModel::roleNames() const {
|
||||
|
||||
@@ -88,6 +88,8 @@ public slots:
|
||||
|
||||
Q_INVOKABLE void removeAdditionalSecret(int idx);
|
||||
|
||||
Q_INVOKABLE QVariantList additionalSecretsList() const;
|
||||
|
||||
Q_INVOKABLE QString generateQrCode(const QString &text);
|
||||
|
||||
Q_INVOKABLE void setEnabled(bool enabled);
|
||||
@@ -116,12 +118,44 @@ public slots:
|
||||
|
||||
Q_INVOKABLE QString workersModeManual() const;
|
||||
|
||||
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
|
||||
|
||||
Q_INVOKABLE bool isPublicHostInputAllowed(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isPublicHostTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isValidMtProxyTag(const QString &tag) const;
|
||||
|
||||
Q_INVOKABLE bool isMtProxyTagTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE int mtProxyBotTagHexLength() const;
|
||||
|
||||
Q_INVOKABLE bool isValidFakeTlsDomain(const QString &domain) const;
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeFakeTlsDomainFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
||||
|
||||
Q_INVOKABLE bool isValidOptionalIpv4(const QString &ip) const;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
static void applyDefaults(amnezia::TelemtProtocolConfig &c);
|
||||
|
||||
QString normalizeFakeTlsDomainInput(const QString &input) const;
|
||||
|
||||
amnezia::DockerContainer m_container = amnezia::DockerContainer::None;
|
||||
QJsonObject m_fullConfig;
|
||||
amnezia::TelemtProtocolConfig m_protocolConfig;
|
||||
|
||||
@@ -56,14 +56,17 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
@@ -6,8 +6,36 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -31,11 +59,4 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user