diff --git a/.gitmodules b/.gitmodules index ae336344..5ef4a9b1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "client/3rd/SortFilterProxyModel"] path = client/3rd/SortFilterProxyModel url = https://github.com/mitchcurtis/SortFilterProxyModel.git +[submodule "client/3rd/mbedtls"] + path = client/3rd/mbedtls + url = https://github.com/Mbed-TLS/mbedtls.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e8dbd7c7..f1e5d37c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,10 @@ if(ANDROID) set(QT_ANDROID_BUILD_ALL_ABIS ON) endif() +if(APPLE AND NOT IOS) + set(CMAKE_OSX_ARCHITECTURES "x86_64") +endif() + add_subdirectory(client) if(NOT IOS AND NOT ANDROID) diff --git a/README.md b/README.md index 6797cd9a..c183de36 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,31 @@ Build might fail with "source files not found" error the first time you try it, dependencies in parallel, and some dependencies end up being built after the ones that require them. In this case simply restart the build. +## How to build the Android app +_tested on Mac OS_ + +The Android app has the following requirements: +* JDK 11 +* Android platform SDK 33 +* cmake 3.25.0 + +After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`. + * set path to jdk 11 + * set path to Android SDK ($ANDROID_HOME) + +In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine! + +Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at ` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake. + +Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platfrom SDK`. + +That's it you should be ready to compile the project from QT Creator! + +### Development flow +After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt__Clang_-`. +If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt__Clang_-/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated proejct, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes! + +You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the geneated project's root directory (`/client/android-build/.`) and you should be good to continue. ## License GPL v.3 diff --git a/client/3rd/mbedtls b/client/3rd/mbedtls new file mode 160000 index 00000000..8c892249 --- /dev/null +++ b/client/3rd/mbedtls @@ -0,0 +1 @@ +Subproject commit 8c89224991adff88d53cd380f42a2baa36f91454 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 55c0deda..1085d4dc 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -65,6 +65,7 @@ include_directories( ) set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/migrations.h ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h @@ -92,6 +93,7 @@ if(NOT IOS) endif() set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/migrations.cpp ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp @@ -481,7 +483,6 @@ if(IOS) set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual) set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension") set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension") - endif() if(ANDROID) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 588854d3..891c305a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -68,8 +68,7 @@ #endif m_settings = std::shared_ptr(new Settings); - m_serverController = std::shared_ptr(new ServerController(m_settings, this)); - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, m_serverController, this)); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); } AmneziaApplication::~AmneziaApplication() @@ -90,7 +89,7 @@ AmneziaApplication::~AmneziaApplication() void AmneziaApplication::init() { m_engine = new QQmlApplicationEngine; - m_uiLogic = new UiLogic(m_settings, m_configurator, m_serverController); + m_uiLogic = new UiLogic(m_settings, m_configurator); const QUrl url(QStringLiteral("qrc:/ui/qml/main.qml")); QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, diff --git a/client/amnezia_application.h b/client/amnezia_application.h index f0f8f529..1ac6e772 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -49,7 +49,6 @@ private: UiLogic *m_uiLogic {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; - std::shared_ptr m_serverController; ContainerProps* m_containerProps {}; ProtocolProps* m_protocolProps {}; diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 22c828fb..be67c8ba 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -51,50 +51,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/android/shadowsocks/build.gradle b/client/android/shadowsocks/build.gradle index 69ebd0bd..f1d738b2 100644 --- a/client/android/shadowsocks/build.gradle +++ b/client/android/shadowsocks/build.gradle @@ -35,7 +35,7 @@ androidExtensions { } //def lifecycleVersion = '2.0.0' -//def roomVersion = '2.0.0' +def roomVersion = "2.4.3" //def preferencexVersion = '1.0.0' dependencies { @@ -45,13 +45,12 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" - implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" - implementation "androidx.room:room-runtime:2.2.5" // runtime + implementation "androidx.room:room-runtime:$roomVersion" // runtime implementation "androidx.preference:preference:1.1.0" implementation "androidx.work:work-runtime-ktx:2.7.1" implementation "androidx.browser:browser:1.3.0-alpha01" @@ -65,6 +64,7 @@ dependencies { // api "com.takisoft.preferencex:preferencex:1.0.0" implementation 'com.takisoft.preferencex:preferencex:1.1.0' api 'org.connectbot.jsocks:jsocks:1.0.0' - kapt "androidx.room:room-compiler:2.2.5" + + kapt "androidx.room:room-compiler:$roomVersion" kapt "androidx.lifecycle:lifecycle-compiler:2.4.0" } diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 6ca99e87..412b1e45 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -287,6 +287,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { } } set(value) { + field = value + if (value) { mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "") mConnectionTime = System.currentTimeMillis() @@ -886,45 +888,4 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { class CloseableFd(val fd: FileDescriptor) : Closeable { override fun close() = Os.close(fd) } - - fun saveAsFile(configContent: String?, suggestedFileName: String): String { - val rootDirPath = cacheDir.absolutePath - val rootDir = File(rootDirPath) - - if (!rootDir.exists()) { - rootDir.mkdirs() - } - - val fileName = if (!TextUtils.isEmpty(suggestedFileName)) suggestedFileName else "amnezia.cfg" - - val file = File(rootDir, fileName) - - try { - file.bufferedWriter().use { out -> out.write(configContent) } - return file.toString() - } catch (e: Exception) { - e.printStackTrace() - } - - return "" - } - - fun shareFile(attachmentFile: String?) { - try { - val intent = Intent(Intent.ACTION_SEND) - intent.type = "text/*" - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - - val file = File(attachmentFile) - val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", file) - intent.putExtra(Intent.EXTRA_STREAM, uri) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - val createChooser = Intent.createChooser(intent, "Config sharing") - createChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(createChooser) - } catch (e: Exception) { - Log.i(tag, e.message.toString()) - } - } } diff --git a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt index 239269a5..bc44217e 100644 --- a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt +++ b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt @@ -32,7 +32,6 @@ class VPNServiceBinder(service: VPNService) : Binder() { const val resumeActivate = 7 const val setNotificationText = 8 const val setFallBackNotification = 9 - const val shareConfig = 10 const val importConfig = 11 } @@ -139,20 +138,6 @@ class VPNServiceBinder(service: VPNService) : Binder() { return true } - ACTIONS.shareConfig -> { - val byteArray = data.createByteArray() - val json = byteArray?.let { String(it) } - val config = JSONObject(json) - val configContent = config.getString("data") - val suggestedName = config.getString("suggestedName") - - val filePath = mService.saveAsFile(configContent, suggestedName) - Log.i(tag, "save file: $filePath") - - mService.shareFile(filePath) - return true - } - ACTIONS.importConfig -> { val buffer = data.readString() @@ -196,7 +181,6 @@ class VPNServiceBinder(service: VPNService) : Binder() { try { mListener?.let { if (it.isBinderAlive) { - Log.i(tag, "Dispatching event: binder alive") val data = Parcel.obtain() data.writeByteArray(payload?.toByteArray(charset("UTF-8"))) it.transact(code, data, Parcel.obtain(), 0) diff --git a/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt index 8416d97c..8733efa5 100644 --- a/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt +++ b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt @@ -2,11 +2,11 @@ package org.amnezia.vpn.qt import android.Manifest import android.annotation.SuppressLint -import android.app.Activity -import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.util.Log +import android.view.MotionEvent +import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.* @@ -17,9 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.common.InputImage +import org.amnezia.vpn.R import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import org.amnezia.vpn.R class CameraActivity : AppCompatActivity() { @@ -78,7 +78,7 @@ class CameraActivity : AppCompatActivity() { } } - @SuppressLint("UnsafeOptInUsageError") + @SuppressLint("UnsafeOptInUsageError", "ClickableViewAccessibility") private fun configureVideoPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) val imageCapture = ImageCapture.Builder().build() @@ -101,7 +101,20 @@ class CameraActivity : AppCompatActivity() { try { preview.setSurfaceProvider(viewFinder.surfaceProvider) cameraProvider.unbindAll() - cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase) + val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase) + viewFinder.setOnTouchListener(View.OnTouchListener { view: View, motionEvent: MotionEvent -> + when (motionEvent.action) { + MotionEvent.ACTION_DOWN -> return@OnTouchListener true + MotionEvent.ACTION_UP -> { + val factory = viewFinder.meteringPointFactory + val point = factory.createPoint(motionEvent.x, motionEvent.y) + val action = FocusMeteringAction.Builder(point).build() + camera.cameraControl.startFocusAndMetering(action) + return@OnTouchListener true + } + else -> return@OnTouchListener false + } + }) } catch(exc: Exception) { Log.e("WUTT", "Use case binding failed", exc) } @@ -146,7 +159,6 @@ class CameraActivity : AppCompatActivity() { } } } - imageProxy.close() } .addOnFailureListener { diff --git a/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt new file mode 100644 index 00000000..de175a98 --- /dev/null +++ b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt @@ -0,0 +1,140 @@ +package org.amnezia.vpn.qt + +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager +import android.content.ContentResolver +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +import java.io.* + +import org.amnezia.vpn.R + + +const val INTENT_ACTION_IMPORT_CONFIG = "org.amnezia.vpn.qt.IMPORT_CONFIG" + +class ImportConfigActivity : Activity() { + + private val STORAGE_PERMISSION_CODE = 42 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_import_config) + startReadConfig(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + startReadConfig(intent) + } + + private fun startMainActivity(config: String?) { + + if (config == null || config.length == 0) { + return + } + + val activityIntent = Intent(applicationContext, VPNActivity::class.java) + activityIntent.action = INTENT_ACTION_IMPORT_CONFIG + activityIntent.addCategory("android.intent.category.DEFAULT") + activityIntent.putExtra("CONFIG", config) + + startActivity(activityIntent) + finish() + } + + private fun startReadConfig(intent: Intent?) { + val newIntent = intent + val newIntentAction: String = newIntent?.action ?: "" + + if (newIntent != null && newIntentAction == Intent.ACTION_VIEW) { + readConfig(newIntent, newIntentAction) + } + } + + private fun readConfig(newIntent: Intent, newIntentAction: String) { + if (isReadStorageAllowed()) { + val configString = processIntent(newIntent, newIntentAction) + startMainActivity(configString) + } else { + requestStoragePermission() + } + } + + private fun requestStoragePermission() { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (requestCode == STORAGE_PERMISSION_CODE) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + val configString = processIntent(intent, intent.action!!) + + if (configString != null) { + startMainActivity(configString) + } + } else { + Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() + } + } + } + + private fun processIntent(intent: Intent, action: String): String? { + val scheme = intent.scheme + + if (scheme == null) { + return null + } + + if (action.compareTo(Intent.ACTION_VIEW) == 0) { + val resolver = contentResolver + + if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { + val uri = intent.data + val name: String? = getContentName(resolver, uri) + + println("Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) + + val input = resolver.openInputStream(uri!!) + + return input?.bufferedReader()?.use(BufferedReader::readText) + } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { + val uri = intent.data + val name = uri!!.lastPathSegment + + println("File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) + + val input = resolver.openInputStream(uri) + + return input?.bufferedReader()?.use(BufferedReader::readText) + } + } + + return null + } + + private fun isReadStorageAllowed(): Boolean { + val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + return permissionStatus == PackageManager.PERMISSION_GRANTED + } + + private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? { + val cursor = resolver!!.query(uri!!, null, null, null, null) + + cursor.use { + cursor!!.moveToFirst() + val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) + return if (nameIndex >= 0) { + return cursor.getString(nameIndex) + } else { + null + } + } + } +} \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt index b5e8d5fb..d2b5b7ab 100644 --- a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt +++ b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt @@ -5,6 +5,8 @@ package org.amnezia.vpn.qt; import android.Manifest +import android.content.ClipData +import android.content.ClipboardManager import android.content.ComponentName import android.content.ContentResolver import android.content.Context @@ -32,11 +34,22 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { private var configString: String? = null private var vpnServiceBinder: IBinder? = null private var isBound = false + set(value) { + field = value + + if (value && configString != null) { + sendImportConfigCommand() + } + } private val TAG = "VPNActivity" - private val STORAGE_PERMISSION_CODE = 42 private val CAMERA_ACTION_CODE = 101 + private val CREATE_FILE_ACTION_CODE = 102 + + private var tmpFileContentToSave: String = "" + + private val delayedCommands: ArrayList> = ArrayList() companion object { private lateinit var instance: VPNActivity @@ -56,19 +69,27 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { @JvmStatic fun sendToService(actionCode: Int, body: String) { VPNActivity.getInstance().dispatchParcel(actionCode, body) } + + @JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) { + VPNActivity.getInstance().saveFile(fileContent, suggestedName) + } + + @JvmStatic fun putTextToClipboard(text: String) { + VPNActivity.getInstance().putToClipboard(text) + } } override fun onCreate(savedInstanceState: Bundle?) { - val newIntent = intent - val newIntentAction = newIntent.action - - if (newIntent != null && newIntentAction != null) { - configString = processIntent(newIntent, newIntentAction) - } - super.onCreate(savedInstanceState) instance = this + + val newIntent = intent + val newIntentAction: String? = newIntent.action + + if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.qt.IMPORT_CONFIG") { + configString = newIntent.getStringExtra("CONFIG") + } } private fun startQrCodeActivity() { @@ -76,6 +97,18 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { startActivityForResult(intent, CAMERA_ACTION_CODE) } + private fun saveFile(fileContent: String, suggestedName: String) { + tmpFileContentToSave = fileContent + + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "text/*" + putExtra(Intent.EXTRA_TITLE, suggestedName) + } + + startActivityForResult(intent, CREATE_FILE_ACTION_CODE) + } + override fun getSystemService(name: String): Any? { return if (Build.VERSION.SDK_INT >= 29 && name == "clipboard") { // QT will always attempt to read the clipboard if content is there. @@ -98,11 +131,22 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { private fun dispatchParcel(actionCode: Int, body: String) { if (!isBound) { Log.d(TAG, "dispatchParcel: not bound") + delayedCommands.add(Pair(actionCode, body)) return - } else { - Log.d(TAG, "dispatchParcel: bound") } + if (delayedCommands.size > 0) { + for (command in delayedCommands) { + processCommand(command.first, command.second) + } + + delayedCommands.clear() + } + + processCommand(actionCode, body) + } + + private fun processCommand(actionCode: Int, body: String) { val out: Parcel = Parcel.obtain() out.writeByteArray(body.toByteArray()) @@ -118,19 +162,15 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { } override fun onNewIntent(newIntent: Intent) { - intent = newIntent + super.onNewIntent(intent) + + setIntent(newIntent) val newIntentAction = newIntent.action - if (newIntent != null && newIntentAction != null && newIntentAction != Intent.ACTION_MAIN) { - if (isReadStorageAllowed()) { - configString = processIntent(newIntent, newIntentAction) - } else { - requestStoragePermission() - } - } - - super.onNewIntent(intent) + if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) { + configString = newIntent.getStringExtra("CONFIG") + } } override fun onResume() { @@ -141,84 +181,6 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { } } - private fun isReadStorageAllowed(): Boolean { - val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - return permissionStatus == PackageManager.PERMISSION_GRANTED - } - - private fun requestStoragePermission() { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (requestCode == STORAGE_PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Storage read permission granted") - - if (configString == null) { - configString = processIntent(intent, intent.action!!) - } - - if (configString != null) { - Log.d(TAG, "not empty") - sendImportConfigCommand() - } else { - Log.d(TAG, "empty") - } - } else { - Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() - } - } - } - - private fun processIntent(intent: Intent, action: String): String? { - val scheme = intent.scheme - - if (scheme == null) { - return null - } - - if (action.compareTo(Intent.ACTION_VIEW) == 0) { - val resolver = contentResolver - - if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { - val uri = intent.data - val name: String? = getContentName(resolver, uri) - - Log.d(TAG, "Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri!!) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { - val uri = intent.data - val name = uri!!.lastPathSegment - - Log.d(TAG, "File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } - } - - return null - } - - private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? { - val cursor = resolver!!.query(uri!!, null, null, null, null) - - cursor.use { - cursor!!.moveToFirst() - val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) - return if (nameIndex >= 0) { - return cursor.getString(nameIndex) - } else { - null - } - } - } - private fun sendImportConfigCommand() { if (configString != null) { val msg: Parcel = Parcel.obtain() @@ -234,7 +196,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { } } - private var connection: ServiceConnection = object : ServiceConnection { + private fun createConnection() = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, binder: IBinder) { vpnServiceBinder = binder @@ -260,6 +222,8 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { } } + private var connection: ServiceConnection = createConnection() + private fun registerBinder(): Boolean { val binder = VPNClientBinder() val out: Parcel = Parcel.obtain() @@ -330,13 +294,38 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { val extra = data?.getStringExtra("result") ?: "" onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra) } + + if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) { + data?.data?.also { uri -> + alterDocument(uri) + } + } } - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { - if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) { - onBackPressed() - return true + private fun alterDocument(uri: Uri) { + try { + applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd -> + FileOutputStream(fd.fileDescriptor).use { fos -> + fos.write(tmpFileContentToSave.toByteArray()) + } + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + + tmpFileContentToSave = "" + } + + private fun putToClipboard(text: String) { + this.runOnUiThread { + val clipboard = applicationContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager? + + if (clipboard != null) { + val clip: ClipData = ClipData.newPlainText("", text) + clipboard.setPrimaryClip(clip) + } } - return super.onKeyDown(keyCode, event) } } diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake index 99aee113..44120cf5 100644 --- a/client/cmake/3rdparty.cmake +++ b/client/cmake/3rdparty.cmake @@ -1,5 +1,7 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}") + if(NOT IOS AND NOT ANDROID) include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake) endif() @@ -21,52 +23,76 @@ set(ZLIB_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/zlib" "${CMAKE_CURRENT_BINARY_DIR}/ link_directories(${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib) link_libraries(${ZLIB_LIBRARY}) -if(NOT LINUX) - set(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/3rd/OpenSSL") - set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/include") - set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib") - set(OPENSSL_LIBRARIES "ssl" "crypto") +if(IOS) + set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE) + set(ENABLE_TESTING OFF CACHE BOOL "" FORCE) + add_subdirectory(${CLIENT_ROOT_DIR}/3rd/mbedtls) + set(WITH_MBEDTLS ON CACHE BOOL "" FORCE) + set(WITH_GCRYPT OFF CACHE BOOL "" FORCE) + set(WITH_EXAMPLES OFF CACHE BOOL "" FORCE) + set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE) + set(ENABLE_TESTING OFF CACHE BOOL "" FORCE) + set(HAVE_LIBCRYPTO OFF CACHE BOOL "" FORCE) + set(MBEDTLS_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/3rd/mbedtls" CACHE PATH "" FORCE) + set(MBEDTLS_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/mbedtls/include" CACHE PATH "" FORCE) + set(MBEDTLS_LIBRARIES "mbedtls" "mbedx509" "mbedcrypto" CACHE STRING "" FORCE) + set(MBEDTLS_FOUND TRUE CACHE BOOL "" FORCE) + set(MBEDTLS_CRYPTO_LIBRARY "mbedcrypto" CACHE STRING "" FORCE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBEDTLS_ALLOW_PRIVATE_ACCESS") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMBEDTLS_ALLOW_PRIVATE_ACCESS") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(WITH_STATIC_LIB ON CACHE BOOL "" FORCE) + set(WITH_SYMBOL_VERSIONING OFF CACHE BOOL "" FORCE) - set(OPENSSL_PATH "${CLIENT_ROOT_DIR}/3rd/OpenSSL") - if(WIN32) - if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") - set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/windows/x86_64/libssl.lib") - set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/windows/x86_64/libcrypto.lib") - else() - set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/windows/x86/libssl.lib") - set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/windows/x86/libcrypto.lib") + include_directories(${CLIENT_ROOT_DIR}/3rd/mbedtls/include) +else(IOS) + if(NOT LINUX) + set(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/3rd/OpenSSL") + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/include") + set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib") + set(OPENSSL_LIBRARIES "ssl" "crypto") + + set(OPENSSL_PATH "${CLIENT_ROOT_DIR}/3rd/OpenSSL") + if(WIN32) + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/windows/x86_64/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/windows/x86_64/libcrypto.lib") + else() + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/windows/x86/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/windows/x86/libcrypto.lib") + endif() + elseif(APPLE AND NOT IOS) + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/macos/x86_64/libssl.a") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/macos/x86_64/libcrypto.a") + elseif(IOS) + set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_LIBRARIES_DIR}/libcrypto.a") + set(OPENSSL_SSL_LIBRARY "${OPENSSL_LIBRARIES_DIR}/libssl.a") + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/ios/iphone/libssl.a") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/ios/iphone/libcrypto.a") + elseif(ANDROID) + set(abi ${CMAKE_ANDROID_ARCH_ABI}) + + set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_LIBRARIES_DIR}/android/${abi}/libcrypto.a") + set(OPENSSL_SSL_LIBRARY "${OPENSSL_LIBRARIES_DIR}/android/${abi}/libssl.a") + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/android/${abi}/libssl.a") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/android/${abi}/libcrypto.a") + + set(OPENSSL_LIBRARIES_DIR "${OPENSSL_LIBRARIES_DIR}/android/${abi}") endif() - elseif(APPLE AND NOT IOS) - set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/macos/x86_64/libssl.a") - set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/macos/x86_64/libcrypto.a") - elseif(IOS) - set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_LIBRARIES_DIR}/libcrypto.a") - set(OPENSSL_SSL_LIBRARY "${OPENSSL_LIBRARIES_DIR}/libssl.a") - set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/ios/iphone/libssl.a") - set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/ios/iphone/libcrypto.a") - elseif(ANDROID) - set(abi ${CMAKE_ANDROID_ARCH_ABI}) - set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_LIBRARIES_DIR}/android/${abi}/libcrypto.a") - set(OPENSSL_SSL_LIBRARY "${OPENSSL_LIBRARIES_DIR}/android/${abi}/libssl.a") - set(OPENSSL_LIB_SSL_PATH "${OPENSSL_PATH}/lib/android/${abi}/libssl.a") - set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_PATH}/lib/android/${abi}/libcrypto.a") - - set(OPENSSL_LIBRARIES_DIR "${OPENSSL_LIBRARIES_DIR}/android/${abi}") + file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH} + DESTINATION ${OPENSSL_LIBRARIES_DIR}) + file(COPY "${OPENSSL_PATH}/include" + DESTINATION ${OPENSSL_ROOT_DIR}) endif() - file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH} - DESTINATION ${OPENSSL_LIBRARIES_DIR}) - file(COPY "${OPENSSL_PATH}/include" - DESTINATION ${OPENSSL_ROOT_DIR}) -endif() - -set(OPENSSL_USE_STATIC_LIBS TRUE) -find_package(OpenSSL REQUIRED) -set(LIBS ${LIBS} - OpenSSL::Crypto - OpenSSL::SSL -) + set(OPENSSL_USE_STATIC_LIBS TRUE) + find_package(OpenSSL REQUIRED) + set(LIBS ${LIBS} + OpenSSL::Crypto + OpenSSL::SSL + ) +endif(IOS) set(WITH_GSSAPI OFF CACHE BOOL "" FORCE) set(WITH_EXAMPLES OFF CACHE BOOL "" FORCE) @@ -74,7 +100,7 @@ add_subdirectory(${CLIENT_ROOT_DIR}/3rd/libssh) add_compile_definitions(_WINSOCKAPI_) set(LIBS ${LIBS} ssh) -# set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_WITH_QT6 ON) add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain) set(LIBS ${LIBS} qt6keychain) diff --git a/client/cmake/Modules/FindMbedTLS.cmake b/client/cmake/Modules/FindMbedTLS.cmake new file mode 100644 index 00000000..e1c58f46 --- /dev/null +++ b/client/cmake/Modules/FindMbedTLS.cmake @@ -0,0 +1,25 @@ + +set(MBEDTLS_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/3rd/mbedtls" CACHE PATH "" FORCE) +set(MBEDTLS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rd/mbedtls/include" CACHE PATH "" FORCE) +set(MBEDTLS_LIBRARIES "mbedtls" "mbedx509" "mbedcrypto" CACHE STRING "" FORCE) +set(MBEDTLS_FOUND TRUE CACHE BOOL "" FORCE) +set(MBEDTLS_CRYPTO_LIBRARY "mbedcrypto" CACHE STRING "" FORCE) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBEDTLS_ALLOW_PRIVATE_ACCESS -DMBEDTLS_THREADING_C -DMBEDTLS_THREADING_PTHREAD") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMBEDTLS_ALLOW_PRIVATE_ACCESS -DMBEDTLS_THREADING_C -DMBEDTLS_THREADING_PTHREAD") +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(WITH_STATIC_LIB ON CACHE BOOL "" FORCE) + +include_directories(${MBEDTLS_INCLUDE_DIR}) + +# show the MBEDTLS_INCLUDE_DIRS and MBEDTLS_LIBRARIES variables only in the advanced view +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) + +install(TARGETS mbedtls mbedx509 mbedcrypto + EXPORT mbedtls-config + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries) + +install(EXPORT mbedtls-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp index 0cfd74fc..6206d141 100644 --- a/client/configurators/cloak_configurator.cpp +++ b/client/configurators/cloak_configurator.cpp @@ -7,8 +7,8 @@ #include "core/servercontroller.h" #include "containers/containers_defs.h" -CloakConfigurator::CloakConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +CloakConfigurator::CloakConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } @@ -17,12 +17,13 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { ErrorCode e = ErrorCode::NoError; + ServerController serverController(m_settings); - QString cloakPublicKey = m_serverController->getTextFileFromContainer(container, credentials, + QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, &e); cloakPublicKey.replace("\n", ""); - QString cloakBypassUid = m_serverController->getTextFileFromContainer(container, credentials, + QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, &e); cloakBypassUid.replace("\n", ""); @@ -47,8 +48,8 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, config.insert(config_key::remote, credentials.hostName); config.insert(config_key::port, "$CLOAK_SERVER_PORT"); - QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(), + serverController.genVarsForScript(credentials, container, containerConfig)); // qDebug().noquote() << textCfg; return textCfg; diff --git a/client/configurators/cloak_configurator.h b/client/configurators/cloak_configurator.h index c6184805..cf6cd002 100644 --- a/client/configurators/cloak_configurator.h +++ b/client/configurators/cloak_configurator.h @@ -11,8 +11,7 @@ class CloakConfigurator : ConfiguratorBase { Q_OBJECT public: - CloakConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + CloakConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); diff --git a/client/configurators/configurator_base.cpp b/client/configurators/configurator_base.cpp index 44377401..c24d7e1f 100644 --- a/client/configurators/configurator_base.cpp +++ b/client/configurators/configurator_base.cpp @@ -1,10 +1,8 @@ #include "configurator_base.h" -ConfiguratorBase::ConfiguratorBase(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent) +ConfiguratorBase::ConfiguratorBase(std::shared_ptr settings, QObject *parent) : QObject{parent}, - m_settings(settings), - m_serverController(serverController) + m_settings(settings) { } diff --git a/client/configurators/configurator_base.h b/client/configurators/configurator_base.h index 8c0614d9..da43a2bc 100644 --- a/client/configurators/configurator_base.h +++ b/client/configurators/configurator_base.h @@ -4,7 +4,6 @@ #include class Settings; -class ServerController; #include "containers/containers_defs.h" #include "core/defs.h" @@ -13,13 +12,10 @@ class ConfiguratorBase : public QObject { Q_OBJECT public: - explicit ConfiguratorBase(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + explicit ConfiguratorBase(std::shared_ptr settings, QObject *parent = nullptr); protected: std::shared_ptr m_settings; - std::shared_ptr m_serverController; - }; #endif // CONFIGURATORBASE_H diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 9b702824..7ed83da1 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -15,8 +15,8 @@ #include "core/servercontroller.h" -Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } @@ -41,16 +41,17 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") .arg(connData.clientId); - ErrorCode e = m_serverController->runContainerScript(credentials, container, scriptCreateCert); + ServerController serverController(m_settings); + ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") .arg(connData.password) .arg(connData.clientId) .arg(certFileName); - e = m_serverController->runContainerScript(credentials, container, scriptExportCert); + e = serverController.runContainerScript(credentials, container, scriptExportCert); - connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, &e); - connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); + connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); + connData.caCert = serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); diff --git a/client/configurators/ikev2_configurator.h b/client/configurators/ikev2_configurator.h index 35c03b19..a7d379f2 100644 --- a/client/configurators/ikev2_configurator.h +++ b/client/configurators/ikev2_configurator.h @@ -11,8 +11,7 @@ class Ikev2Configurator : ConfiguratorBase { Q_OBJECT public: - Ikev2Configurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + Ikev2Configurator(std::shared_ptr settings, QObject *parent = nullptr); struct ConnectionData { QByteArray clientCert; // p12 client cert diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 0ef6da0a..456aeb4d 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -19,8 +19,8 @@ #include #include -OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } @@ -40,7 +40,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co arg(amnezia::protocols::openvpn::clientsDirPath). arg(connData.clientId); - ErrorCode e = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName); + ServerController serverController(m_settings); + ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { if (errorCode) *errorCode = e; return connData; @@ -52,8 +53,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co return connData; } - connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); - connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, + connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); + connData.clientCert = serverController.getTextFileFromContainer(container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); if (e) { @@ -61,7 +62,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co return connData; } - connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); + connData.taKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { if (errorCode) *errorCode = ErrorCode::SshSftpFailureError; @@ -73,8 +74,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + ServerController serverController(m_settings); + QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { @@ -163,10 +165,11 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, .arg(ContainerProps::containerToString(container)) .arg(clientId); + ServerController serverController(m_settings); QStringList scriptList {script_import, script_sign}; - QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container)); + QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container)); - return m_serverController->runScript(credentials, script); + return serverController.runScript(credentials, script); } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() diff --git a/client/configurators/openvpn_configurator.h b/client/configurators/openvpn_configurator.h index 25230499..3b84e0a0 100644 --- a/client/configurators/openvpn_configurator.h +++ b/client/configurators/openvpn_configurator.h @@ -11,8 +11,7 @@ class OpenVpnConfigurator : ConfiguratorBase { Q_OBJECT public: - OpenVpnConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + OpenVpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); struct ConnectionData { QString clientId; diff --git a/client/configurators/shadowsocks_configurator.cpp b/client/configurators/shadowsocks_configurator.cpp index 97503ac4..a71064c8 100644 --- a/client/configurators/shadowsocks_configurator.cpp +++ b/client/configurators/shadowsocks_configurator.cpp @@ -7,8 +7,8 @@ #include "containers/containers_defs.h" #include "core/servercontroller.h" -ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } @@ -17,9 +17,10 @@ QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &c DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { ErrorCode e = ErrorCode::NoError; + ServerController serverController(m_settings); - QString ssKey = m_serverController->getTextFileFromContainer(container, credentials, - amnezia::protocols::shadowsocks::ssKeyPath, &e); + QString ssKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::shadowsocks::ssKeyPath, &e); ssKey.replace("\n", ""); if (e) { @@ -36,8 +37,8 @@ QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &c config.insert("method", "$SHADOWSOCKS_CIPHER"); - QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(), + serverController.genVarsForScript(credentials, container, containerConfig)); //qDebug().noquote() << textCfg; return textCfg; diff --git a/client/configurators/shadowsocks_configurator.h b/client/configurators/shadowsocks_configurator.h index be80f169..b03149eb 100644 --- a/client/configurators/shadowsocks_configurator.h +++ b/client/configurators/shadowsocks_configurator.h @@ -10,8 +10,7 @@ class ShadowSocksConfigurator : ConfiguratorBase { Q_OBJECT public: - ShadowSocksConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + ShadowSocksConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index 725f6ead..e1435bc3 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -15,8 +15,8 @@ #include "utilities.h" -SshConfigurator::SshConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } diff --git a/client/configurators/ssh_configurator.h b/client/configurators/ssh_configurator.h index d7a177c3..2f8cb550 100644 --- a/client/configurators/ssh_configurator.h +++ b/client/configurators/ssh_configurator.h @@ -11,8 +11,7 @@ class SshConfigurator : ConfiguratorBase { Q_OBJECT public: - SshConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + SshConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QProcessEnvironment prepareEnv(); QString convertOpenSShKey(const QString &key); diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 1433eeaa..ceb6a5a4 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -14,16 +14,15 @@ #include "utilities.h" #include "settings.h" -VpnConfigurator::VpnConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { - openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, serverController, this)); - shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, serverController, this)); - cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, serverController, this)); - wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, serverController, this)); - ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, serverController, this)); - sshConfigurator = std::shared_ptr(new SshConfigurator(settings, serverController, this)); + openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); + shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); + cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, this)); + wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, this)); + ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); + sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); } QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 47bdc1fd..5d0fcfee 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -19,8 +19,7 @@ class VpnConfigurator : ConfiguratorBase { Q_OBJECT public: - VpnConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr); @@ -35,8 +34,6 @@ public: void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, const QString &stdOut); - std::shared_ptr m_serverController; - std::shared_ptr openVpnConfigurator; std::shared_ptr shadowSocksConfigurator; std::shared_ptr cloakConfigurator; diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 4c22dc62..f7ab1657 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -21,8 +21,8 @@ #include "core/servercontroller.h" #include "settings.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): - ConfiguratorBase(settings, serverController, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent): + ConfiguratorBase(settings, parent) { } @@ -70,6 +70,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } ErrorCode e = ErrorCode::NoError; + ServerController serverController(m_settings); // Get list of already created clients (only IP addreses) QString nextIpNumber; @@ -81,7 +82,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return ErrorCode::NoError; }; - e = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut); + e = serverController.runContainerScript(credentials, container, script, cbReadStdOut); if (errorCode && e) { *errorCode = e; return connData; @@ -119,14 +120,14 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { if (errorCode) *errorCode = e; return connData; } - connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { @@ -144,7 +145,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon arg(connData.pskKey). arg(connData.clientIP); - e = m_serverController->uploadTextFileToContainer(container, credentials, configPart, + e = serverController.uploadTextFileToContainer(container, credentials, configPart, protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { @@ -152,9 +153,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } - e = m_serverController->runScript(credentials, - m_serverController->replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", - m_serverController->genVarsForScript(credentials, container))); + e = serverController.runScript(credentials, + serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", + serverController.genVarsForScript(credentials, container))); return connData; } @@ -162,8 +163,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + ServerController serverController(m_settings); + QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 76790d74..ab903d29 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -11,8 +11,7 @@ class WireguardConfigurator : ConfiguratorBase { Q_OBJECT public: - WireguardConfigurator(std::shared_ptr settings, - std::shared_ptr serverController, QObject *parent = nullptr); + WireguardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); struct ConnectionData { QString clientPrivKey; // client private key diff --git a/client/core/defs.h b/client/core/defs.h index 875d9a0a..452038a5 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -36,6 +36,7 @@ enum ErrorCode // Ssh connection errors SshRequsetDeniedError, SshInterruptedError, SshInternalError, + SshPrivateKeyError, SshPrivateKeyFormatError, // Ssh sftp errors SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index c40b73b7..17b40b09 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -22,6 +22,8 @@ QString errorString(ErrorCode code){ case(SshRequsetDeniedError): return QObject::tr("Ssh request was denied"); case(SshInterruptedError): return QObject::tr("Ssh request was interrupted"); case(SshInternalError): return QObject::tr("Ssh internal error"); + case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered"); + case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); // Libssh sftp errors case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered"); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index b3da3ce1..74ebf1b0 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -39,6 +39,7 @@ ServerController::ServerController(std::shared_ptr settings, QObject * ServerController::~ServerController() { + m_sshClient.disconnectFromHost(); } @@ -92,7 +93,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr return ErrorCode::NoError; } - ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, const std::function &cbReadStdOut, @@ -203,21 +203,6 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, return QByteArray::fromHex(stdOut.toUtf8()); } -ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials) -{ - QString caCert = ServerController::getTextFileFromContainer(container, - credentials, protocols::openvpn::caCertPath); - QString taKey = ServerController::getTextFileFromContainer(container, - credentials, protocols::openvpn::taKeyPath); - - if (!caCert.isEmpty() && !taKey.isEmpty()) { - return ErrorCode::NoError; - } - else { - return ErrorCode::ServerCheckFailed; - } -} - ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { @@ -260,8 +245,6 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, //qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; - disconnectFromHost(credentials); - e = isUserInSudo(credentials, container); if (e) return e; @@ -622,11 +605,6 @@ void ServerController::setCancelInstallation(const bool cancel) m_cancelInstallation = cancel; } -void ServerController::disconnectFromHost(const ServerCredentials &credentials) -{ - m_sshClient.disconnectFromHost(); -} - ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) { return runScript(credentials, @@ -647,6 +625,10 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars) ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) { + if (container == DockerContainer::Dns) { + return ErrorCode::NoError; + } + QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; @@ -657,21 +639,27 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential return ErrorCode::NoError; }; - const QString containerString = ProtocolProps::protoToString(ContainerProps::defaultProtocol(container)); + const Proto protocol = ContainerProps::defaultProtocol(container); + const QString containerString = ProtocolProps::protoToString(protocol); const QJsonObject containerConfig = config.value(containerString).toObject(); QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); - QString port = containerConfig.value(config_key::port).toString(); - QString transportProto = containerConfig.value(config_key::transport_proto).toString(); + QString defaultPort("%1"); + QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); + QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); + QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); - QString script = QString("sudo lsof -i -P -n | grep -E ':%1").arg(port); + QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port); for (auto &port : fixedPorts) { script = script.append("|:%1").arg(port); } script = script.append("' | grep -i %1").arg(transportProto); - runScript(credentials, + ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } if (!stdOut.isEmpty()) { return ErrorCode::ServerPortAlreadyAllocatedError; @@ -786,3 +774,9 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential return ErrorCode::NoError; } + +ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback) +{ + auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); + return error; +} diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 49935a74..2611a191 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -22,41 +22,26 @@ public: typedef QList> Vars; -// ErrorCode fromSshConnectionErrorCode(QSsh::SshError error); - - // QSsh exitCode and exitStatus are different things -// ErrorCode fromSshProcessExitStatus(int exitStatus); - -// QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials); - void disconnectFromHost(const ServerCredentials &credentials); - ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); // create initial config - generate passwords, etc QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp); - bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); + ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); - ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials); - - ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, - const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); - - ErrorCode uploadTextFileToContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); - - QByteArray getTextFileFromContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); - - ErrorCode setupServerFirewall(const ServerCredentials &credentials); + QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &path, ErrorCode *errorCode = nullptr); QString replaceVars(const QString &script, const Vars &vars); + Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); ErrorCode runScript(const ServerCredentials &credentials, QString script, const std::function &cbReadStdOut = nullptr, @@ -66,12 +51,11 @@ public: const std::function &cbReadStdOut = nullptr, const std::function &cbReadStdErr = nullptr); - Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); - QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); void setCancelInstallation(const bool cancel); - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); + + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); @@ -81,8 +65,14 @@ private: ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); + bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); + + ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, + const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + + ErrorCode setupServerFirewall(const ServerCredentials &credentials); std::shared_ptr m_settings; std::shared_ptr m_configurator; diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index bdb396c3..39d9f4f1 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -10,18 +10,23 @@ #endif namespace libssh { + std::function Client::m_passphraseCallback; + Client::Client(QObject *parent) : QObject(parent) { } Client::~Client() { } + int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) + { + auto passphrase = m_passphraseCallback(); + passphrase.toStdString().copy(buf, passphrase.size() + 1); + return 0; + } + ErrorCode Client::connectToHost(const ServerCredentials &credentials) { -// if (is_ssh_initialized()) { -// qDebug() << "Failed to initialize ssh"; -// return ErrorCode::InternalError; -// } if (m_session == nullptr) { m_session = ssh_new(); @@ -51,20 +56,42 @@ namespace libssh { int authResult = SSH_ERROR; if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { - ssh_key privateKey; - ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, nullptr, nullptr, &privateKey); - authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey); - } - else { + ssh_key privateKey = nullptr; + ssh_key publicKey = nullptr; + authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + if (authResult == SSH_OK) { + authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey); + } + + if (authResult == SSH_OK) { + authResult = ssh_userauth_try_publickey(m_session, authUsername.c_str(), publicKey); + } + + if (authResult == SSH_OK) { + authResult = ssh_userauth_publickey(m_session, authUsername.c_str(), privateKey); + } + + if (publicKey) { + ssh_key_free(publicKey); + } + if (privateKey) { + ssh_key_free(privateKey); + } + if (authResult != SSH_OK) { + qDebug() << ssh_get_error(m_session); + ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session)); + if (errorCode == ErrorCode::NoError) { + errorCode = ErrorCode::SshPrivateKeyFormatError; + } + return errorCode; + } + } else { authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); + if (authResult != SSH_OK) { + qDebug() << ssh_get_error(m_session); + return fromLibsshErrorCode(ssh_get_error_code(m_session)); + } } - - if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); - } - - return fromLibsshErrorCode(ssh_get_error_code(m_session)); } return ErrorCode::NoError; } @@ -319,4 +346,33 @@ namespace libssh { default: return ErrorCode::SshSftpFailureError; } } + + ErrorCode Client::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &passphraseCallback) + { + int authResult = SSH_ERROR; + ErrorCode errorCode = ErrorCode::NoError; + + ssh_key privateKey = nullptr; + m_passphraseCallback = passphraseCallback; + authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + if (authResult == SSH_OK) { + char* key = new char[65535]; + + authResult = ssh_pki_export_privkey_base64(privateKey, nullptr, nullptr, nullptr, &key); + decryptedPrivateKey = key; + delete[] key; + + if (authResult != SSH_OK) { + qDebug() << "failed to export private key"; + errorCode = ErrorCode::InternalError; + } + } else { + errorCode = ErrorCode::SshPrivateKeyError; + } + + if (privateKey) { + ssh_key_free(privateKey); + } + return errorCode; + } } diff --git a/client/core/sshclient.h b/client/core/sshclient.h index 912748f0..db22d0dd 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -36,15 +36,19 @@ namespace libssh { const std::string& localPath, const std::string& remotePath, const std::string& fileDesc); + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &passphraseCallback); private: ErrorCode closeChannel(); ErrorCode closeSftpSession(); ErrorCode fromLibsshErrorCode(int errorCode); ErrorCode fromLibsshSftpErrorCode(int errorCode); + static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); ssh_session m_session = nullptr; ssh_channel m_channel = nullptr; sftp_session m_sftpSession = nullptr; + + static std::function m_passphraseCallback; signals: void writeToChannelFinished(); void sftpFileCopyFinished(); diff --git a/client/main.cpp b/client/main.cpp index 62685953..f20c5dd1 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -4,6 +4,7 @@ #include "amnezia_application.h" #include "defines.h" +#include "migrations.h" #ifdef Q_OS_WIN #include "Windows.h" @@ -16,6 +17,9 @@ int main(int argc, char *argv[]) { + Migrations migrationsManager; + migrationsManager.doMigrations(); + QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); diff --git a/client/migrations.cpp b/client/migrations.cpp new file mode 100644 index 00000000..6fac6be7 --- /dev/null +++ b/client/migrations.cpp @@ -0,0 +1,86 @@ +#include "migrations.h" + +#include +#include +#include +#include + +#include "defines.h" + +Migrations::Migrations(QObject *parent) + : QObject{parent} +{ + QString version(APP_MAJOR_VERSION); + + QStringList versionDigits = version.split("."); + + if (versionDigits.size() >= 3) { + currentMajor = versionDigits[0].toInt(); + currentMinor = versionDigits[1].toInt(); + currentMicro = versionDigits[2].toInt(); + } + + if (versionDigits.size() == 4) { + currentPatch = versionDigits[3].toInt(); + } +} + +void Migrations::doMigrations() +{ + if (currentMajor == 3) { + migrateV3(); + } +} + +void Migrations::migrateV3() +{ +#ifdef Q_OS_ANDROID + qDebug() << "Migration to V3 on Android..."; + + QString packageName = "org.amnezia.vpn"; + + QDir dir("."); + QString currentDir = dir.absolutePath(); + + int packageNameIndex = currentDir.indexOf(packageName); + + if (packageNameIndex == -1) { + return; + } + + QString rootLocation = currentDir.left(packageNameIndex + packageName.size()); + + if (rootLocation.isEmpty()) { + return; + } + + QString location = rootLocation + "/files/.config/AmneziaVPN.ORG/AmneziaVPN.conf"; + + QFile oldConfig(location); + + if (oldConfig.exists()) { + QString newConfigPath = rootLocation + "/files/settings"; + + QDir newConfigDir(newConfigPath); + + newConfigPath += "/AmneziaVPN.ORG"; + + bool mkPathRes = newConfigDir.mkpath(newConfigPath); + + if (!mkPathRes) { + return; + } + + QFile newConfigFile(newConfigPath + "/AmneziaVPN.conf"); + + if (!newConfigFile.exists()) { + bool cpResult = QFile::copy(oldConfig.fileName(), newConfigFile.fileName()); + if (cpResult) { + oldConfig.remove(); + QDir oldConfigDir(rootLocation + "/files/.config"); + oldConfigDir.rmdir("AmneziaVPN.ORG"); + } + } + } +#endif +} diff --git a/client/migrations.h b/client/migrations.h new file mode 100644 index 00000000..ea6bae92 --- /dev/null +++ b/client/migrations.h @@ -0,0 +1,24 @@ +#ifndef MIGRATIONS_H +#define MIGRATIONS_H + +#include + +class Migrations : public QObject +{ + Q_OBJECT +public: + explicit Migrations(QObject *parent = nullptr); + + void doMigrations(); + +private: + void migrateV3(); + +private: + int currentMajor = 0; + int currentMinor = 0; + int currentMicro = 0; + int currentPatch = 0; +}; + +#endif // MIGRATIONS_H diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index a86ab7ca..2e5641c9 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -15,6 +15,7 @@ #include "private/qandroidextras_p.h" #include "ui/pages_logic/StartPageLogic.h" +#include "androidvpnactivity.h" #include "androidutils.h" namespace { @@ -54,6 +55,10 @@ AndroidController::AndroidController() : QObject() isConnected = doc.object()["connected"].toBool(); + if (isConnected) { + emit scheduleStatusCheckSignal(); + } + emit initialized( true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); @@ -66,9 +71,11 @@ AndroidController::AndroidController() : QObject() Q_UNUSED(parcelBody); qDebug() << "Transact: connected"; - isConnected = true; + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } - emit scheduleStatusCheckSignal(); + isConnected = true; emit connectionStateChanged(VpnProtocol::Connected); }, Qt::QueuedConnection); @@ -85,7 +92,6 @@ AndroidController::AndroidController() : QObject() connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, [this](const QString& parcelBody) { qDebug() << "Transact: update"; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); QString rx = doc.object()["rx_bytes"].toString(); @@ -203,12 +209,7 @@ void AndroidController::setNotificationText(const QString& title, } void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { - QJsonObject rootObject; - rootObject["data"] = configContent; - rootObject["suggestedName"] = suggestedName; - QJsonDocument doc(rootObject); - - AndroidVPNActivity::sendToService(ServiceAction::ACTION_SHARE_CONFIG, doc.toJson()); + AndroidVPNActivity::saveFileAs(configContent, suggestedName); } /* @@ -248,7 +249,7 @@ void AndroidController::cleanupBackendLogs() { } void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importConnectionFromCode(data); + m_startPageLogic->selectConfigFormat(data); } const QJsonObject &AndroidController::vpnConfig() const @@ -266,6 +267,11 @@ void AndroidController::startQrReaderActivity() AndroidVPNActivity::instance()->startQrCodeReader(); } +void AndroidController::copyTextToClipboard(QString text) +{ + AndroidVPNActivity::instance()->copyTextToClipboard(text); +} + void AndroidController::scheduleStatusCheckSlot() { QTimer::singleShot(1000, [this]() { diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 59ecd9b3..00b37225 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -11,7 +11,6 @@ #include "ui/pages_logic/StartPageLogic.h" #include "protocols/vpnprotocol.h" -#include "androidvpnactivity.h" using namespace amnezia; @@ -44,6 +43,7 @@ public: void setVpnConfig(const QJsonObject &newVpnConfig); void startQrReaderActivity(); + void copyTextToClipboard(QString text); signals: void connectionStateChanged(VpnProtocol::VpnConnectionState state); diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp index 17639023..9431597b 100644 --- a/client/platforms/android/androidvpnactivity.cpp +++ b/client/platforms/android/androidvpnactivity.cpp @@ -57,6 +57,22 @@ void AndroidVPNActivity::startQrCodeReader() QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); } +void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) { + QJniObject::callStaticMethod( + CLASSNAME, + "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", + QJniObject::fromString(fileContent).object(), + QJniObject::fromString(suggestedFilename).object()); +} + +void AndroidVPNActivity::copyTextToClipboard(QString text) +{ + QJniObject::callStaticMethod( + CLASSNAME, + "putTextToClipboard", "(Ljava/lang/String;)V", + QJniObject::fromString(text).object()); +} + // static AndroidVPNActivity* AndroidVPNActivity::instance() { if (s_instance == nullptr) { @@ -70,9 +86,9 @@ AndroidVPNActivity* AndroidVPNActivity::instance() { void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) { int messageType = (int)type; - QJniEnvironment env; QJniObject::callStaticMethod( - CLASSNAME, "sendToService", "(ILjava/lang/String;)V", + CLASSNAME, + "sendToService", "(ILjava/lang/String;)V", static_cast(messageType), QJniObject::fromString(data).object()); } diff --git a/client/platforms/android/androidvpnactivity.h b/client/platforms/android/androidvpnactivity.h index 1bc1a522..8eeb5598 100644 --- a/client/platforms/android/androidvpnactivity.h +++ b/client/platforms/android/androidvpnactivity.h @@ -75,6 +75,8 @@ public: static void sendToService(ServiceAction type, const QString& data); static void connectService(); static void startQrCodeReader(); + static void saveFileAs(QString fileContent, QString suggestedFilename); + static void copyTextToClipboard(QString text); signals: void serviceConnected(); diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 224d5176..666bf80d 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -8,8 +8,7 @@ #include "wireguardprotocol.h" #include "utilities.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); @@ -47,11 +46,8 @@ void WireguardProtocol::stop() m_wireguardStopProcess->setProgram(PermittedProcess::Wireguard); - - QStringList arguments({"--remove", configPath()}); - m_wireguardStopProcess->setArguments(arguments); - - qDebug() << arguments.join(" "); + m_wireguardStopProcess->setArguments(stopArgs()); + qDebug() << stopArgs().join(" "); connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; @@ -62,12 +58,25 @@ void WireguardProtocol::stop() qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; }); +#ifdef Q_OS_LINUX + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); + if (result.returnValue()) { + setConnectionState(VpnProtocol::Disconnected); + return; + } + } else { + qCritical() << "IPC client not initialized"; + setConnectionState(VpnProtocol::Disconnected); + return; + } +#endif + m_wireguardStopProcess->start(); m_wireguardStopProcess->waitForFinished(10000); setConnectionState(VpnProtocol::Disconnected); #endif - } void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration) @@ -79,13 +88,28 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura return; } - m_isConfigLoaded = true; - m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.close(); - m_configFileName = m_configFile.fileName(); - qDebug().noquote() << QString("Set config data") << m_configFileName; +#ifdef Q_OS_LINUX + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName()); + if (result.returnValue()) { + qCritical() << "Failed to copy wireguard config"; + return; + } + } else { + qCritical() << "IPC client not initialized"; + return; + } + m_configFileName = "/etc/wireguard/wg99.conf"; +#else + m_configFileName = m_configFile.fileName(); +#endif + + m_isConfigLoaded = true; + + qDebug().noquote() << QString("Set config data") << configPath(); qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } @@ -120,8 +144,15 @@ ErrorCode WireguardProtocol::start() return lastError(); } - if (!QFileInfo::exists(configPath())) { - setLastError(ErrorCode::ConfigMissing); + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardConfigExists(configPath()); + if (result.returnValue()) { + setLastError(ErrorCode::ConfigMissing); + return lastError(); + } + } else { + qCritical() << "IPC client not initialized"; + setLastError(ErrorCode::InternalError); return lastError(); } @@ -143,11 +174,8 @@ ErrorCode WireguardProtocol::start() m_wireguardStartProcess->setProgram(PermittedProcess::Wireguard); - - QStringList arguments({"--add", configPath()}); - m_wireguardStartProcess->setArguments(arguments); - - qDebug() << arguments.join(" "); + m_wireguardStartProcess->setArguments(startArgs()); + qDebug() << startArgs().join(" "); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; @@ -176,7 +204,7 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardError, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAllStandardError(); - reply.waitForFinished(1000); + reply.waitForFinished(10); qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardError" << reply.returnValue(); }); @@ -204,10 +232,33 @@ void WireguardProtocol::updateVpnGateway(const QString &line) // qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); // } // } - // } +// } } QString WireguardProtocol::serviceName() const { return "AmneziaVPN.WireGuard0"; } + +QStringList WireguardProtocol::stopArgs() +{ +#ifdef Q_OS_WIN + return {"--remove", configPath()}; +#elif defined Q_OS_LINUX + return {"down", "wg99"}; +#else + return {"--remove", configPath()}; +#endif +} + +QStringList WireguardProtocol::startArgs() +{ +#ifdef Q_OS_WIN + return {"--add", configPath()}; +#elif defined Q_OS_LINUX + return {"up", "wg99"}; +#else + return {"--add", configPath()}; +#endif +} + diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 7a50b0d3..880417b4 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -28,7 +28,8 @@ private: void updateRouteGateway(QString line); void updateVpnGateway(const QString &line); QString serviceName() const; - + QStringList stopArgs(); + QStringList startArgs(); private: QString m_configFileName; diff --git a/client/resources.qrc b/client/resources.qrc index 924e1406..8deef801 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -165,6 +165,7 @@ ui/qml/Controls/PopupWithQuestion.qml ui/qml/Pages/PageAdvancedServerSettings.qml ui/qml/Controls/PopupWarning.qml + ui/qml/Controls/PopupWithTextField.qml server_scripts/check_user_in_sudo.sh diff --git a/client/server_scripts/remove_all_containers.sh b/client/server_scripts/remove_all_containers.sh index bae7cc3d..a560ade7 100644 --- a/client/server_scripts/remove_all_containers.sh +++ b/client/server_scripts/remove_all_containers.sh @@ -1,3 +1,3 @@ -sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker stop -sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker rm +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 sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp index 0b02241c..a96827b6 100644 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp +++ b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp @@ -44,11 +44,11 @@ void AdvancedServerSettingsLogic::onPushButtonClearServerClicked() uiLogic()->pageLogic()->onDisconnect(); } - ErrorCode e = m_serverController->removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); - m_serverController->disconnectFromHost(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); - if (e) { + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); + if (errorCode) { emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" + - tr("Error message: ") + errorString(e) + "\n" + + tr("Error message: ") + errorString(errorCode) + "\n" + tr("See logs for details.")); } else { set_labelWaitInfoVisible(true); @@ -69,7 +69,7 @@ void AdvancedServerSettingsLogic::onPushButtonScanServerClicked() bool isServerCreated; auto containersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(false, isServerCreated); + ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); if (errorCode != ErrorCode::NoError) { emit uiLogic()->showWarningMessage(tr("Error occurred while scanning the server.") + "\n" + tr("Error message: ") + errorString(errorCode) + "\n" + diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp index e5358009..e86201e7 100644 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ b/client/ui/pages_logic/AppSettingsLogic.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace amnezia; using namespace PageEnumNS; @@ -82,8 +83,8 @@ void AppSettingsLogic::onPushButtonBackupAppConfigClicked() void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() { - QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); + QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) return; @@ -98,6 +99,5 @@ void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() } else { emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); } - } diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp index cbdf2692..0e92f8c9 100644 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ b/client/ui/pages_logic/GeneralSettingsLogic.cpp @@ -13,7 +13,7 @@ GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent): void GeneralSettingsLogic::onUpdatePage() { uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - set_existsAnyServer(uiLogic()->m_selectedServerIndex >= 0); + set_existsAnyServer(m_settings->serversCount() > 0); uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex())); diff --git a/client/ui/pages_logic/PageLogicBase.cpp b/client/ui/pages_logic/PageLogicBase.cpp index 9274ef49..05c4e3a1 100644 --- a/client/ui/pages_logic/PageLogicBase.cpp +++ b/client/ui/pages_logic/PageLogicBase.cpp @@ -11,7 +11,6 @@ PageLogicBase::PageLogicBase(UiLogic *logic, QObject *parent): { m_settings = logic->m_settings; m_configurator = logic->m_configurator; - m_serverController = logic->m_serverController; } diff --git a/client/ui/pages_logic/PageLogicBase.h b/client/ui/pages_logic/PageLogicBase.h index 887b0ec7..6616de9d 100644 --- a/client/ui/pages_logic/PageLogicBase.h +++ b/client/ui/pages_logic/PageLogicBase.h @@ -28,7 +28,6 @@ protected: std::shared_ptr m_settings; std::shared_ptr m_configurator; - std::shared_ptr m_serverController; signals: void updatePage(); diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp index f6a8abdb..9874837f 100644 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp +++ b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp @@ -96,19 +96,18 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function -#include "protocols/CloakLogic.h" -#include "protocols/OpenVpnLogic.h" -#include "protocols/ShadowSocksLogic.h" +#include "protocols/PageProtocolLogicBase.h" #include "core/servercontroller.h" #include @@ -31,6 +29,7 @@ void ServerContainersLogic::onUpdatePage() p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); emit updatePage(); } @@ -69,7 +68,8 @@ void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) { //buttonSetEnabledFunc(false); - ErrorCode e = m_serverController->removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); + ServerController serverController(m_settings); + ErrorCode e = serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); //buttonSetEnabledFunc(true); @@ -83,18 +83,20 @@ void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) { - QJsonObject config = m_serverController->createContainerInitialConfig(c, port, tp); + ServerController serverController(m_settings); + QJsonObject config = serverController.createContainerInitialConfig(c, port, tp); emit uiLogic()->goToPage(Page::ServerConfiguringProgress); qApp->processEvents(); bool isServerCreated = false; - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(false, isServerCreated); + ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); if (errorCode == ErrorCode::NoError) { if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { auto installAction = [this, c, &config]() { - return m_serverController->setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); + ServerController serverController(m_settings); + return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); }; errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index e91b1e33..79d13c8b 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -21,7 +21,6 @@ void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) void ServerListLogic::onServerListPushbuttonSettingsClicked(int index) { uiLogic()->m_selectedServerIndex = index; - uiLogic()->m_installCredentials = m_settings->serverCredentials(index); uiLogic()->goToPage(Page::ServerSettings); } diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 8c78667d..8d9c33f6 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -6,9 +6,11 @@ #include "configurators/vpn_configurator.h" #include "../uilogic.h" #include "utilities.h" +#include "core/servercontroller.h" #include #include +#include #ifdef Q_OS_ANDROID #include @@ -94,8 +96,7 @@ void StartPageLogic::onPushButtonConnect() set_labelWaitInfoText(tr("Please fill in all fields")); return; } - } - else { + } else { if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty() ) { @@ -111,7 +112,7 @@ void StartPageLogic::onPushButtonConnect() serverCredentials.hostName = serverCredentials.hostName.split(":").at(0); } serverCredentials.userName = lineEditLoginText(); - if (pushButtonConnectKeyChecked()){ + if (pushButtonConnectKeyChecked()) { QString key = textEditSshKeyText(); if (key.startsWith("ssh-rsa")) { emit uiLogic()->showPublicKeyWarning(); @@ -123,28 +124,44 @@ void StartPageLogic::onPushButtonConnect() } serverCredentials.password = key; - } - else { + } else { serverCredentials.password = lineEditPasswordText(); } set_pushButtonConnectEnabled(false); set_pushButtonConnectText(tr("Connecting...")); - ErrorCode e = ErrorCode::NoError; -#ifdef Q_DEBUG - //QString output = m_serverController->checkSshConnection(serverCredentials, &e); -#else + ServerController serverController(m_settings); + ErrorCode errorCode = ErrorCode::NoError; + + if (pushButtonConnectKeyChecked()) { + auto passphraseCallback = [this, &serverController]() { + emit showPassphraseRequestMessage(); + QEventLoop loop; + QObject::connect(this, &StartPageLogic::passphraseDialogClosed, &loop, &QEventLoop::quit); + loop.exec(); + + return m_privateKeyPassphrase; + }; + + QString decryptedPrivateKey; + errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); + if (errorCode == ErrorCode::NoError) { + serverCredentials.password = decryptedPrivateKey; + } + } + QString output; -#endif + if (errorCode == ErrorCode::NoError) { + output = serverController.checkSshConnection(serverCredentials, &errorCode); + } bool ok = true; - if (e) { + if (errorCode) { set_labelWaitInfoVisible(true); - set_labelWaitInfoText(errorString(e)); + set_labelWaitInfoText(errorString(errorCode)); ok = false; - } - else { + } else { if (output.contains("Please login as the user")) { output.replace("\n", ""); set_labelWaitInfoVisible(true); @@ -167,7 +184,7 @@ void StartPageLogic::onPushButtonImport() void StartPageLogic::onPushButtonImportOpenFile() { - QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open config file"), + QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); if (fileName.isEmpty()) return; @@ -175,14 +192,7 @@ void StartPageLogic::onPushButtonImportOpenFile() file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - auto configFormat = checkConfigFormat(QString(data)); - if (configFormat == ConfigTypes::OpenVpn) { - importConnectionFromOpenVpnConfig(QString(data)); - } else if (configFormat == ConfigTypes::WireGuard) { - importConnectionFromWireguardConfig(QString(data)); - } else { - importConnectionFromCode(QString(data)); - } + selectConfigFormat(QString(data)); } #ifdef Q_OS_ANDROID @@ -192,6 +202,18 @@ void StartPageLogic::startQrDecoder() } #endif +void StartPageLogic::selectConfigFormat(QString configData) +{ + auto configFormat = checkConfigFormat(configData); + if (configFormat == ConfigTypes::OpenVpn) { + importConnectionFromOpenVpnConfig(configData); + } else if (configFormat == ConfigTypes::WireGuard) { + importConnectionFromWireguardConfig(configData); + } else { + importConnectionFromCode(configData); + } +} + bool StartPageLogic::importConnection(const QJsonObject &profile) { ServerCredentials credentials; diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h index b3dea002..a20fd6c7 100644 --- a/client/ui/pages_logic/StartPageLogic.h +++ b/client/ui/pages_logic/StartPageLogic.h @@ -23,6 +23,8 @@ class StartPageLogic : public PageLogicBase AUTO_PROPERTY(QString, labelWaitInfoText) AUTO_PROPERTY(bool, pushButtonBackFromStartVisible) + AUTO_PROPERTY(QString, privateKeyPassphrase); + READONLY_PROPERTY(QRegularExpression, ipAddressPortRegex) public: Q_INVOKABLE void onUpdatePage() override; @@ -35,6 +37,8 @@ public: Q_INVOKABLE void startQrDecoder(); #endif + void selectConfigFormat(QString configData); + bool importConnection(const QJsonObject &profile); bool importConnectionFromCode(QString code); bool importConnectionFromQr(const QByteArray &data); @@ -45,5 +49,8 @@ public: explicit StartPageLogic(UiLogic *uiLogic, QObject *parent = nullptr); ~StartPageLogic() = default; +signals: + void showPassphraseRequestMessage(); + void passphraseDialogClosed(); }; #endif // START_PAGE_LOGIC_H diff --git a/client/ui/pages_logic/protocols/CloakLogic.cpp b/client/ui/pages_logic/protocols/CloakLogic.cpp index d62f8624..3256de87 100644 --- a/client/ui/pages_logic/protocols/CloakLogic.cpp +++ b/client/ui/pages_logic/protocols/CloakLogic.cpp @@ -112,15 +112,16 @@ void CloakLogic::onPushButtonSaveClicked() progressBarFunc.setTextVisibleFunc(true); progressBarFunc.setTextFunc(QString("Configuring...")); - ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, - containerConfig, - newContainerConfig); - }, - pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); + + auto installAction = [this, containerConfig, &newContainerConfig]() { + ServerController serverController(m_settings); + return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); + }; + + ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp index d5deecf3..fe3a2ed6 100644 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp +++ b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp @@ -162,15 +162,16 @@ void OpenVpnLogic::onPushButtonSaveClicked() progressBarFunc.setTextVisibleFunc(true); progressBarFunc.setTextFunc(QString("Configuring...")); - ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, - containerConfig, - newContainerConfig); - }, - pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); + + auto installAction = [this, containerConfig, &newContainerConfig]() { + ServerController serverController(m_settings); + return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); + }; + + ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp index 5259a6e0..b1d75b8b 100644 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp +++ b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp @@ -104,15 +104,15 @@ void ShadowSocksLogic::onPushButtonSaveClicked() progressBarFunc.setTextVisibleFunc(true); progressBarFunc.setTextFunc(QString("Configuring...")); - ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, - containerConfig, - newContainerConfig); - }, - pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); + + auto installAction = [this, containerConfig, &newContainerConfig]() { + ServerController serverController(m_settings); + return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); + }; + ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); diff --git a/client/ui/qml/Controls/PopupWithTextField.qml b/client/ui/qml/Controls/PopupWithTextField.qml new file mode 100644 index 00000000..acdf1247 --- /dev/null +++ b/client/ui/qml/Controls/PopupWithTextField.qml @@ -0,0 +1,62 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Popup { + id: root + + property alias text: textField.text + property alias placeholderText: textField.placeholderText + property string yesText: "yes" + property string noText: "no" + property var yesFunc + property var noFunc + + signal editingFinished() + + anchors.centerIn: Overlay.overlay + modal: true + closePolicy: Popup.NoAutoClose + + width: parent.width - 20 + + ColumnLayout { + width: parent.width + + TextField { + id: textField + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + font.pixelSize: 16 + echoMode: TextInput.Password + } + + RowLayout { + Layout.fillWidth: true + BlueButtonType { + id: yesButton + Layout.preferredWidth: parent.width / 2 + Layout.fillWidth: true + text: yesText + onClicked: { + root.enabled = false + if (yesFunc && typeof yesFunc === "function") { + yesFunc() + } + root.enabled = true + } + } + BlueButtonType { + id: noButton + Layout.preferredWidth: parent.width / 2 + Layout.fillWidth: true + text: noText + onClicked: { + if (noFunc && typeof noFunc === "function") { + noFunc() + } + } + } + } + } +} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml index eacb607d..85c63b4d 100644 --- a/client/ui/qml/Pages/PageStart.qml +++ b/client/ui/qml/Pages/PageStart.qml @@ -10,6 +10,14 @@ PageBase { page: PageEnum.Start logic: StartPageLogic + Connections { + target: StartPageLogic + + function onShowPassphraseRequestMessage() { + popupWithTextField.open() + } + } + BackButton { id: back_from_start visible: pageLoader.depth > 1 @@ -325,4 +333,22 @@ PageBase { } } } + + PopupWithTextField { + id: popupWithTextField + placeholderText: "Enter private key passphrase" + yesFunc: function() { + editingFinished() + close() + StartPageLogic.passphraseDialogClosed() + text = "" + } + noFunc: function() { + close() + StartPageLogic.passphraseDialogClosed() + } + onEditingFinished: { + StartPageLogic.privateKeyPassphrase = text + } + } } diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml index b8758384..d91c013f 100644 --- a/client/ui/qml/main.qml +++ b/client/ui/qml/main.qml @@ -61,8 +61,12 @@ Window { function close_page() { if (pageLoader.depth <= 1) { + if (GC.isMobile()) { + root.close() + } return } + pageLoader.currentItem.deactivated() pageLoader.pop() } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 895a526c..cc66a47d 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -78,16 +77,14 @@ using namespace amnezia; using namespace PageEnumNS; UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - std::shared_ptr serverController, QObject *parent) : QObject(parent), m_settings(settings), - m_configurator(configurator), - m_serverController(serverController) + m_configurator(configurator) { m_containersModel = new ContainersModel(settings, this); m_protocolsModel = new ProtocolsModel(settings, this); - m_vpnConnection = new VpnConnection(settings, configurator, serverController); + m_vpnConnection = new VpnConnection(settings, configurator); m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); @@ -133,6 +130,7 @@ void UiLogic::initalizeUiLogic() connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { if (connected) { pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); + m_vpnConnection->restoreConnection(); } }); if (!AndroidController::instance()->initialize(pageLogic())) { @@ -224,9 +222,10 @@ void UiLogic::keyPressEvent(Qt::Key key) m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: - case Qt::Key_Back: if (currentPage() == Page::Vpn) break; if (currentPage() == Page::ServerConfiguringProgress) break; + case Qt::Key_Back: + // if (currentPage() == Page::Start && pagesStack.size() < 2) break; // if (currentPage() == Page::Sites && // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { @@ -243,10 +242,16 @@ void UiLogic::keyPressEvent(Qt::Key key) void UiLogic::onCloseWindow() { - if (m_settings->serversCount() == 0) qApp->quit(); - else { - hide(); +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_settings->serversCount() == 0) + { + qApp->quit(); + } else { + emit hide(); } +#endif } QString UiLogic::containerName(int container) @@ -324,18 +329,17 @@ void UiLogic::installServer(QPair &container) }; bool isServerCreated = false; - ErrorCode errorCode = addAlreadyInstalledContainersGui(true, isServerCreated); + ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); if (errorCode == ErrorCode::NoError) { if (!isContainerAlreadyAddedToGui(container.first)) { progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); auto installAction = [&] () { - return m_serverController->setupContainer(m_installCredentials, container.first, container.second); + ServerController serverController(m_settings); + return serverController.setupContainer(m_installCredentials, container.first, container.second); }; errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); - m_serverController->disconnectFromHost(m_installCredentials); - if (errorCode == ErrorCode::NoError) { if (!isServerCreated) { QJsonObject server; @@ -466,7 +470,11 @@ void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &da void UiLogic::copyToClipboard(const QString &text) { +#ifdef Q_OS_ANDROID + AndroidController::instance()->copyTextToClipboard(text); +#else qApp->clipboard()->setText(text); +#endif } void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { @@ -488,6 +496,24 @@ void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QSt MobileUtils::shareText(filesToSend); } +QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep {"raw%3A%2F"}; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} + void UiLogic::registerPagesLogic() { amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); @@ -510,19 +536,27 @@ void UiLogic::registerPagesLogic() registerPageLogic(); } -ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool createNewServer, bool &isServerCreated) +ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) { isServerCreated = false; - ServerCredentials credentials; - if (createNewServer) { - credentials = m_installCredentials; - } else { - credentials = m_settings->serverCredentials(m_selectedServerIndex); + ServerCredentials installCredentials = m_installCredentials; + bool createNewServer = true; + int serverIndex; + + for (int i = 0; i < m_settings->serversCount(); i++) { + const ServerCredentials credentials = m_settings->serverCredentials(i); + if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { + createNewServer = false; + isServerCreated = true; + installCredentials = credentials; + serverIndex = i; + break; + } } QMap installedContainers; - ErrorCode errorCode = m_serverController->getAlreadyInstalledContainers(credentials, installedContainers); - m_serverController->disconnectFromHost(credentials); + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(installCredentials, installedContainers); if (errorCode != ErrorCode::NoError) { return errorCode; } @@ -531,10 +565,10 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool createNewServer, bool & QJsonObject server; QJsonArray containerConfigs; if (createNewServer) { - server.insert(config_key::hostName, credentials.hostName); - server.insert(config_key::userName, credentials.userName); - server.insert(config_key::password, credentials.password); - server.insert(config_key::port, credentials.port); + server.insert(config_key::hostName, installCredentials.hostName); + server.insert(config_key::userName, installCredentials.userName); + server.insert(config_key::password, installCredentials.password); + server.insert(config_key::port, installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); } @@ -547,8 +581,8 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool createNewServer, bool & containerConfigs.append(container.value()); server.insert(config_key::containers, containerConfigs); } else { - m_settings->setContainerConfig(m_selectedServerIndex, container.key(), container.value()); - m_settings->setDefaultContainer(m_selectedServerIndex, installedContainers.firstKey()); + m_settings->setContainerConfig(serverIndex, container.key(), container.value()); + m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); } } @@ -576,3 +610,4 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) } return false; } + diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 92035fe5..f668a925 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -1,12 +1,13 @@ #ifndef UILOGIC_H #define UILOGIC_H -#include -#include -#include +#include #include +#include #include +#include +#include #include #include #include @@ -62,14 +63,12 @@ class UiLogic : public QObject AUTO_PROPERTY(bool, pageEnabled) AUTO_PROPERTY(int, pagesStackDepth) AUTO_PROPERTY(int, currentPageValue) - AUTO_PROPERTY(QString, popupWarningText) READONLY_PROPERTY(QObject *, containersModel) READONLY_PROPERTY(QObject *, protocolsModel) public: - explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - std::shared_ptr serverController, QObject *parent = nullptr); + explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent = nullptr); ~UiLogic(); void showOnStartup(); @@ -117,10 +116,15 @@ public: Q_INVOKABLE void saveBinaryFile(const QString& desc, QString ext, const QString& data); Q_INVOKABLE void copyToClipboard(const QString& text); - Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool createNewServer, bool &isServerCreated); + Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); void shareTempFile(const QString &suggestedName, QString ext, const QString& data); - + static QString getOpenFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = QFileDialog::Options()); signals: void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true); void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true); @@ -181,7 +185,6 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - std::shared_ptr m_serverController; NotificationHandler* m_notificationHandler; diff --git a/client/utilities.cpp b/client/utilities.cpp index d83f563d..e5d2fd52 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -231,7 +231,7 @@ QString Utils::wireguardExecPath() #ifdef Q_OS_WIN return Utils::executable("wireguard/wireguard-service", true); #elif defined Q_OS_LINUX - return Utils::usrExecutable("wg"); + return Utils::usrExecutable("wg-quick"); #else return Utils::executable("/wireguard", true); #endif diff --git a/client/utilities.h b/client/utilities.h index 932b9b27..aeb06865 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -50,7 +50,6 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); - #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index e01f7e61..c9e97e03 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -20,7 +20,6 @@ #ifdef Q_OS_ANDROID #include "../../platforms/android/android_controller.h" -#include "protocols/android_vpnprotocol.h" #endif #ifdef Q_OS_IOS @@ -31,11 +30,9 @@ #include "vpnconnection.h" VpnConnection::VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, - std::shared_ptr serverController, QObject* parent) : QObject(parent), + std::shared_ptr configurator, QObject* parent) : QObject(parent), m_settings(settings), m_configurator(configurator), - m_serverController(serverController), m_isIOSConnected(false) { } @@ -353,10 +350,8 @@ void VpnConnection::connectToVpn(int serverIndex, } m_vpnProtocol->prepare(); #elif defined Q_OS_ANDROID - Proto proto = ContainerProps::defaultProtocol(container); - AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); - connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); - connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); + androidVpnProtocol = createDefaultAndroidVpnProtocol(container); + createAndroidConnections(container); m_vpnProtocol.reset(androidVpnProtocol); #elif defined Q_OS_IOS @@ -373,16 +368,52 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnProtocol.reset(iosVpnProtocol); #endif - connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState))); - connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); - - m_serverController->disconnectFromHost(credentials); + createProtocolConnections(); e = m_vpnProtocol.data()->start(); if (e) emit VpnProtocol::Error; } +void VpnConnection::createProtocolConnections() { + connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); +} + +#ifdef Q_OS_ANDROID +void VpnConnection::restoreConnection() { + createAndroidConnections(); + + m_vpnProtocol.reset(androidVpnProtocol); + + createProtocolConnections(); +} + +void VpnConnection::createAndroidConnections() +{ + int serverIndex = m_settings->defaultServerIndex(); + DockerContainer container = m_settings->defaultContainer(serverIndex); + + createAndroidConnections(container); +} + +void VpnConnection::createAndroidConnections(DockerContainer container) +{ + androidVpnProtocol = createDefaultAndroidVpnProtocol(container); + + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); + connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); +} + +AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) +{ + Proto proto = ContainerProps::defaultProtocol(container); + AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); + + return androidVpnProtocol; +} +#endif + QString VpnConnection::bytesPerSecToText(quint64 bytes) { double mbps = bytes * 8 / 1e6; @@ -401,8 +432,6 @@ void VpnConnection::disconnectFromVpn() } #endif - - if (!m_vpnProtocol.data()) { emit connectionStateChanged(VpnProtocol::Disconnected); #ifdef Q_OS_ANDROID @@ -415,11 +444,8 @@ void VpnConnection::disconnectFromVpn() VpnProtocol::VpnConnectionState VpnConnection::connectionState() { - - if (!m_vpnProtocol) return VpnProtocol::Disconnected; return m_vpnProtocol->connectionState(); - } bool VpnConnection::isConnected() const diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 3a0d4064..8dea7478 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -18,6 +18,10 @@ #include "core/ipcclient.h" #endif +#ifdef Q_OS_ANDROID +#include "protocols/android_vpnprotocol.h" +#endif + class VpnConfigurator; class ServerController; @@ -29,8 +33,7 @@ class VpnConnection : public QObject public: explicit VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, - std::shared_ptr serverController, QObject* parent = nullptr); + std::shared_ptr configurator, QObject* parent = nullptr); ~VpnConnection() override; static QString bytesPerSecToText(quint64 bytes); @@ -61,6 +64,10 @@ public: const QString &remoteAddress() const; void addSitesRoutes(const QString &gw, Settings::RouteMode mode); +#ifdef Q_OS_ANDROID + void restoreConnection(); +#endif + public slots: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -88,7 +95,6 @@ protected: private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - std::shared_ptr m_serverController; QJsonObject m_vpnConfiguration; QJsonObject m_routeMode; @@ -101,6 +107,15 @@ private: #ifdef Q_OS_IOS IOSVpnProtocol * iosVpnProtocol{nullptr}; #endif +#ifdef Q_OS_ANDROID + AndroidVpnProtocol* androidVpnProtocol = nullptr; + + AndroidVpnProtocol* createDefaultAndroidVpnProtocol(DockerContainer container); + void createAndroidConnections(); + void createAndroidConnections(DockerContainer container); +#endif + + void createProtocolConnections(); }; #endif // VPNCONNECTION_H diff --git a/ipc/ipc.h b/ipc/ipc.h index e867c063..cb6a1088 100644 --- a/ipc/ipc.h +++ b/ipc/ipc.h @@ -20,13 +20,12 @@ inline QString permittedProcessPath(PermittedProcess pid) { if (pid == PermittedProcess::OpenVPN) { return Utils::openVpnExecPath(); - } - if (pid == PermittedProcess::Wireguard) { + } else if (pid == PermittedProcess::Wireguard) { return Utils::wireguardExecPath(); - } - else if (pid == PermittedProcess::CertUtil) { + } else if (pid == PermittedProcess::CertUtil) { return Utils::certUtilPath(); } + return ""; } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 2c9f4746..8970f7c8 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -18,5 +18,9 @@ class IpcInterface SLOT( void cleanUp() ); SLOT( void setLogsEnabled(bool enabled) ); + + SLOT( bool copyWireguardConfig(const QString &sourcePath) ); + SLOT( bool isWireguardRunning() ); + SLOT( bool isWireguardConfigExists(const QString &configPath) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 3d248b84..ddbc646c 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "router.h" #include "logger.h" @@ -124,3 +125,50 @@ void IpcServer::setLogsEnabled(bool enabled) Logger::deinit(); } } + +bool IpcServer::copyWireguardConfig(const QString &sourcePath) +{ +#ifdef Q_OS_LINUX + const QString wireguardConfigPath = "/etc/wireguard/wg99.conf"; + if (QFile::exists(wireguardConfigPath)) + { + QFile::remove(wireguardConfigPath); + } + + if (!QFile::copy(sourcePath, wireguardConfigPath)) { + qDebug() << "WireguardProtocol::WireguardProtocol error occured while copying wireguard config:"; + return false; + } + return true; +#else + return false; +#endif +} + +bool IpcServer::isWireguardRunning() +{ +#ifdef Q_OS_LINUX + QProcess checkWireguardStatusProcess; + + connect(&checkWireguardStatusProcess, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) { + qDebug() << "WireguardProtocol::WireguardProtocol error occured while checking wireguard status: " << error; + }); + + checkWireguardStatusProcess.setProgram("/bin/wg"); + checkWireguardStatusProcess.setArguments(QStringList{"show"}); + checkWireguardStatusProcess.start(); + checkWireguardStatusProcess.waitForFinished(10000); + QString output = checkWireguardStatusProcess.readAllStandardOutput(); + if (!output.isEmpty()) { + return true; + } + return false; +#else + return false; +#endif +} + +bool IpcServer::isWireguardConfigExists(const QString &configPath) +{ + return QFileInfo::exists(configPath); +} diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index a5b52ed1..d5706784 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -25,6 +25,9 @@ public: virtual QStringList getTapList() override; virtual void cleanUp() override; virtual void setLogsEnabled(bool enabled) override; + virtual bool copyWireguardConfig(const QString &sourcePath) override; + virtual bool isWireguardRunning() override; + virtual bool isWireguardConfigExists(const QString &configPath) override; private: int m_localpid = 0;