Compare commits

...

48 Commits

Author SHA1 Message Date
Dmitriy Karpushin aef20430f2 Configuring of ikev2 2023-07-07 14:19:03 +03:00
Dmitriy Karpushin b818de3378 Merge remote-tracking branch 'origin/feature/ikev2_android' into feature/ikev2_android 2023-04-24 15:42:18 +03:00
Dmitriy Karpushin cf86c6e912 Start logic 2023-04-24 15:41:24 +03:00
Dmitry Karpushin 483263171b Merge branch 'dev' into feature/ikev2_android 2023-04-24 13:19:23 +03:00
Dmitriy Karpushin 5fb91d8f5b Update of strongswan submodule 2023-04-24 13:18:09 +03:00
Dmitriy Karpushin 100f8ad95d Refactoring of build process + utility classes 2023-04-24 13:05:56 +03:00
pokamest 6429ff0603 Merge pull request #228 from amnezia-vpn/bugfix/openvpn-crl-verify
bugfix/openvpn-crl-verify
2023-04-21 12:43:35 +01:00
vladimir.kuznetsov f5057dfac4 removed crl-verify from client config
- specified full path to crl in server config
- added crl generation when setting up a container
2023-04-21 07:44:35 +03:00
pokamest 00d61def0b Merge pull request #226 from amnezia-vpn/bugfix/sftp-files-permissions
bugfix/sftp-files-permissions
2023-04-19 16:19:47 +01:00
vladimir.kuznetsov f5a26c7116 set the value S_IRWXU for windows, so that when copying via sftp, the necessary permissions for the file are set 2023-04-19 17:58:03 +03:00
pokamest 54fba99bed Merge pull request #225 from amnezia-vpn/bugfix/tcp-port-busy-check
in the port busy check, for tcp now only LISTEN ports are checked
2023-04-19 14:09:49 +01:00
vladimir.kuznetsov 7216a8b923 in the port busy check, for tcp now only LISTEN ports are checked 2023-04-19 06:39:50 +03:00
pokamest 97e322ba22 Merge pull request #223 from amnezia-vpn/bugfix/tcp-port-busy-check
in the port busy check, for tcp only the local port is now checked
2023-04-18 18:26:59 +01:00
vladimir.kuznetsov fc603f11ce in the port busy check, for tcp only the local port is now checked 2023-04-18 20:00:40 +03:00
pokamest 10bca290c3 Merge pull request #221 from amnezia-vpn/bugfix/check-sudo-for-root
skipping sudo check for root user
2023-04-18 11:54:29 +01:00
vladimir.kuznetsov 3dabaeb2c9 skipping sudo check for root user 2023-04-18 05:48:37 +03:00
pokamest cf74b879c6 Merge pull request #219 from amnezia-vpn/change/client-management-update-page
change/client-management-update-page
2023-04-16 14:41:28 +01:00
vladimir.kuznetsov 0ae2a1f177 now, when onUpdateAllPages is called, the ClientInfoLogic and ClientManagementLogic pages will not be updated
- moved the Client Management button to the Advanced Settings page
-
2023-04-16 07:32:32 +03:00
Dmitriy Karpushin e43041572e Strongswan submodule fixes 2023-04-13 09:55:58 +03:00
Dmitriy Karpushin 4b103a1622 Pre-configuring of StrongSwan from project cmake file 2023-04-13 09:14:56 +03:00
pokamest af29637163 Merge branch 'dev' 2023-04-11 18:00:54 +01:00
Josh Soref 7351fe9633 Spelling (#214)
Spelling fixed
2023-04-11 14:50:44 +01:00
pokamest 1a6b4a1188 Merge pull request #213 from amnezia-vpn/chore/qtssh-gitmodules
removed qtssh from gitmodules
2023-04-11 02:00:34 +01:00
vladimir.kuznetsov 8751dd3797 removed qtssh from gitmodules 2023-04-09 05:47:27 +03:00
pokamest 9a6df25280 Merge pull request #149 from amnezia-vpn/feature/qt6-client-management-panel
feature/qt6-client-management-panel
2023-04-08 19:33:09 +01:00
vladimir.kuznetsov ada8912a1f Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/qt6-client-management-panel 2023-04-08 19:03:10 +03:00
pokamest a5e5c3d941 Merge pull request #210 from amnezia-vpn/dev
Pre-release 3.0.4
2023-04-06 20:05:51 +01:00
Dmitriy Karpushin b6c7ef415d strongswan submodule for IKEv2 support 2023-04-05 16:07:31 +03:00
vladimir.kuznetsov b000eda126 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/qt6-client-management-panel 2023-03-19 17:15:13 +03:00
vladimir.kuznetsov 4171afe275 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/qt6-client-management-panel 2023-02-27 19:59:01 +03:00
vladimir.kuznetsov 25829451c8 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/qt6-client-management-panel 2023-01-29 09:52:12 +03:00
vladimir.kuznetsov 45016b76e7 moved crl-verify crl.pem to openvpn config templates 2023-01-19 17:49:17 +03:00
vladimir.kuznetsov 8ea80a616e Merge branch 'qt_migration' of github.com:amnezia-vpn/desktop-client into feature/qt6-client-management-panel 2023-01-18 20:06:01 +03:00
vladimir.kuznetsov c5df7f9bb7 added diagrams describing the process of obtaining information about clients 2023-01-18 19:55:12 +03:00
pokamest 891f990e35 Merge pull request #154 from amnezia-vpn/dev
Release 2.1.2
2023-01-18 12:22:18 +00:00
vladimir.kuznetsov d6d3bf6943 moved certificate content acquisition from ClientManagementLogic to ClientInfoLogic 2023-01-17 21:04:15 +03:00
vladimir.kuznetsov f6e8346841 moved getClientsList and setClientsList from serverController 2023-01-17 18:41:36 +03:00
vladimir.kuznetsov 3a210c5bab added wireguard key revocation 2023-01-16 20:24:37 +03:00
vladimir.kuznetsov 3f99c52349 change Flickable to FlickableType for ClientManagement and ClientInfo pages 2023-01-16 12:37:14 +03:00
vladimir.kuznetsov 599910daea added openvpn certificate revocation 2023-01-15 18:09:05 +03:00
vladimir.kuznetsov bee42ea2fb fixed double call onEditingFinished when pressing Enter 2023-01-14 18:30:08 +03:00
vladimir.kuznetsov f24df9fb05 Merge branch 'qt_migration' of github.com:amnezia-vpn/desktop-client into feature/qt6-client-management-panel 2023-01-12 20:28:13 +03:00
vladimir.kuznetsov ce2a122d51 added busy indicator when saving a list of clients, on the client info panel 2023-01-12 20:20:24 +03:00
vladimir.kuznetsov 24ea686e4d added busy indicator when loading a list of clients, on the client management panel 2023-01-11 21:36:18 +03:00
vladimir.kuznetsov a7030cdcb9 added saving the list of clients for wireguard
- added error handling when getting/saving a list of clients
2023-01-11 20:36:47 +03:00
vladimir.kuznetsov 8c137ecc52 added page to display WireGuard client information 2023-01-10 16:21:45 +03:00
vladimir.kuznetsov a42beb86c0 added client management panel
- added classes for displaying the client management panel
- added class for displaying the client info
- added page to display a list of clients
- added page to display OpenVpn client information
- added diagram with OpenVpn certificate revocation process
2023-01-09 12:38:01 +03:00
pokamest 7d09d41a7d Merge pull request #108 from amnezia-vpn/dev
Release 2.1.0
2022-09-02 14:08:31 +03:00
112 changed files with 3831 additions and 362 deletions
+3 -3
View File
@@ -1,6 +1,3 @@
[submodule "3rd/QtSsh"]
path = 3rd/QtSsh
url = https://github.com/amnezia-vpn/QtSsh.git
[submodule "client/3rd/wireguard-tools"]
path = client/3rd/wireguard-tools
url = https://github.com/WireGuard/wireguard-tools/
@@ -37,6 +34,9 @@
[submodule "client/3rd/SortFilterProxyModel"]
path = client/3rd/SortFilterProxyModel
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
[submodule "client/3rd/strongswan/sources"]
path = client/3rd/strongswan/sources
url = https://github.com/kolobchanin/strongswan.git
[submodule "client/3rd/mbedtls"]
path = client/3rd/mbedtls
url = https://github.com/Mbed-TLS/mbedtls.git
Submodule
+1
Submodule 3rd/QtSsh added at a34ded6e69
+4 -4
View File
@@ -87,7 +87,7 @@ Error 1
Add a user defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
if above error still persists on you M1 Mac, then most proably you need to install arch based cmake
if above error still persists on you M1 Mac, then most probably you need to install arch based cmake
```
arch -arm64 brew install cmake
```
@@ -112,15 +112,15 @@ In case you get errors regarding missing SDK or 'sdkmanager not running', you ca
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 <path>` 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`.
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 platform 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_<version>_Clang_<architecture>-<BuildType>`.
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_<version>_Clang_<architecture>-<BuildType>/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!
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_<version>_Clang_<architecture>-<BuildType>/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 project, 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 (`<path>/client/android-build/.`) and you should be good to continue.
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 generated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
## License
GPL v.3
+20
View File
@@ -0,0 +1,20 @@
include(ExternalProject)
set(STRONGSWAN_SOURCES_ROOT ${CMAKE_CURRENT_LIST_DIR}/sources)
ExternalProject_Add(
strongswan
UPDATE_DISCONNECTED true
CONFIGURE_HANDLED_BY_BUILD true
PREFIX ${STRONGSWAN_SOURCES_ROOT}
SOURCE_DIR ${STRONGSWAN_SOURCES_ROOT}
BINARY_DIR ${STRONGSWAN_SOURCES_ROOT}
INSTALL_DIR ${STRONGSWAN_SOURCES_ROOT}
STAMP_DIR ${STRONGSWAN_SOURCES_ROOT}/stamp
LOG_DIR ${STRONGSWAN_SOURCES_ROOT}/log
TMP_DIR ${STRONGSWAN_SOURCES_ROOT}/tmp
CONFIGURE_COMMAND ./autogen.sh
COMMAND ./configure --disable-kernel-netlink
BUILD_COMMAND make #dist
INSTALL_COMMAND ""
)
+4 -3
View File
@@ -273,6 +273,8 @@ if(ANDROID)
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp
)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/3rd/strongswan DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/3rd)
endif()
if(IOS)
@@ -341,7 +343,7 @@ if(IOS)
enable_language(OBJCXX)
enable_language(Swift)
#disbale in cicd
#disable in cicd
include(cmake/osxtools.cmake)
# set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE)
@@ -353,7 +355,7 @@ if(IOS)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
#need to change for debug and relase
#need to change for debug and release
set_target_properties(${PROJECT}
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
@@ -507,7 +509,6 @@ if(ANDROID)
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNService.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
+39 -6
View File
@@ -1,7 +1,7 @@
apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext{
ext {
kotlin_version = "1.7.22"
// for libwg
appcompatVersion = '1.1.0'
@@ -11,6 +11,7 @@ buildscript {
streamsupportVersion = '1.7.0'
threetenabpVersion = '1.1.1'
groupName = 'org.amnezia.vpn'
relativePathToStrongswan = '../3rd/strongswan/sources/src/frontends/android/app/src/main/jni'
}
repositories {
@@ -105,7 +106,7 @@ android {
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
jniLibs.srcDirs = ['libs', relativePathToStrongswan]
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
}
}
@@ -138,33 +139,65 @@ android {
targetSdkVersion = 31
versionCode 10 // Change to a higher number
versionName "2.0.10" // Change to a higher number
multiDexEnabled true
javaCompileOptions.annotationProcessorOptions.arguments = [
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
]
}
buildTypes {
buildTypes {
release {
// That would enable treeshaking and remove java code that is just called from qt
minifyEnabled false
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
arguments "-DANDROID_PACKAGE_NAME=${groupName}",
"-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
debug {
//applicationIdSuffix ".debug"
//versionNameSuffix "-debug"
minifyEnabled false
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
arguments "-DANDROID_PACKAGE_NAME=${groupName}",
"-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
}
}
task buildStrongSwanNative(type: Exec) {
environment = System.getenv() + ['JNI_PACKAGE': 'org_amnezia_vpn', 'JNI_PACKAGE_STRING': 'org/amnezia/vpn']
workingDir "${relativePathToStrongswan}"
commandLine "${android.ndkDirectory}/ndk-build", '-j', Runtime.runtime.availableProcessors()
doLast {
copy {
from "${workingDir} + /../libs"
into 'libs'
}
}
}
task cleanStrongSwanNative(type: Exec) {
workingDir "${relativePathToStrongswan}"
commandLine "${android.ndkDirectory}/ndk-build", 'clean'
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildStrongSwanNative
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
clean.dependsOn 'cleanStrongSwanNative'
// externalNativeBuild {
// cmake {
@@ -261,7 +261,7 @@ object BaseService {
fun stopRunner(restart: Boolean = false, msg: String? = null) {
if (data.state == State.Stopping) return
// channge the state
// change the state
data.changeState(State.Stopping)
GlobalScope.launch(Dispatchers.Main.immediate) {
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
@@ -30,7 +30,7 @@ object Key {
const val id = "profileId"
const val name = "profileName"
const val individual = "Proxyed"
const val individual = "Proxied"
const val serviceMode = "serviceMode"
const val modeProxy = "proxy"
@@ -835,7 +835,7 @@ public final class Ed25519 {
}
/**
* Decodes {@code s} into an extented projective point.
* Decodes {@code s} into an extended projective point.
* See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3
*/
private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException {
@@ -973,7 +973,7 @@ public final class Ed25519 {
*
* <p>NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong
* results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the
* values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must
* values in a[10..19] or b[10..19] aren't swapped, and all values in a[0..9],b[0..9] must
* have magnitude less than Integer.MAX_VALUE.
*/
static void copyConditional(long[] a, long[] b, int icopy) {
@@ -0,0 +1,761 @@
package org.amnezia.vpn
import android.annotation.TargetApi
import android.content.pm.PackageManager
import android.net.VpnService
import android.net.VpnService.Builder
import android.os.Build
import android.os.ParcelFileDescriptor
import android.system.OsConstants
import java.lang.Runnable
import org.amnezia.vpn.ikev2.VpnProfile
import org.amnezia.vpn.ikev2.VpnProfile.SelectedAppsHandling
import org.amnezia.vpn.ikev2.VpnType
import org.amnezia.vpn.ikev2.VpnType.VpnTypeFeature
import org.amnezia.vpn.ikev2.utils.IPRange
import org.amnezia.vpn.ikev2.utils.IPRangeSet
import org.amnezia.vpn.ikev2.utils.Utils
import org.amnezia.vpn.ikev2.utils.Constants
import org.amnezia.vpn.ikev2.utils.SettingsWriter
import org.amnezia.vpn.ikev2.utils.SimpleFetcher
const val PACKAGE_NAME = "org.amnezia.vpn"
const val LOG_FILE = "charon.log"
const val TAG = "amnezia_ikev2"
class IKEv2Thread(
val vpnServiceBuilder: android.net.VpnService.Builder,
val filesDirAbsolutePath: String
): Runnable {
private val mBuilderAdapter: BuilderAdapter = BuilderAdapter(vpnServiceBuilder)
private var mCurrentProfile: VpnProfile? = null
private var mNextProfile: VpnProfile? = null
@kotlin.jvm.Volatile
private var mCurrentCertificateAlias: String? = null
@kotlin.jvm.Volatile
private var mCurrentUserCertificateAlias: String? = null
@kotlin.jvm.Volatile
private var mProfileUpdated = false
@kotlin.jvm.Volatile
private var mTerminate = false
@kotlin.jvm.Volatile
private var mIsDisconnecting = false
private val mLogFile: String
private val mAppDir: String
init {
mLogFile = filesDirAbsolutePath + java.io.File.separator + LOG_FILE
mAppDir = filesDirAbsolutePath
}
fun setNextProfile(profile: VpnProfile) {
// TODO: take a look at "vpnprofileimportactivity" in starongswan repo
// to understand how to pass the profile object before starting of ikev2 tunnel
synchronized(this) {
mNextProfile = profile
mProfileUpdated = true
notifyAll()
}
}
override fun run() {
while (true) {
synchronized(this) {
try {
while (!mProfileUpdated) {
Log.i(TAG, "charon contunue")
continue
}
mProfileUpdated = false
stopCurrentConnection()
if (mNextProfile == null) {
setState(State.DISABLED)
if (mTerminate) {
return@synchronized
}
} else {
mCurrentProfile = mNextProfile
mNextProfile = null
val currentProfile = mCurrentProfile
/* store this in a separate (volatile) variable to avoid
* a possible deadlock during deinitialization */
mCurrentCertificateAlias = currentProfile!!.certificateAlias
mCurrentUserCertificateAlias = currentProfile!!.userCertificateAlias
startConnection(currentProfile)
mIsDisconnecting = false
SimpleFetcher.enable()
addNotification()
mBuilderAdapter.setProfile(currentProfile)
val flags: Int = currentProfile!!.flags
if (initializeCharon(
mBuilderAdapter,
mLogFile,
mAppDir,
currentProfile.vpnType!!.has(VpnTypeFeature.BYOD),
flags and VpnProfile.FLAGS_IPv6_TRANSPORT !== 0
)
) {
Log.i(TAG, "charon started")
if (currentProfile.vpnType!!.has(VpnTypeFeature.USER_PASS) &&
currentProfile.password == null
) { /* this can happen if Always-on VPN is enabled with an incomplete profile */
setError(ErrorState.PASSWORD_MISSING)
return@synchronized
}
val writer = SettingsWriter()
writer.setValue("global.language", java.util.Locale.getDefault().getLanguage())
writer.setValue("global.mtu", currentProfile.MTU)
writer.setValue("global.nat_keepalive", currentProfile.NATKeepAlive)
writer.setValue("global.rsa_pss", flags and VpnProfile.FLAGS_RSA_PSS !== 0)
writer.setValue("global.crl", flags and VpnProfile.FLAGS_DISABLE_CRL === 0)
writer.setValue("global.ocsp", flags and VpnProfile.FLAGS_DISABLE_OCSP === 0)
writer.setValue("connection.type", currentProfile.vpnType!!.identifier)
writer.setValue("connection.server", currentProfile.gateway)
writer.setValue("connection.port", currentProfile.port)
writer.setValue("connection.username", currentProfile.username)
writer.setValue("connection.password", currentProfile.password)
writer.setValue("connection.local_id", currentProfile.localId)
writer.setValue("connection.remote_id", currentProfile.remoteId)
writer.setValue("connection.certreq", flags and VpnProfile.FLAGS_SUPPRESS_CERT_REQS === 0)
writer.setValue("connection.strict_revocation", flags and VpnProfile.FLAGS_STRICT_REVOCATION !== 0)
writer.setValue("connection.ike_proposal", currentProfile.ikeProposal)
writer.setValue("connection.esp_proposal", currentProfile.espProposal)
initiate(writer.serialize())
} else {
Log.e(TAG, "failed to start charon")
setError(ErrorState.GENERIC_ERROR)
setState(State.DISABLED)
mCurrentProfile = null
}
}
} catch (ex: java.lang.InterruptedException) {
stopCurrentConnection()
setState(State.DISABLED)
}
}
}
}
/**
* Notify the state service about a new connection attempt.
* Called by the handler thread.
*
* @param profile currently active VPN profile
*/
private fun startConnection(profile: VpnProfile) {
// synchronized(mServiceLock) {
// if (mService != null) {
// mService.startConnection(profile)
// }
// }
}
/**
* Stop any existing connection by deinitializing charon.
*/
private fun stopCurrentConnection() {
synchronized(this) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mNextProfile?.let {
mBuilderAdapter.setProfile(it)
mBuilderAdapter.establishBlocking()
}
}
if (mCurrentProfile != null) {
setState(State.DISCONNECTING)
mIsDisconnecting = true
SimpleFetcher.disable()
deinitializeCharon()
Log.i(TAG, "charon stopped")
mCurrentProfile = null
if (mNextProfile == null) { /* only do this if we are not connecting to another profile */
removeNotification()
mBuilderAdapter.closeBlocking()
}
}
}
}
/**
* Update the current VPN state on the state service. Called by the handler
* thread and any of charon's threads.
*
* @param state current state
*/
private fun setState(state: State) {
// synchronized(mServiceLock) {
// if (mService != null) {
// mService.setState(state)
// }
// }
}
/**
* Initialization of charon, provided by libandroidbridge.so
*
* @param builder BuilderAdapter for this connection
* @param logfile absolute path to the logfile
* @param appdir absolute path to the data directory of the app
* @param byod enable BYOD features
* @param ipv6 enable IPv6 transport
* @return TRUE if initialization was successful
*/
external fun initializeCharon(
builder: BuilderAdapter?,
logfile: String?,
appdir: String?,
byod: Boolean,
ipv6: Boolean
): Boolean
/**
* Deinitialize charon, provided by libandroidbridge.so
*/
external fun deinitializeCharon()
/**
* Initiate VPN, provided by libandroidbridge.so
*/
external fun initiate(config: String?)
/**
* Adapter for VpnService.Builder which is used to access it safely via JNI.
* There is a corresponding C object to access it from native code.
*/
class BuilderAdapter(val builder: VpnService.Builder) {
private lateinit var mProfile: VpnProfile
private lateinit var mBuilder: VpnService.Builder
private lateinit var mCache: BuilderCache
private lateinit var mEstablishedCache: BuilderCache
private val mDropper: PacketDropper = PacketDropper()
init {
mBuilder = builder
}
@kotlin.jvm.Synchronized
fun setProfile(profile: VpnProfile) {
mProfile = profile
mBuilder = createBuilder(mProfile.name ?: "")
mCache = BuilderCache(mProfile)
}
private fun createBuilder(name: String): VpnService.Builder {
// val builder: VpnService.Builder = Builder()
mBuilder.setSession(name)
/* even though the option displayed in the system dialog says "Configure"
* we just use our main Activity */
// val context: Context = getApplicationContext()
// val intent = Intent(context, MainActivity::class.java)
// var flags: Int = PendingIntent.FLAG_UPDATE_CURRENT
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// flags = flags or PendingIntent.FLAG_IMMUTABLE
// }
// val pending: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
// builder.setConfigureIntent(pending)
/* mark all VPN connections as unmetered (default changed for Android 10) */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mBuilder.setMetered(false)
}
return mBuilder
}
@kotlin.jvm.Synchronized
fun addAddress(address: String?, prefixLength: Int): Boolean {
try {
mCache.addAddress(address, prefixLength)
} catch (ex: java.lang.IllegalArgumentException) {
return false
}
return true
}
@kotlin.jvm.Synchronized
fun addDnsServer(address: String?): Boolean {
try {
mCache.addDnsServer(address)
} catch (ex: java.lang.IllegalArgumentException) {
return false
}
return true
}
@kotlin.jvm.Synchronized
fun addRoute(address: String?, prefixLength: Int): Boolean {
try {
mCache.addRoute(address, prefixLength)
} catch (ex: java.lang.IllegalArgumentException) {
return false
}
return true
}
@kotlin.jvm.Synchronized
fun addSearchDomain(domain: String): Boolean {
try {
mBuilder.addSearchDomain(domain)
} catch (ex: java.lang.IllegalArgumentException) {
return false
}
return true
}
@kotlin.jvm.Synchronized
fun setMtu(mtu: Int): Boolean {
try {
mCache.setMtu(mtu)
} catch (ex: java.lang.IllegalArgumentException) {
return false
}
return true
}
@kotlin.jvm.Synchronized
private fun establishIntern(): ParcelFileDescriptor? {
val fd: ParcelFileDescriptor?
try {
mCache.applyData(mBuilder)
fd = mBuilder.establish()
if (fd != null) {
closeBlocking()
}
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
return null
}
if (fd == null) {
return null
}
/* now that the TUN device is created we don't need the current
* builder anymore, but we might need another when reestablishing */
mBuilder = createBuilder(mProfile.name ?: "")
mEstablishedCache = mCache
mCache = BuilderCache(mProfile)
return fd
}
@kotlin.jvm.Synchronized
fun establish(): Int {
val fd: ParcelFileDescriptor? = establishIntern()
return if (fd != null) fd.detachFd() else -1
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@kotlin.jvm.Synchronized
fun establishBlocking() {
/* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
mCache.addAddress("172.16.252.1", 32)
mCache.addAddress("fd00::fd02:1", 128)
mCache.addRoute("0.0.0.0", 0)
mCache.addRoute("::", 0)
/* set DNS servers to avoid DNS leak later */
mBuilder.addDnsServer("8.8.8.8")
mBuilder.addDnsServer("2001:4860:4860::8888")
/* use blocking mode to simplify packet dropping */
mBuilder.setBlocking(true)
val fd: ParcelFileDescriptor? = establishIntern()
if (fd != null) {
mDropper.start(fd)
}
}
@kotlin.jvm.Synchronized
fun closeBlocking() {
mDropper.stop()
}
@kotlin.jvm.Synchronized
fun establishNoDns(): Int {
val fd: ParcelFileDescriptor?
if (mEstablishedCache == null) {
return -1
}
fd = try {
val builder: VpnService.Builder = createBuilder(mProfile.name ?: "")
mEstablishedCache.applyData(builder)
builder.establish()
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
return -1
}
return if (fd == null) {
-1
} else fd.detachFd()
}
private inner class PacketDropper : java.lang.Runnable {
private var mFd: ParcelFileDescriptor? = null
private lateinit var mThread: java.lang.Thread
fun start(fd: ParcelFileDescriptor) {
mFd = fd
mThread = java.lang.Thread(this)
mThread.start()
}
fun stop() {
if (mFd != null) {
try {
mThread.interrupt()
mThread.join()
mFd?.close()
} catch (e: java.lang.InterruptedException) {
e.printStackTrace()
} catch (e: java.io.IOException) {
e.printStackTrace()
}
mFd = null
}
}
@kotlin.jvm.Synchronized
override fun run() {
try {
val plain: java.io.FileInputStream = java.io.FileInputStream(mFd?.getFileDescriptor())
val packet: java.nio.ByteBuffer = java.nio.ByteBuffer.allocate(mCache.mMtu)
while (true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { /* just read and ignore all data, regular read() is not interruptible */
val len: Int = plain.getChannel().read(packet)
packet.clear()
if (len < 0) {
break
}
} else { /* this is rather ugly but on older platforms not even the NIO version of read() is interruptible */
var wait = true
if (plain.available() > 0) {
val len: Int = plain.read(packet.array())
packet.clear()
if (len < 0 || java.lang.Thread.interrupted()) {
break
}
/* check again right away, there may be another packet */wait = false
}
if (wait) {
java.lang.Thread.sleep(250)
}
}
}
} catch (e: java.nio.channels.ClosedByInterruptException) {
/* regular interruption */
} catch (e: java.lang.InterruptedException) {
} catch (e: java.io.IOException) {
e.printStackTrace()
}
}
}
}
/**
* Cache non DNS related information so we can recreate the builder without
* that information when reestablishing IKE_SAs
*/
class BuilderCache(profile: VpnProfile) {
val mAddresses: MutableList<IPRange> = java.util.ArrayList<IPRange>()
val mRoutesIPv4: MutableList<IPRange> = java.util.ArrayList<IPRange>()
val mRoutesIPv6: MutableList<IPRange> = java.util.ArrayList<IPRange>()
val mIncludedSubnetsv4: IPRangeSet = IPRangeSet()
val mIncludedSubnetsv6: IPRangeSet = IPRangeSet()
val mExcludedSubnets: IPRangeSet
val mSplitTunneling: Int
val mAppHandling: SelectedAppsHandling
val mSelectedApps: java.util.SortedSet<String>
val mDnsServers: MutableList<java.net.InetAddress> = java.util.ArrayList<java.net.InetAddress>()
var mMtu: Int
var mIPv4Seen = false
var mIPv6Seen = false
var mDnsServersConfigured = false
init {
val included: IPRangeSet = IPRangeSet.fromString(profile?.includedSubnets)
for (range in included) {
if (range.getFrom() is java.net.Inet4Address) {
mIncludedSubnetsv4.add(range)
} else if (range.getFrom() is java.net.Inet6Address) {
mIncludedSubnetsv6.add(range)
}
}
mExcludedSubnets = IPRangeSet.fromString(profile.excludedSubnets ?: "")
val splitTunneling: Int? = profile.splitTunneling
mSplitTunneling = splitTunneling ?: 0
var appHandling: SelectedAppsHandling = profile.selectedAppsHandling
mSelectedApps = profile.selectedAppsSet
when (appHandling) {
SelectedAppsHandling.SELECTED_APPS_DISABLE -> {
appHandling = SelectedAppsHandling.SELECTED_APPS_EXCLUDE
mSelectedApps.clear()
mSelectedApps.add(PACKAGE_NAME)
}
SelectedAppsHandling.SELECTED_APPS_EXCLUDE -> {
mSelectedApps.add(PACKAGE_NAME)
}
SelectedAppsHandling.SELECTED_APPS_ONLY -> {
mSelectedApps.remove(PACKAGE_NAME)
}
}
mAppHandling = appHandling
val dnsServers = profile.dnsServers
if (dnsServers != null) {
for (server in dnsServers.split("\\s+")) {
try {
mDnsServers.add(Utils.parseInetAddress(server))
recordAddressFamily(server)
mDnsServersConfigured = true
} catch (e: java.net.UnknownHostException) {
e.printStackTrace()
}
}
}
/* set a default MTU, will be set by the daemon for regular interfaces */
val mtu: Int? = profile.MTU
mMtu = mtu ?: Constants.MTU_MAX
}
fun addAddress(address: String?, prefixLength: Int) {
try {
mAddresses.add(IPRange(address, prefixLength))
recordAddressFamily(address)
} catch (ex: java.net.UnknownHostException) {
ex.printStackTrace()
}
}
fun addDnsServer(address: String?) {
/* ignore received DNS servers if any were configured */
if (mDnsServersConfigured) {
return
}
try {
mDnsServers.add(Utils.parseInetAddress(address))
recordAddressFamily(address)
} catch (e: java.net.UnknownHostException) {
e.printStackTrace()
}
}
fun addRoute(address: String?, prefixLength: Int) {
try {
if (isIPv6(address)) {
mRoutesIPv6.add(IPRange(address, prefixLength))
} else {
mRoutesIPv4.add(IPRange(address, prefixLength))
}
} catch (ex: java.net.UnknownHostException) {
ex.printStackTrace()
}
}
fun setMtu(mtu: Int) {
mMtu = mtu
}
fun recordAddressFamily(address: String?) {
try {
if (isIPv6(address)) {
mIPv6Seen = true
} else {
mIPv4Seen = true
}
} catch (ex: java.net.UnknownHostException) {
ex.printStackTrace()
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun applyData(builder: VpnService.Builder) {
for (address in mAddresses) {
builder.addAddress(address.getFrom(), address.getPrefix())
}
for (server in mDnsServers) {
builder.addDnsServer(server)
}
/* add routes depending on whether split tunneling is allowed or not,
* that is, whether we have to handle and block non-VPN traffic */
if (mSplitTunneling and VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 === 0) {
if (mIPv4Seen) { /* split tunneling is used depending on the routes and configuration */
val ranges = IPRangeSet()
if (mIncludedSubnetsv4.size() > 0) {
ranges.add(mIncludedSubnetsv4)
} else {
ranges.addAll(mRoutesIPv4)
}
ranges.remove(mExcludedSubnets)
for (subnet in ranges.subnets()) {
try {
builder.addRoute(subnet.getFrom(), subnet.getPrefix())
} catch (e: java.lang.IllegalArgumentException) { /* some Android versions don't seem to like multicast addresses here,
* ignore it for now */
if (!subnet.getFrom().isMulticastAddress()) {
throw e
}
}
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { /* allow traffic that would otherwise be blocked to bypass the VPN */
builder.allowFamily(OsConstants.AF_INET)
}
} else if (mIPv4Seen) { /* only needed if we've seen any addresses. otherwise, traffic
* is blocked by default (we also install no routes in that case) */
builder.addRoute("0.0.0.0", 0)
}
/* same thing for IPv6 */
if (mSplitTunneling and VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 === 0) {
if (mIPv6Seen) {
val ranges = IPRangeSet()
if (mIncludedSubnetsv6.size() > 0) {
ranges.add(mIncludedSubnetsv6)
} else {
ranges.addAll(mRoutesIPv6)
}
ranges.remove(mExcludedSubnets)
for (subnet in ranges.subnets()) {
try {
builder.addRoute(subnet.getFrom(), subnet.getPrefix())
} catch (e: java.lang.IllegalArgumentException) {
if (!subnet.getFrom().isMulticastAddress()) {
throw e
}
}
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.allowFamily(OsConstants.AF_INET6)
}
} else if (mIPv6Seen) {
builder.addRoute("::", 0)
}
/* apply selected applications */
if (mSelectedApps.size > 0 &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
) {
when (mAppHandling) {
SelectedAppsHandling.SELECTED_APPS_EXCLUDE -> for (app in mSelectedApps) {
try {
builder.addDisallowedApplication(app)
} catch (e: PackageManager.NameNotFoundException) {
// possible if not configured via GUI or app was uninstalled
}
}
SelectedAppsHandling.SELECTED_APPS_ONLY -> for (app in mSelectedApps) {
try {
builder.addAllowedApplication(app)
} catch (e: PackageManager.NameNotFoundException) {
// possible if not configured via GUI or app was uninstalled
}
}
else -> {}
}
}
builder.setMtu(mMtu)
}
@kotlin.Throws(java.net.UnknownHostException::class)
private fun isIPv6(address: String?): Boolean {
val addr: java.net.InetAddress = Utils.parseInetAddress(address)
if (addr is java.net.Inet4Address) {
return false
} else if (addr is java.net.Inet6Address) {
return true
}
return false
}
}
/**
* Function called via JNI to determine information about the Android version.
*/
private fun getAndroidVersion(): String? {
var version = "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
version += "/" + Build.VERSION.SECURITY_PATCH
}
return version
}
/**
* Function called via JNI to determine information about the device.
*/
private fun getDeviceString(): String? {
return ((Build.MODEL + " - " + Build.BRAND).toString() + "/" + Build.PRODUCT).toString() + "/" + Build.MANUFACTURER
}
/**
* Add a permanent notification while we are connected to avoid the service getting killed by
* the system when low on memory.
*/
private fun addNotification() {
// mHandler.post(object : java.lang.Runnable {
// fun run() {
// mShowNotification = true
// startForeground(
// org.strongswan.android.logic.CharonVpnService.VPN_STATE_NOTIFICATION_ID,
// buildNotification(false)
// )
// }
// })
}
/**
* Remove the permanent notification.
*/
private fun removeNotification() {
// mHandler.post(object : java.lang.Runnable {
// fun run() {
// mShowNotification = false
// stopForeground(true)
// }
// })
}
/**
* Set an error on the state service. Called by the handler thread and any
* of charon's threads.
*
* @param error error state
*/
private fun setError(error: ErrorState) {
// synchronized(mServiceLock) {
// if (mService != null) {
// mService.setError(error)
// }
// }
}
enum class State {
DISABLED, CONNECTING, CONNECTED, DISCONNECTING
}
enum class ErrorState {
NO_ERROR, AUTH_FAILED, PEER_AUTH_FAILED, LOOKUP_FAILED, UNREACHABLE, GENERIC_ERROR, PASSWORD_MISSING, CERTIFICATE_UNAVAILABLE
}
}
@@ -27,7 +27,7 @@ object NotificationUtil {
* Parcel - Gets called from AndroidController.cpp
*/
fun update(data: Parcel) {
// [data] is here a json containing the noification content
// [data] is here a json containing the notification content
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
val content = JSONObject(json)
@@ -35,10 +35,10 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
private var bytesOutIndex = -1
init {
findConfigIndicies()
findConfigIndices()
}
private fun findConfigIndicies() {
private fun findConfigIndices() {
val n: Int = stats_n()
for (i in 0 until n) {
+1 -1
View File
@@ -29,7 +29,7 @@ object Prefs {
return sharedPreferences
} catch (e: Exception) {
Log.e("Android-Prefs", "Getting Encryption Storage failed, plaintext fallback")
return context.getSharedPreferences("com.amnezia.vpn.prefrences", Context.MODE_PRIVATE)
return context.getSharedPreferences("com.amnezia.vpn.preferences", Context.MODE_PRIVATE)
}
}
}
@@ -11,14 +11,14 @@ import android.util.Log as nativeLog
/*
* Drop in replacement for android.util.Log
* Also stores a copy of all logs in tmp/mozilla_deamon_logs.txt
* Also stores a copy of all logs in tmp/mozilla_daemon_logs.txt
*/
class Log {
val LOG_MAX_FILE_SIZE = 204800
private var file: File
private constructor(context: Context) {
val tempDIR = context.cacheDir
file = File(tempDIR, "mozilla_deamon_logs.txt")
file = File(tempDIR, "mozilla_daemon_logs.txt")
if (file.length() > LOG_MAX_FILE_SIZE) {
file.writeText("")
}
@@ -46,7 +46,7 @@ class Log {
if (!BuildConfig.DEBUG) { return; }
nativeLog.e(tag, message)
}
// Only Prints && Loggs when in debug, noop in release.
// Only Prints && Logs when in debug, noop in release.
fun sensitive(tag: String, message: String?) {
if (!BuildConfig.DEBUG) { return; }
if (message == null) { return; }
@@ -150,6 +150,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private var mOpenVPNThreadv3: OpenVPNThreadv3? = null
var currentTunnelHandle = -1
private var ikev2VpnThread: IKEv2Thread? = null
private var intent: Intent? = null
private var flags = 0
private var startId = 0
@@ -165,6 +167,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
Log.e(tag, "Wireguard Version ${wgVersion()}")
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
mAlreadyInitialised = true
ikev2VpnThread = IKEv2Thread(mbuilder, getFilesDir().getAbsolutePath())
}
override fun onCreate() {
@@ -384,8 +387,11 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
startShadowsocks()
startTest()
}
"ikev2" -> {
startIPSEC()
}
else -> {
Log.e(tag, "No protocol")
Log.e(tag, "Unknown protocol ($mProtocol)")
return 0
}
}
@@ -393,6 +399,18 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
return 1
}
private fun startIPSEC() {
Log.v(tag, "start ipsec")
ikev2VpnThread = IKEv2Thread(mbuilder, getFilesDir().getAbsolutePath())
// TODO: pass the "vpnprofile" instance as a parameter
// ikev2VpnThread.setNextProfile()
Thread({
ikev2VpnThread?.run()
}).start()
}
fun establish(): ParcelFileDescriptor? {
Log.v(tag, "Aman: establish....................")
mbuilder.allowFamily(OsConstants.AF_INET)
@@ -548,7 +566,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
* Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidVpnProtocol.cpp
*/
private fun buildWireugardConfig(obj: JSONObject): Config {
private fun buildWireguardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder()
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
val config = parseConfigData(wireguardConfigData.getString("config"))
@@ -697,7 +715,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
}
private fun startWireGuard() {
val wireguard_conf = buildWireugardConfig(mConfig!!)
val wireguard_conf = buildWireguardConfig(mConfig!!)
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up")
@@ -50,7 +50,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
when (code) {
ACTIONS.activate -> {
try {
Log.i(tag, "Activiation Requested, parsing Config")
Log.i(tag, "Activation Requested, parsing Config")
// [data] is here a json containing the wireguard/openvpn conf
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
@@ -0,0 +1,111 @@
package org.amnezia.vpn.ikev2
import android.text.TextUtils
class VpnProfile : kotlin.Cloneable {
var name: String? = null
var gateway: String? = null
var username: String? = null
var password: String? = null
var certificateAlias: String? = null
var userCertificateAlias: String? = null
var remoteId: String? = null
var localId: String? = null
var excludedSubnets: String? = null
var includedSubnets: String? = null
var selectedApps: String? = null
var ikeProposal: String? = null
var espProposal: String? = null
var dnsServers: String? = null
var MTU: Int? = null
var port: Int? = null
var splitTunneling: Int? = null
var NATKeepAlive: Int? = null
private var mFlags: Int? = null
var selectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE
private var mVpnType: VpnType? = null
private var mUUID: java.util.UUID?
var id: Long = -1
enum class SelectedAppsHandling(val value: Int) {
SELECTED_APPS_DISABLE(0), SELECTED_APPS_EXCLUDE(1), SELECTED_APPS_ONLY(2);
}
init {
mUUID = java.util.UUID.randomUUID()
}
var uUID: java.util.UUID?
get() = mUUID
set(uuid) {
mUUID = uuid
}
var vpnType: VpnType?
get() = mVpnType
set(type) {
mVpnType = type
}
fun setSelectedApps(selectedApps: java.util.SortedSet<String?>) {
this.selectedApps = if (selectedApps.size > 0) TextUtils.join(" ", selectedApps) else null
}
val selectedAppsSet: java.util.SortedSet<String>
get() {
val set: java.util.TreeSet<String> = java.util.TreeSet<String>()
if (!TextUtils.isEmpty(selectedApps)) {
set.addAll(
java.util.Arrays.asList<String>(
*selectedApps!!.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()))
}
return set
}
fun setSelectedAppsHandling(value: Int) {
selectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE
for (handling in VpnProfile.SelectedAppsHandling.values()) {
if (handling.value == value) {
selectedAppsHandling = handling
break
}
}
}
var flags: Int = 0
override fun toString(): String {
return name!!
}
override fun equals(o: Any?): Boolean {
if (o != null && o is VpnProfile) {
val other = o
return if (mUUID != null && other.uUID != null) {
mUUID == other.uUID
} else id == other.id
}
return false
}
override fun clone(): VpnProfile {
return try {
super.clone() as VpnProfile
} catch (e: java.lang.CloneNotSupportedException) {
throw java.lang.AssertionError()
}
}
companion object {
/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
const val SPLIT_TUNNELING_BLOCK_IPV4 = 1
const val SPLIT_TUNNELING_BLOCK_IPV6 = 2
const val FLAGS_SUPPRESS_CERT_REQS = 1 shl 0
const val FLAGS_DISABLE_CRL = 1 shl 1
const val FLAGS_DISABLE_OCSP = 1 shl 2
const val FLAGS_STRICT_REVOCATION = 1 shl 3
const val FLAGS_RSA_PSS = 1 shl 4
const val FLAGS_IPv6_TRANSPORT = 1 shl 5
}
}
@@ -0,0 +1,79 @@
package org.amnezia.vpn.ikev2
enum class VpnType(
/**
* The identifier used to store this value in the database
* @return identifier
*/
val identifier: String, features: java.util.EnumSet<VpnTypeFeature>
) {
/* the order here must match the items in R.array.vpn_types */
IKEV2_EAP("ikev2-eap", java.util.EnumSet.of(VpnTypeFeature.USER_PASS)), IKEV2_CERT(
"ikev2-cert", java.util.EnumSet.of(
VpnTypeFeature.CERTIFICATE
)
),
IKEV2_CERT_EAP(
"ikev2-cert-eap",
java.util.EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)
),
IKEV2_EAP_TLS(
"ikev2-eap-tls", java.util.EnumSet.of(
VpnTypeFeature.CERTIFICATE
)
),
IKEV2_BYOD_EAP("ikev2-byod-eap", java.util.EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD));
/**
* Features of a VPN type.
*/
enum class VpnTypeFeature {
/** client certificate is required */
CERTIFICATE,
/** username and password are required */
USER_PASS,
/** enable BYOD features */
BYOD
}
private val mFeatures: java.util.EnumSet<VpnTypeFeature>
/**
* Enum which provides additional information about the supported VPN types.
*
* @param id identifier used to store and transmit this specific type
* @param features of the given VPN type
* @param certificate true if a client certificate is required
*/
init {
mFeatures = features
}
/**
* Checks whether a feature is supported/required by this type of VPN.
*
* @return true if the feature is supported/required
*/
fun has(feature: VpnTypeFeature?): Boolean {
return mFeatures.contains(feature)
}
companion object {
/**
* Get the enum entry with the given identifier.
*
* @param identifier get the enum entry with this identifier
* @return the enum entry, or the default if not found
*/
fun fromIdentifier(identifier: String): VpnType {
for (type in VpnType.values()) {
if (identifier == type.identifier) {
return type
}
}
return IKEV2_EAP
}
}
}
@@ -0,0 +1,272 @@
/*
* Copyright (C) 2013 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.nio.ByteBuffer;
/**
* Very similar to ByteBuffer (although with a stripped interface) but it
* automatically resizes the underlying buffer.
*/
public class BufferedByteWriter
{
/**
* The underlying byte buffer
*/
private byte[] mBuffer;
/**
* ByteBuffer used as wrapper around the buffer to easily convert values
*/
private ByteBuffer mWriter;
/**
* Create a writer with a default initial capacity
*/
public BufferedByteWriter()
{
this(0);
}
/**
* Create a writer with the given initial capacity (helps avoid expensive
* resizing if known).
* @param capacity initial capacity
*/
public BufferedByteWriter(int capacity)
{
capacity = capacity > 4 ? capacity : 32;
mBuffer = new byte[capacity];
mWriter = ByteBuffer.wrap(mBuffer);
}
/**
* Ensure that there is enough space available to write the requested
* number of bytes. If necessary the internal buffer is resized.
* @param required required number of bytes
*/
private void ensureCapacity(int required)
{
if (mWriter.remaining() >= required)
{
return;
}
byte[] buffer = new byte[(mBuffer.length + required) * 2];
System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position());
mBuffer = buffer;
ByteBuffer writer = ByteBuffer.wrap(buffer);
writer.position(mWriter.position());
mWriter = writer;
}
/**
* Write the given byte array to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put(byte[] value)
{
ensureCapacity(value.length);
mWriter.put(value);
return this;
}
/**
* Write the given byte to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put(byte value)
{
ensureCapacity(1);
mWriter.put(value);
return this;
}
/**
* Write the 8-bit length of the given data followed by the data itself
* @param value
* @return the writer
*/
public BufferedByteWriter putLen8(byte[] value)
{
ensureCapacity(1 + value.length);
mWriter.put((byte)value.length);
mWriter.put(value);
return this;
}
/**
* Write the 16-bit length of the given data followed by the data itself
* @param value
* @return the writer
*/
public BufferedByteWriter putLen16(byte[] value)
{
ensureCapacity(2 + value.length);
mWriter.putShort((short)value.length);
mWriter.put(value);
return this;
}
/**
* Write the given short value (16-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put16(byte value)
{
return this.put16((short)(value & 0xFF));
}
/**
* Write the given short value (16-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put16(short value)
{
ensureCapacity(2);
mWriter.putShort(value);
return this;
}
/**
* Write 24-bit of the given value in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put24(byte value)
{
ensureCapacity(3);
mWriter.putShort((short)0);
mWriter.put(value);
return this;
}
/**
* Write 24-bit of the given value in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put24(short value)
{
ensureCapacity(3);
mWriter.put((byte)0);
mWriter.putShort(value);
return this;
}
/**
* Write 24-bit of the given value in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put24(int value)
{
ensureCapacity(3);
mWriter.put((byte)(value >> 16));
mWriter.putShort((short)value);
return this;
}
/**
* Write the given int value (32-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put32(byte value)
{
return put32(value & 0xFF);
}
/**
* Write the given int value (32-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put32(short value)
{
return put32(value & 0xFFFF);
}
/**
* Write the given int value (32-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put32(int value)
{
ensureCapacity(4);
mWriter.putInt(value);
return this;
}
/**
* Write the given long value (64-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put64(byte value)
{
return put64(value & 0xFFL);
}
/**
* Write the given long value (64-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put64(short value)
{
return put64(value & 0xFFFFL);
}
/**
* Write the given long value (64-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put64(int value)
{
return put64(value & 0xFFFFFFFFL);
}
/**
* Write the given long value (64-bit) in big-endian order to the buffer
* @param value
* @return the writer
*/
public BufferedByteWriter put64(long value)
{
ensureCapacity(8);
mWriter.putLong(value);
return this;
}
/**
* Convert the internal buffer to a new byte array.
* @return byte array
*/
public byte[] toByteArray()
{
int length = mWriter.position();
byte[] bytes = new byte[length];
System.arraycopy(mBuffer, 0, bytes, 0, length);
return bytes;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2016-2020 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
public final class Constants
{
/**
* Intent action used to notify about changes to the VPN profiles
*/
public static final String VPN_PROFILES_CHANGED = "org.strongswan.android.VPN_PROFILES_CHANGED";
/**
* Used in the intent above to notify about edits or inserts of a VPN profile (long)
*/
public static final String VPN_PROFILES_SINGLE = "org.strongswan.android.VPN_PROFILES_SINGLE";
/**
* Used in the intent above to notify about the deletion of multiple VPN profiles (array of longs)
*/
public static final String VPN_PROFILES_MULTIPLE = "org.strongswan.android.VPN_PROFILES_MULTIPLE";
/**
* Limits for MTU
*/
public static final int MTU_MAX = 1500;
public static final int MTU_MIN = 1280;
/**
* Limits for NAT-T keepalive
*/
public static final int NAT_KEEPALIVE_MAX = 120;
public static final int NAT_KEEPALIVE_MIN = 10;
/**
* Preference key for default VPN profile
*/
public static final String PREF_DEFAULT_VPN_PROFILE = "pref_default_vpn_profile";
/**
* Value used to signify that the most recently used profile should be used as default
*/
public static final String PREF_DEFAULT_VPN_PROFILE_MRU = "pref_default_vpn_profile_mru";
/**
* Preference key to store the most recently used VPN profile
*/
public static final String PREF_MRU_VPN_PROFILE = "pref_mru_vpn_profile";
/**
* Preference key to store whether the user permanently dismissed our warning to add the app to the power whitelist
*/
public static final String PREF_IGNORE_POWER_WHITELIST = "pref_ignore_power_whitelist";
}
@@ -0,0 +1,510 @@
/*
* Copyright (C) 2012-2017 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
/**
* Class that represents a range of IP addresses. This range could be a proper subnet, but that's
* not necessarily the case (see {@code getPrefix} and {@code toSubnets}).
*/
public class IPRange implements Comparable<IPRange>
{
private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
private byte[] mFrom;
private byte[] mTo;
private Integer mPrefix;
/**
* Determine if the range is a proper subnet and, if so, what the network prefix is.
*/
private void determinePrefix()
{
boolean matching = true;
mPrefix = mFrom.length * 8;
for (int i = 0; i < mFrom.length; i++)
{
for (int bit = 0; bit < 8; bit++)
{
if (matching)
{
if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit]))
{
mPrefix = (i * 8) + bit;
matching = false;
}
}
else
{
if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0)
{
mPrefix = null;
return;
}
}
}
}
}
private IPRange(byte[] from, byte[] to)
{
mFrom = from;
mTo = to;
determinePrefix();
}
public IPRange(String from, String to) throws UnknownHostException
{
this(Utils.parseInetAddress(from), Utils.parseInetAddress(to));
}
public IPRange(InetAddress from, InetAddress to)
{
initializeFromRange(from, to);
}
private void initializeFromRange(InetAddress from, InetAddress to)
{
byte[] fa = from.getAddress(), ta = to.getAddress();
if (fa.length != ta.length)
{
throw new IllegalArgumentException("Invalid range");
}
if (compareAddr(fa, ta) < 0)
{
mFrom = fa;
mTo = ta;
}
else
{
mTo = fa;
mFrom = ta;
}
determinePrefix();
}
public IPRange(String base, int prefix) throws UnknownHostException
{
this(Utils.parseInetAddress(base), prefix);
}
public IPRange(InetAddress base, int prefix)
{
this(base.getAddress(), prefix);
}
private IPRange(byte[] from, int prefix)
{
initializeFromCIDR(from, prefix);
}
private void initializeFromCIDR(byte[] from, int prefix)
{
if (from.length != 4 && from.length != 16)
{
throw new IllegalArgumentException("Invalid address");
}
if (prefix < 0 || prefix > from.length * 8)
{
throw new IllegalArgumentException("Invalid prefix");
}
byte[] to = from.clone();
byte mask = (byte)(0xff << (8 - prefix % 8));
int i = prefix / 8;
if (i < from.length)
{
from[i] = (byte)(from[i] & mask);
to[i] = (byte)(to[i] | ~mask);
Arrays.fill(from, i+1, from.length, (byte)0);
Arrays.fill(to, i+1, to.length, (byte)0xff);
}
mFrom = from;
mTo = to;
mPrefix = prefix;
}
public IPRange(String cidr) throws UnknownHostException
{
/* only verify the basic structure */
if (!cidr.matches("(?i)^(([0-9.]+)|([0-9a-f:]+))(-(([0-9.]+)|([0-9a-f:]+))|(/\\d+))?$"))
{
throw new IllegalArgumentException("Invalid CIDR or range notation");
}
if (cidr.contains("-"))
{
String[] parts = cidr.split("-");
InetAddress from = InetAddress.getByName(parts[0]);
InetAddress to = InetAddress.getByName(parts[1]);
initializeFromRange(from, to);
}
else
{
String[] parts = cidr.split("/");
InetAddress addr = InetAddress.getByName(parts[0]);
byte[] base = addr.getAddress();
int prefix = base.length * 8;
if (parts.length > 1)
{
prefix = Integer.parseInt(parts[1]);
}
initializeFromCIDR(base, prefix);
}
}
/**
* Returns the first address of the range. The network ID in case this is a proper subnet.
*/
public InetAddress getFrom()
{
try
{
return InetAddress.getByAddress(mFrom);
}
catch (UnknownHostException ignored)
{
return null;
}
}
/**
* Returns the last address of the range.
*/
public InetAddress getTo()
{
try
{
return InetAddress.getByAddress(mTo);
}
catch (UnknownHostException ignored)
{
return null;
}
}
/**
* If this range is a proper subnet returns its prefix, otherwise returns null.
*/
public Integer getPrefix()
{
return mPrefix;
}
@Override
public int compareTo(@NonNull IPRange other)
{
int cmp = compareAddr(mFrom, other.mFrom);
if (cmp == 0)
{ /* smaller ranges first */
cmp = compareAddr(mTo, other.mTo);
}
return cmp;
}
@Override
public boolean equals(Object o)
{
if (o == null || !(o instanceof IPRange))
{
return false;
}
return this == o || compareTo((IPRange)o) == 0;
}
@Override
public String toString()
{
try
{
if (mPrefix != null)
{
return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix;
}
return InetAddress.getByAddress(mFrom).getHostAddress() + "-" +
InetAddress.getByAddress(mTo).getHostAddress();
}
catch (UnknownHostException ignored)
{
return super.toString();
}
}
private int compareAddr(byte a[], byte b[])
{
if (a.length != b.length)
{
return (a.length < b.length) ? -1 : 1;
}
for (int i = 0; i < a.length; i++)
{
if (a[i] != b[i])
{
if (((int)a[i] & 0xff) < ((int)b[i] & 0xff))
{
return -1;
}
else
{
return 1;
}
}
}
return 0;
}
/**
* Check if this range fully contains the given range.
*/
public boolean contains(IPRange range)
{
return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0;
}
/**
* Check if this and the given range overlap.
*/
public boolean overlaps(IPRange range)
{
return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0);
}
private byte[] dec(byte[] addr)
{
for (int i = addr.length - 1; i >= 0; i--)
{
if (--addr[i] != (byte)0xff)
{
break;
}
}
return addr;
}
private byte[] inc(byte[] addr)
{
for (int i = addr.length - 1; i >= 0; i--)
{
if (++addr[i] != 0)
{
break;
}
}
return addr;
}
/**
* Remove the given range from the current range. Returns a list of resulting ranges (these are
* not proper subnets). At most two ranges are returned, in case the given range is contained in
* this but does not equal it, which would result in an empty list (which is also the case if
* this range is fully contained in the given range).
*/
public List<IPRange> remove(IPRange range)
{
ArrayList<IPRange> list = new ArrayList<>();
if (!overlaps(range))
{ /* | this | or | this |
* | range | | range | */
list.add(this);
}
else if (!range.contains(this))
{ /* we are not completely removed, so none of these cases applies:
* | this | or | this | or | this |
* | range | | range | | range | */
if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0)
{ /* the removed range is completely within our boundaries:
* | this |
* | range | */
list.add(new IPRange(mFrom, dec(range.mFrom.clone())));
list.add(new IPRange(inc(range.mTo.clone()), mTo));
}
else
{ /* one end is within our boundaries the other at or outside it:
* | this | or | this | or | this | or | this |
* | range | | range | | range | | range | */
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone());
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone());
list.add(new IPRange(from, to));
}
}
return list;
}
private boolean adjacent(IPRange range)
{
if (compareAddr(mTo, range.mFrom) < 0)
{
byte[] to = inc(mTo.clone());
return compareAddr(to, range.mFrom) == 0;
}
byte[] from = dec(mFrom.clone());
return compareAddr(from, range.mTo) == 0;
}
/**
* Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them.
*/
public IPRange merge(IPRange range)
{
if (overlaps(range))
{
if (contains(range))
{
return this;
}
else if (range.contains(this))
{
return range;
}
}
else if (!adjacent(range))
{
return null;
}
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom;
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo;
return new IPRange(from, to);
}
/**
* Split the given range into a sorted list of proper subnets.
*/
public List<IPRange> toSubnets()
{
ArrayList<IPRange> list = new ArrayList<>();
if (mPrefix != null)
{
list.add(this);
}
else
{
int i = 0, bit = 0, prefix, netmask, common_byte, common_bit;
int from_cur, from_prev = 0, to_cur, to_prev = 1;
boolean from_full = true, to_full = true;
byte[] from = mFrom.clone();
byte[] to = mTo.clone();
/* find a common prefix */
while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit]))
{
if (++bit == 8)
{
bit = 0;
i++;
}
}
prefix = i * 8 + bit;
/* at this point we know that the addresses are either equal, or that the
* current bits in the 'from' and 'to' addresses are 0 and 1, respectively.
* we now look at the rest of the bits as two binary trees (0=left, 1=right)
* where 'from' and 'to' are both leaf nodes. all leaf nodes between these
* nodes are addresses contained in the range. to collect them as subnets
* we follow the trees from both leaf nodes to their root node and record
* all complete subtrees (right for from, left for to) we come across as
* subnets. in that process host bits are zeroed out. if both addresses
* are equal we won't enter the loop below.
* 0_____|_____1 for the 'from' address we assume we start on a
* 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until
* _|_ _|_ _|_ _|_ we reach the root of this subtree, which is
* | | | | | | | | either the root of this whole 'from'-subtree
* 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node
* of the right subtree (1) of another node (which actually could be the
* leaf node we start from). that whole subtree gets recorded as subnet.
* next we follow the right edges to the root of that subtree which again is
* either the 'from'-root or the root node in the left subtree (0) of
* another node. the complete right subtree of that node is the next subnet
* we record. from there we assume that we are in that right subtree and
* recursively follow right edges to its root. for the 'to' address the
* procedure is exactly the same but with left and right reversed.
*/
if (++bit == 8)
{
bit = 0;
i++;
}
common_byte = i;
common_bit = bit;
netmask = from.length * 8;
for (i = from.length - 1; i >= common_byte; i--)
{
int bit_min = (i == common_byte) ? common_bit : 0;
for (bit = 7; bit >= bit_min; bit--)
{
byte mask = mBitmask[bit];
from_cur = from[i] & mask;
if (from_prev == 0 && from_cur != 0)
{ /* 0 -> 1: subnet is the whole current (right) subtree */
list.add(new IPRange(from.clone(), netmask));
from_full = false;
}
else if (from_prev != 0 && from_cur == 0)
{ /* 1 -> 0: invert bit to switch to right subtree and add it */
from[i] ^= mask;
list.add(new IPRange(from.clone(), netmask));
from_cur = 1;
}
/* clear the current bit */
from[i] &= ~mask;
from_prev = from_cur;
to_cur = to[i] & mask;
if (to_prev != 0 && to_cur == 0)
{ /* 1 -> 0: subnet is the whole current (left) subtree */
list.add(new IPRange(to.clone(), netmask));
to_full = false;
}
else if (to_prev == 0 && to_cur != 0)
{ /* 0 -> 1: invert bit to switch to left subtree and add it */
to[i] ^= mask;
list.add(new IPRange(to.clone(), netmask));
to_cur = 0;
}
/* clear the current bit */
to[i] &= ~mask;
to_prev = to_cur;
netmask--;
}
}
if (from_full && to_full)
{ /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable
* due to the shortcut at the top */
list.add(new IPRange(from.clone(), prefix));
}
else if (from_full)
{ /* full from subnet (from=0.. after prefix) */
list.add(new IPRange(from.clone(), prefix + 1));
}
else if (to_full)
{ /* full to subnet (to=1.. after prefix) */
list.add(new IPRange(to.clone(), prefix + 1));
}
}
Collections.sort(list);
return list;
}
}
@@ -0,0 +1,224 @@
/*
* Copyright (C) 2012-2017 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
/**
* Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
* modifying the set and enumerating the resulting subnets.
*/
public class IPRangeSet implements Iterable<IPRange>
{
private TreeSet<IPRange> mRanges = new TreeSet<>();
/**
* Parse the given string (space separated ranges in CIDR or range notation) and return the
* resulting set or {@code null} if the string was invalid. An empty set is returned if the given string
* is {@code null}.
*/
public static IPRangeSet fromString(String ranges)
{
IPRangeSet set = new IPRangeSet();
if (ranges != null)
{
for (String range : ranges.split("\\s+"))
{
try
{
set.add(new IPRange(range));
}
catch (Exception unused)
{ /* besides due to invalid strings exceptions might get thrown if the string
* contains a hostname (NetworkOnMainThreadException) */
return null;
}
}
}
return set;
}
/**
* Add a range to this set. Automatically gets merged with existing ranges.
*/
public void add(IPRange range)
{
if (mRanges.contains(range))
{
return;
}
reinsert:
while (true)
{
Iterator<IPRange> iterator = mRanges.iterator();
while (iterator.hasNext())
{
IPRange existing = iterator.next();
IPRange replacement = existing.merge(range);
if (replacement != null)
{
iterator.remove();
range = replacement;
continue reinsert;
}
}
mRanges.add(range);
break;
}
}
/**
* Add all ranges from the given set.
*/
public void add(IPRangeSet ranges)
{
if (ranges == this)
{
return;
}
for (IPRange range : ranges.mRanges)
{
add(range);
}
}
/**
* Add all ranges from the given collection to this set.
*/
public void addAll(Collection<? extends IPRange> coll)
{
for (IPRange range : coll)
{
add(range);
}
}
/**
* Remove the given range from this set. Existing ranges are automatically adjusted.
*/
public void remove(IPRange range)
{
ArrayList <IPRange> additions = new ArrayList<>();
Iterator<IPRange> iterator = mRanges.iterator();
while (iterator.hasNext())
{
IPRange existing = iterator.next();
List<IPRange> result = existing.remove(range);
if (result.size() == 0)
{
iterator.remove();
}
else if (!result.get(0).equals(existing))
{
iterator.remove();
additions.addAll(result);
}
}
mRanges.addAll(additions);
}
/**
* Remove the given ranges from ranges in this set.
*/
public void remove(IPRangeSet ranges)
{
if (ranges == this)
{
mRanges.clear();
return;
}
for (IPRange range : ranges.mRanges)
{
remove(range);
}
}
/**
* Get all the subnets derived from all the ranges in this set.
*/
public Iterable<IPRange> subnets()
{
return new Iterable<IPRange>()
{
@Override
public Iterator<IPRange> iterator()
{
return new Iterator<IPRange>()
{
private Iterator<IPRange> mIterator = mRanges.iterator();
private List<IPRange> mSubnets;
@Override
public boolean hasNext()
{
return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
}
@Override
public IPRange next()
{
if (mSubnets == null || mSubnets.size() == 0)
{
IPRange range = mIterator.next();
mSubnets = range.toSubnets();
}
return mSubnets.remove(0);
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
@Override
public Iterator<IPRange> iterator()
{
return mRanges.iterator();
}
/**
* Returns the number of ranges, not subnets.
*/
public int size()
{
return mRanges.size();
}
@Override
public String toString()
{ /* we could use TextUtils, but that causes the unit tests to fail */
StringBuilder sb = new StringBuilder();
for (IPRange range : mRanges)
{
if (sb.length() > 0)
{
sb.append(" ");
}
sb.append(range.toString());
}
return sb.toString();
}
}
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2015 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* Simple generator for data/files that may be parsed by libstrongswan's
* settings_t class.
*/
public class SettingsWriter
{
/**
* Top-level section
*/
private final SettingsSection mTop = new SettingsSection();
/**
* Set a string value
* @param key
* @param value
* @return the writer
*/
public SettingsWriter setValue(String key, String value)
{
Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+");
if (key == null || !pattern.matcher(key).matches())
{
return this;
}
String[] keys = key.split("\\.");
SettingsSection section = mTop;
section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1));
section.Settings.put(keys[keys.length-1], value);
return this;
}
/**
* Set an integer value
* @param key
* @param value
* @return the writer
*/
public SettingsWriter setValue(String key, Integer value)
{
return setValue(key, value == null ? null : value.toString());
}
/**
* Set a boolean value
* @param key
* @param value
* @return the writer
*/
public SettingsWriter setValue(String key, Boolean value)
{
return setValue(key, value == null ? null : value ? "1" : "0");
}
/**
* Serializes the settings to a string in the format understood by
* libstrongswan's settings_t parser.
* @return serialized settings
*/
public String serialize()
{
StringBuilder builder = new StringBuilder();
serializeSection(mTop, builder);
return builder.toString();
}
/**
* Serialize the settings in a section and recursively serialize sub-sections
* @param section
* @param builder
*/
private void serializeSection(SettingsSection section, StringBuilder builder)
{
for (Entry<String, String> setting : section.Settings.entrySet())
{
builder.append(setting.getKey()).append('=');
if (setting.getValue() != null)
{
builder.append("\"").append(escapeValue(setting.getValue())).append("\"");
}
builder.append('\n');
}
for (Entry<String, SettingsSection> subsection : section.Sections.entrySet())
{
builder.append(subsection.getKey()).append(" {\n");
serializeSection(subsection.getValue(), builder);
builder.append("}\n");
}
}
/**
* Escape value so it may be wrapped in "
* @param value
* @return
*/
private String escapeValue(String value)
{
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
/**
* Find or create the nested sections with the given names
* @param sections list of section names
* @return final section
*/
private SettingsSection findOrCreateSection(String[] sections)
{
SettingsSection section = mTop;
for (String name : sections)
{
SettingsSection subsection = section.Sections.get(name);
if (subsection == null)
{
subsection = new SettingsSection();
section.Sections.put(name, subsection);
}
section = subsection;
}
return section;
}
/**
* A section containing sub-sections and settings.
*/
private class SettingsSection
{
/**
* Assigned key/value pairs
*/
LinkedHashMap<String,String> Settings = new LinkedHashMap<String, String>();
/**
* Assigned sub-sections
*/
LinkedHashMap<String,SettingsSection> Sections = new LinkedHashMap<String, SettingsWriter.SettingsSection>();
}
}
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2017-2018 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import androidx.annotation.Keep;
@Keep
public class SimpleFetcher
{
private static ExecutorService mExecutor = Executors.newCachedThreadPool();
private static Object mLock = new Object();
private static ArrayList<Future> mFutures = new ArrayList<>();
private static boolean mDisabled;
public static byte[] fetch(String uri, byte[] data, String contentType)
{
Future<byte[]> future;
synchronized (mLock)
{
if (mDisabled)
{
return null;
}
future = mExecutor.submit(() -> {
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.setRequestProperty("Connection", "close");
try
{
if (contentType != null)
{
conn.setRequestProperty("Content-Type", contentType);
}
if (data != null)
{
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(data.length);
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(data);
out.close();
}
return streamToArray(conn.getInputStream());
}
catch (SocketTimeoutException e)
{
return null;
}
finally
{
conn.disconnect();
}
});
mFutures.add(future);
}
try
{
/* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */
return future.get(10000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e)
{
return null;
}
finally
{
synchronized (mLock)
{
mFutures.remove(future);
}
}
}
/**
* Enable fetching after it has been disabled.
*/
public static void enable()
{
synchronized (mLock)
{
mDisabled = false;
}
}
/**
* Disable the fetcher and abort any future requests.
*
* The native thread is not cancelable as it is working on an IKE_SA (canceling the methods of
* HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the
* Future (causing a return from fetch() immediately) and let the executor thread continue its
* thing in the background.
*
* Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then
* block in the subsequent fetch for a CRL).
*/
public static void disable()
{
synchronized (mLock)
{
mDisabled = true;
for (Future future : mFutures)
{
future.cancel(true);
}
}
}
private static byte[] streamToArray(InputStream in) throws IOException
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
try
{
while ((len = in.read(buf)) != -1)
{
out.write(buf, 0, len);
}
return out.toByteArray();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
in.close();
}
return null;
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2014-2019 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.amnezia.vpn.ikev2.utils;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Utils
{
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
/**
* Converts the given byte array to a hexadecimal string encoding.
*
* @param bytes byte array to convert
* @return hex string
*/
public static String bytesToHex(byte[] bytes)
{
char[] hex = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++)
{
int value = bytes[i];
hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
hex[i*2+1] = HEXDIGITS[ value & 0x0f];
}
return new String(hex);
}
/**
* Validate the given proposal string
*
* @param ike true for IKE, false for ESP
* @param proposal proposal string
* @return true if valid
*/
public native static boolean isProposalValid(boolean ike, String proposal);
/**
* Parse an IP address without doing a name lookup
*
* @param address IP address string
* @return address bytes if valid
*/
private native static byte[] parseInetAddressBytes(String address);
/**
* Parse an IP address without doing a name lookup (as compared to InetAddress.fromName())
*
* @param address IP address string
* @return address if valid
* @throws UnknownHostException if address is invalid
*/
public static InetAddress parseInetAddress(String address) throws UnknownHostException
{
byte[] bytes = parseInetAddressBytes(address);
if (bytes == null)
{
throw new UnknownHostException();
}
return InetAddress.getByAddress(bytes);
}
}
@@ -1,189 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn.qt;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.Manifest.permission;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
// Gets used by /platforms/android/androidAppListProvider.cpp
public class PackageManagerHelper {
final static String TAG = "PackageManagerHelper";
final static int MIN_CHROME_VERSION = 65;
final static List<String> CHROME_BROWSERS = Arrays.asList(
new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"});
private static String getAllAppNames(Context ctx) {
JSONObject output = new JSONObject();
PackageManager pm = ctx.getPackageManager();
List<String> browsers = getBrowserIDs(pm);
List<PackageInfo> packs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
// Do not add ourselves and System Apps to the list, unless it might be a browser
if ((!isSystemPackage(p,pm) || browsers.contains(p.packageName))
&& !isSelf(p)) {
String appid = p.packageName;
String appName = p.applicationInfo.loadLabel(pm).toString();
try {
output.put(appid, appName);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
return output.toString();
}
private static Drawable getAppIcon(Context ctx, String id) {
try {
return ctx.getPackageManager().getApplicationIcon(id);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return new ColorDrawable(Color.TRANSPARENT);
}
private static boolean isSystemPackage(PackageInfo pkgInfo, PackageManager pm) {
if( (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
// no system app
return false;
}
// For Systems Packages there are Cases where we want to add it anyway:
// Has the use Internet permission (otherwise makes no sense)
// Had at least 1 update (this means it's probably on any AppStore)
// Has a a launch activity (has a ui and is not just a system service)
if(!usesInternet(pkgInfo)){
return true;
}
if(!hadUpdate(pkgInfo)){
return true;
}
if(pm.getLaunchIntentForPackage(pkgInfo.packageName) == null){
// If there is no way to launch this from a homescreen, def a sys package
return true;
}
return false;
}
private static boolean isSelf(PackageInfo pkgInfo) {
return pkgInfo.packageName.equals("org.amnezia.vpn")
|| pkgInfo.packageName.equals("org.amnezia.vpn.debug");
}
private static boolean usesInternet(PackageInfo pkgInfo){
if(pkgInfo.requestedPermissions == null){
return false;
}
for(int i=0; i < pkgInfo.requestedPermissions.length; i++) {
String permission = pkgInfo.requestedPermissions[i];
if(Manifest.permission.INTERNET.equals(permission)){
return true;
}
}
return false;
}
private static boolean hadUpdate(PackageInfo pkgInfo){
return pkgInfo.lastUpdateTime > pkgInfo.firstInstallTime;
}
// Returns List of all Packages that can classify themselves as browsers
private static List<String> getBrowserIDs(PackageManager pm) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.mozilla.org/"));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that
// are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set
// in the intent filter
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
List<String> browsers = new ArrayList<String>();
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo info = resolveInfos.get(i);
String browserID = info.activityInfo.packageName;
browsers.add(browserID);
}
return browsers;
}
// Gets called in AndroidAuthenticationListener;
public static boolean isWebViewSupported(Context ctx) {
Log.v(TAG, "Checking if installed Webview is compatible with FxA");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// The default Webview is able do to FXA
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PackageInfo pi = WebView.getCurrentWebViewPackage();
if (CHROME_BROWSERS.contains(pi.packageName)) {
return isSupportedChromeBrowser(pi);
}
return isNotAncientBrowser(pi);
}
// Before O the webview is hardcoded, but we dont know which package it is.
// Check if com.google.android.webview is installed
PackageManager pm = ctx.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
// Otherwise check com.android.webview
try {
PackageInfo pi = pm.getPackageInfo("com.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
Log.e(TAG, "Android System WebView is not found");
// Giving up :(
return false;
}
private static boolean isSupportedChromeBrowser(PackageInfo pi) {
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
try {
String versionCode = pi.versionName.split(Pattern.quote(" "))[0];
String majorVersion = versionCode.split(Pattern.quote("."))[0];
int version = Integer.parseInt(majorVersion);
return version >= MIN_CHROME_VERSION;
} catch (Exception e) {
Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName);
return false;
}
}
private static boolean isNotAncientBrowser(PackageInfo pi) {
// Not a google chrome - So the version name is worthless
// Lets just make sure the WebView
// used is not ancient ==> Was updated in at least the last 365 days
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
double oneYearInMillis = 31536000000L;
return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis);
}
}
@@ -114,7 +114,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
// QT will always attempt to read the clipboard if content is there.
// since we have no use of the clipboard in android 10+
// we _can_ return null
// And we defnitly should since android 12 displays clipboard access.
// And we definitely should since android 12 displays clipboard access.
null
} else {
super.getSystemService(name)
@@ -259,13 +259,13 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
private val ACTION_REGISTER_LISTENER = 3
private val ACTION_RESUME_ACTIVATE = 7
private val ACTION_IMPORT_CONFIG = 11
private val EVENT_PERMISSION_REQURED = 6
private val EVENT_PERMISSION_REQUIRED = 6
private val EVENT_DISCONNECTED = 2
private val UI_EVENT_QR_CODE_RECEIVED = 0
fun onPermissionRequest(code: Int, data: Parcel?) {
if (code != EVENT_PERMISSION_REQURED) {
if (code != EVENT_PERMISSION_REQUIRED) {
return
}
+13 -10
View File
@@ -12,16 +12,16 @@ set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
include(${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/QSimpleCrypto.cmake)
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/zlib)
if(WIN32)
set(ZLIB_LIBRARY $<IF:$<CONFIG:Debug>,zlibd,zlib>)
else()
set(ZLIB_LIBRARY z)
endif()
set(ZLIB_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/zlib" "${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib")
link_directories(${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib)
link_libraries(${ZLIB_LIBRARY})
#set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
#add_subdirectory(${CLIENT_ROOT_DIR}/3rd/zlib)
#if(WIN32)
# set(ZLIB_LIBRARY $<IF:$<CONFIG:Debug>,zlibd,zlib>)
#else()
# set(ZLIB_LIBRARY z)
#endif()
#set(ZLIB_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/zlib" "${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib")
#link_directories(${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib)
#link_libraries(${ZLIB_LIBRARY})
if(IOS)
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
@@ -105,6 +105,9 @@ set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
set(LIBS ${LIBS} qt6keychain)
include(${CLIENT_ROOT_DIR}/3rd/strongswan/strongswan.cmake)
include_directories(
${CLIENT_ROOT_DIR}/3rd/OpenSSL/include
${CLIENT_ROOT_DIR}/3rd/libssh/include
+1 -1
View File
@@ -13,7 +13,7 @@ endif()
if(CODE_SIGN_IDENTITY)
find_program(CODESIGN_BIN NAMES codesign)
if(NOT CODESIGN_BIN)
messsage(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
message(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
endif()
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${CODE_SIGN_IDENTITY})
endif()
+1 -1
View File
@@ -30,7 +30,7 @@ public:
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
// workaround for containers which is not support normal configaration
// workaround for containers which is not support normal configuration
void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
@@ -72,7 +72,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
// Get list of already created clients (only IP addreses)
// Get list of already created clients (only IP addresses)
QString nextIpNumber;
{
QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath);
+1
View File
@@ -170,6 +170,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return true;
case DockerContainer::Ipsec: return true;
default: return false;
}
+3 -3
View File
@@ -1,5 +1,5 @@
#ifndef CONTAIERNS_DEFS_H
#define CONTAIERNS_DEFS_H
#ifndef CONTAINERS_DEFS_H
#define CONTAINERS_DEFS_H
#include <QObject>
#include <QQmlEngine>
@@ -75,4 +75,4 @@ static void declareQmlContainerEnum() {
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
#endif // CONTAIERNS_DEFS_H
#endif // CONTAINERS_DEFS_H
+2 -2
View File
@@ -104,8 +104,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto proccessReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return proccessReplica;
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
}
+13 -4
View File
@@ -289,10 +289,10 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, QJsonObject &newConfig)
{
bool reinstallRequred = isReinstallContainerRequred(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred;
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
if (reinstallRequred) {
if (reinstallRequired) {
return setupContainer(credentials, container, newConfig, true);
}
else {
@@ -326,7 +326,7 @@ QJsonObject ServerController::createContainerInitialConfig(DockerContainer conta
return config;
}
bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
@@ -655,6 +655,11 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
script = script.append("|:%1").arg(port);
}
script = script.append("' | grep -i %1").arg(transportProto);
if (transportProto == "tcp") {
script = script.append(" | grep LISTEN");
}
ErrorCode errorCode = runScript(credentials,
replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
@@ -669,6 +674,10 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
{
if (credentials.userName == "root") {
return ErrorCode::NoError;
}
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
+2 -3
View File
@@ -32,11 +32,11 @@ public:
// create initial config - generate passwords, etc
QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
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);
@@ -62,10 +62,9 @@ private:
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
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);
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
+1 -1
View File
@@ -6,7 +6,7 @@
#include <fstream>
#ifdef Q_OS_WINDOWS
#define S_IRWXU 0
const uint32_t S_IRWXU = 0644;
#endif
namespace libssh {
+1 -1
View File
@@ -1,6 +1,6 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
# Ensure this file is checked into source control!
#gem 'fastlane-plugin-run_tests_firebase_testlab'
#gem 'fastlane-plugin-firebase_app_distribution'
+2 -2
View File
@@ -157,7 +157,7 @@ void Logger::clearServiceLogs()
if (!m_IpcClient->isSocketConnected()) {
if (!IpcClient::init(m_IpcClient)) {
qWarning() << "Error occured when init IPC client";
qWarning() << "Error occurred when init IPC client";
return;
}
}
@@ -167,7 +167,7 @@ void Logger::clearServiceLogs()
m_IpcClient->Interface()->cleanUp();
}
else {
qWarning() << "Error occured cleaning up service logs";
qWarning() << "Error occurred cleaning up service logs";
}
#endif
}
+1 -1
View File
@@ -57,7 +57,7 @@ void ManagementServer::onNewConnection()
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
{
Q_UNUSED(socketError)
qDebug().noquote() << QString("Mananement server error: %1").arg(m_socket->errorString());
qDebug().noquote() << QString("Management server error: %1").arg(m_socket->errorString());
}
void ManagementServer::onSocketDisconnected()
@@ -176,6 +176,21 @@ ErrorCode AndroidController::start()
appContext.object());
QJsonDocument doc(m_vpnConfig);
/*** The following code snippet needs to correct displaying of config in debug console
* (Android's stdout limits length of output message)
*
* QString string(doc.toJson(QJsonDocument::Compact));
*
* qDebug() << "*** config: ";
* for (int i = 0; i <= string.length()/100; i++) {
* int start = i*100;
* qDebug() << string.mid(start, 100);
* }
*
* qDebug() << "*** config: " << m_vpnConfig;
***/
AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
return NoError;
@@ -297,7 +312,7 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int
[](int receiverRequestCode, int resultCode,
const QJniObject& data) {
// Currently this function just used in
// VPNService.kt::checkPersmissions. So the result
// VPNService.kt::checkPermissions. So the result
// we're getting is if the User gave us the
// Vpn.bind permission. In case of NO we should
// abort.
@@ -17,7 +17,7 @@ enum ServiceAction {
ACTION_ACTIVATE = 1,
// Deactivate the vpn. Body is empty
ACTION_DEACTIVATE = 2,
// Register an IBinder to recieve events body is an Ibinder
// Register an IBinder to receive events body is an Ibinder
ACTION_REGISTERLISTENER = 3,
// Requests an EVENT_STATISTIC_UPDATE to be send
ACTION_REQUEST_STATISTIC = 4,
@@ -40,14 +40,14 @@ typedef enum ServiceAction ServiceAction;
// Event Types that will be Dispatched after registration
enum ServiceEvents {
// The Service has Accecpted our Binder
// The Service has Accepted our Binder
// Responds with the current status of the vpn.
EVENT_INIT = 0,
// WG-Go has enabled the adapter (empty response)
EVENT_CONNECTED = 1,
// WG-Go has disabled the adapter (empty response)
EVENT_DISCONNECTED = 2,
// Contains the Current transfered bytes to endpoint x.
// Contains the Current transferred bytes to endpoint x.
EVENT_STATISTIC_UPDATE = 3,
EVENT_BACKEND_LOGS = 4,
// An Error happened during activation
+3 -3
View File
@@ -30,9 +30,9 @@
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
UIVisualEffectView *blurBackround = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackround];
blurBackround.frame = _screen.frame;
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
+1 -1
View File
@@ -130,7 +130,7 @@ Logger logger(LOG_IAP, "IOSIAPHandler");
logger.debug() << "transaction deferred";
break;
default:
logger.warning() << "transaction unknwon state";
logger.warning() << "transaction unknown state";
break;
}
}
+3 -3
View File
@@ -428,7 +428,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let password = ssConfig[Constants.ssPasswordKey] as? String else {
self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Cannot asign profile params for ss in tunnel"]))
userInfo: [NSLocalizedDescriptionKey: "Cannot assign profile params for ss in tunnel"]))
return nil
}
var insettings: [String: Any] = .init()
@@ -639,7 +639,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
wg_log(.error, message: "Start reading packets to connection")
wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")")
packetFlow.readPackets { [weak self] packets, protocols in
wg_log(.error, message: "\(packets.count) outcoming packets proccessed of \(protocols.first?.stringValue ?? "unknown") type")
wg_log(.error, message: "\(packets.count) outcoming packets processed of \(protocols.first?.stringValue ?? "unknown") type")
guard let `self` = self else { return }
self.session?.writeMultipleDatagrams(packets, completionHandler: { _ in
self.processQueue.async {
@@ -662,7 +662,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
session?.setReadHandler({ ssdata, error in
wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null"), error: \(error?.localizedDescription ?? "none")")
guard error == nil, let packets = ssdata else { return }
wg_log(.error, message: "\(packets.count) incoming packets proccessed")
wg_log(.error, message: "\(packets.count) incoming packets processed")
self.packetFlow.writePackets(packets, withProtocols: [NSNumber(value: AF_INET)])
}, maxDatagrams: Int.max)
+1 -1
View File
@@ -166,7 +166,7 @@ class TunProvider: NSObject {
dispatchQueue.async {
let success = leaf_shutdown(self.tunId)
if !success {
let errMsg = "Tunnel canot be stopped for some odd reason."
let errMsg = "Tunnel cannot be stopped for some odd reason."
self.stopCompletion?(.undefinedError(errMsg))
}
pthread_kill(self.tunThreadId!, SIGUSR1)
@@ -120,7 +120,7 @@ void MacosRouteMonitor::handleRtmChange(const struct rt_msghdr* rtm,
for (auto addr : addrlist) {
list.append(addrToString(addr));
}
logger.debug() << "Route chagned by" << rtm->rtm_pid
logger.debug() << "Route changed by" << rtm->rtm_pid
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
}
@@ -138,7 +138,7 @@ void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm,
list.append(addrToString(addr));
}
logger.debug() << "Interface " << ifm->ifm_index
<< "chagned flags:" << ifm->ifm_flags
<< "changed flags:" << ifm->ifm_flags
<< QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" ");
}
@@ -50,7 +50,7 @@ void Ikev2Protocol::stop()
void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE rasconnstate, DWORD dwError)
{
Q_UNUSED(unMsg);
qDebug()<<"Recive the new event "<<static_cast<int>(rasconnstate);
qDebug()<<"Receive the new event "<<static_cast<int>(rasconnstate);
switch (rasconnstate)
{
case RASCS_OpenPort:
@@ -54,7 +54,7 @@ private:
private:
QJsonObject m_config;
//RAS functions and parametrs
//RAS functions and parameters
HRASCONN hRasConn{nullptr};
bool create_new_vpn(const QString & vpn_name,
const QString & serv_addr);
+1 -1
View File
@@ -36,7 +36,7 @@ public:
void cleanupBackendLogs();
signals:
void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes);
void newTransmittedDataCount(quint64 rxBytes, quint64 txBytes);
protected slots:
+2 -2
View File
@@ -27,7 +27,7 @@ Proto currentProto = amnezia::Proto::Any;
IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent)
: VpnProtocol(configuration, parent), m_protocol(proto)
{
connect(this, &IOSVpnProtocol::newTransmitedDataCount, this, &IOSVpnProtocol::setBytesChanged);
connect(this, &IOSVpnProtocol::newTransmittedDataCount, this, &IOSVpnProtocol::setBytesChanged);
}
IOSVpnProtocol* IOSVpnProtocol::instance() {
@@ -209,7 +209,7 @@ void IOSVpnProtocol::checkStatus()
qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway)
<< "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address)
<< "RxBytes:" << rxBytes << "TxBytes:" << txBytes;
emit newTransmitedDataCount(rxBytes, txBytes);
emit newTransmittedDataCount(rxBytes, txBytes);
}];
}
+1
View File
@@ -77,6 +77,7 @@ constexpr char defaultSubnetAddress[] = "10.8.0.0";
constexpr char defaultSubnetMask[] = "255.255.255.0";
constexpr char defaultSubnetCidr[] = "24";
constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf";
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued";
constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key";
+4
View File
@@ -84,6 +84,10 @@
<file>ui/qml/Pages/PageAbout.qml</file>
<file>ui/qml/Pages/PageQrDecoderIos.qml</file>
<file>ui/qml/Pages/PageViewConfig.qml</file>
<file>ui/qml/Pages/PageClientManagement.qml</file>
<file>ui/qml/Pages/ClientInfo/PageClientInfoBase.qml</file>
<file>ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml</file>
<file>ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml</file>
<file>ui/qml/Pages/Protocols/PageProtoCloak.qml</file>
<file>ui/qml/Pages/Protocols/PageProtoOpenVPN.qml</file>
<file>ui/qml/Pages/Protocols/PageProtoShadowSocks.qml</file>
+3 -3
View File
@@ -334,14 +334,14 @@ class XCodeprojPatcher
def setup_target_gobridge(platform)
target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
bridge_platofrm = platform == 'ios' ? 'iOS' : 'macOS'
bridge_platform = platform == 'ios' ? 'iOS' : 'macOS'
target_gobridge.build_working_directory = platform == 'ios' ? '3rd/wireguard-apple/Sources/WireGuardKitGo' : 'macos/gobridge'
target_gobridge.build_tool_path = 'make'
target_gobridge.pass_build_settings_in_environment = '1'
target_gobridge.build_arguments_string = '$(ACTION)'
target_gobridge.name = "WireGuardGoBridge<#{bridge_platofrm}>"
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platofrm}>"
target_gobridge.name = "WireGuardGoBridge<#{bridge_platform}>"
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platform}>"
@project.targets << target_gobridge
@target_extension.add_dependency target_gobridge
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,5 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,4 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
@@ -18,6 +18,7 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,4 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
+3 -3
View File
@@ -403,7 +403,7 @@ Please note, this protocol still does not support export connection profile to m
<location filename="../ui/mainwindow.ui" line="1388"/>
<source>Optional.
We recommend to enable VPN mode &quot;For selected sites&quot; and add blocked sites you need to visit manually. If you will choose this option, you will need add every bloked site you want to visit to the access list. You may switch between modes later.
We recommend to enable VPN mode &quot;For selected sites&quot; and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.
Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.</source>
<translation>Опционально.
@@ -582,7 +582,7 @@ OpenVPN over ShadowSocks</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="4302"/>
<source>Secondray DNS server</source>
<source>Secondary DNS server</source>
<translation>Вторичный DNS сервер</translation>
</message>
<message>
@@ -1111,7 +1111,7 @@ This code does not include server credentials.</source>
</message>
<message>
<location filename="../ui/mainwindow.cpp" line="1756"/>
<source>VPN Protocol not choosen</source>
<source>VPN Protocol not chosen</source>
<translation>VPN протокол не выбран</translation>
</message>
<message>
+104
View File
@@ -0,0 +1,104 @@
#include "clientManagementModel.h"
#include <QJsonObject>
ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
{
}
void ClientManagementModel::clearData()
{
beginResetModel();
m_content.clear();
endResetModel();
}
void ClientManagementModel::setContent(const QVector<QVariant> &data)
{
beginResetModel();
m_content = data;
endResetModel();
}
QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
{
QJsonObject clientsTable;
for (const auto &item : m_content) {
if (protocol == amnezia::Proto::OpenVpn) {
clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
} else if (protocol == amnezia::Proto::WireGuard) {
clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
}
}
return clientsTable;
}
int ClientManagementModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return static_cast<int>(m_content.size());
}
QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
return QVariant();
}
if (role == NameRole) {
return m_content[index.row()].toJsonObject()["clientName"].toString();
} else if (role == OpenVpnCertIdRole) {
return m_content[index.row()].toJsonObject()["openvpnCertId"].toString();
} else if (role == OpenVpnCertDataRole) {
return m_content[index.row()].toJsonObject()["openvpnCertData"].toString();
} else if (role == WireGuardPublicKey) {
return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString();
}
return QVariant();
}
void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
return;
}
auto client = m_content[index.row()].toJsonObject();
if (role == NameRole) {
client["clientName"] = data.toString();
} else if (role == OpenVpnCertIdRole) {
client["openvpnCertId"] = data.toString();
} else if (role == OpenVpnCertDataRole) {
client["openvpnCertData"] = data.toString();
} else if (role == WireGuardPublicKey) {
client["wireguardPublicKey"] = data.toString();
} else {
return;
}
if (m_content[index.row()] != client) {
m_content[index.row()] = client;
emit dataChanged(index, index);
}
}
bool ClientManagementModel::removeRows(int row)
{
beginRemoveRows(QModelIndex(), row, row);
m_content.removeAt(row);
endRemoveRows();
return true;
}
QHash<int, QByteArray> ClientManagementModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "clientName";
roles[OpenVpnCertIdRole] = "openvpnCertId";
roles[OpenVpnCertDataRole] = "openvpnCertData";
roles[WireGuardPublicKey] = "wireguardPublicKey";
return roles;
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef CLIENTMANAGEMENTMODEL_H
#define CLIENTMANAGEMENTMODEL_H
#include <QAbstractListModel>
#include "protocols/protocols_defs.h"
class ClientManagementModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ClientRoles {
NameRole = Qt::UserRole + 1,
OpenVpnCertIdRole,
OpenVpnCertDataRole,
WireGuardPublicKey,
};
ClientManagementModel(QObject *parent = nullptr);
void clearData();
void setContent(const QVector<QVariant> &data);
QJsonObject getContent(amnezia::Proto protocol);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole);
bool removeRows(int row);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QVector<QVariant> m_content;
};
#endif // CLIENTMANAGEMENTMODEL_H
+1 -1
View File
@@ -93,7 +93,7 @@ void NotificationHandler::unsecuredNetworkNotification(const QString& networkNam
QString title = tr("AmneziaVPN notification");
QString message = tr("Unsucured network detected: ") + networkName;
QString message = tr("Unsecured network detected: ") + networkName;
notifyInternal(UnsecuredNetwork, title, message, 2000);
}
+4 -2
View File
@@ -12,7 +12,8 @@ public:
enum Type {
Basic,
Proto,
ShareProto
ShareProto,
ClientInfo
};
Q_ENUM(Type)
};
@@ -24,7 +25,8 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn,
Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress,
GeneralSettings, AppSettings, NetworkSettings, ServerSettings,
ServerContainers, ServersList, ShareConnection, Sites,
ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings};
ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig,
AdvancedServerSettings, ClientManagement, ClientInfo};
Q_ENUM_NS(Page)
static void declareQmlPageEnum() {
+213
View File
@@ -0,0 +1,213 @@
#include "ClientInfoLogic.h"
#include <QMessageBox>
#include "defines.h"
#include "core/errorstrings.h"
#include "core/servercontroller.h"
#include "ui/models/clientManagementModel.h"
#include "ui/uilogic.h"
namespace {
bool isErrorOccured(ErrorCode error) {
if (error != ErrorCode::NoError) {
QMessageBox::warning(nullptr, APPLICATION_NAME,
QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error));
return true;
}
return false;
}
}
ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent):
PageLogicBase(logic, parent)
{
}
void ClientInfoLogic::setCurrentClientId(int index)
{
m_currentClientIndex = index;
}
void ClientInfoLogic::onUpdatePage()
{
set_pageContentVisible(false);
set_busyIndicatorIsRunning(true);
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
const QString containerNameString = ContainerProps::containerHumanNames().value(container);
set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString);
const QVector<amnezia::Proto> protocols = ContainerProps::protocolsForContainer(container);
if (!protocols.empty()) {
const Proto currentMainProtocol = protocols.front();
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
const QModelIndex modelIndex = model->index(m_currentClientIndex);
set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString());
if (currentMainProtocol == Proto::OpenVpn) {
const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString();
if (certData.isEmpty() && !certId.isEmpty()) {
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'")
.arg(certId);
ServerController serverController(m_settings);
const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut);
certData = stdOut;
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
emit uiLogic()->closePage();
return;
}
}
set_labelOpenVpnCertId(certId);
set_textAreaOpenVpnCertData(certData);
} else if (currentMainProtocol == Proto::WireGuard) {
set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString());
}
}
set_pageContentVisible(true);
set_busyIndicatorIsRunning(false);
}
void ClientInfoLogic::onLineEditNameAliasEditingFinished()
{
set_busyIndicatorIsRunning(true);
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
const QModelIndex modelIndex = model->index(m_currentClientIndex);
model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole);
const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
const QVector<amnezia::Proto> protocols = ContainerProps::protocolsForContainer(selectedContainer);
if (!protocols.empty()) {
const Proto currentMainProtocol = protocols.front();
const QJsonObject clientsTable = model->getContent(currentMainProtocol);
ErrorCode error = setClientsList(credentials,
selectedContainer,
currentMainProtocol,
clientsTable);
isErrorOccured(error);
}
set_busyIndicatorIsRunning(false);
}
void ClientInfoLogic::onRevokeOpenVpnCertificateClicked()
{
set_busyIndicatorIsRunning(true);
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
const QModelIndex modelIndex = model->index(m_currentClientIndex);
const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
"cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\"
"cp pki/crl.pem .'").arg(certId);
ServerController serverController(m_settings);
const QString script = serverController.replaceVars(getOpenVpnCertData,
serverController.genVarsForScript(credentials, container));
auto error = serverController.runScript(credentials, script);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
emit uiLogic()->goToPage(Page::ServerSettings);
return;
}
model->removeRows(m_currentClientIndex);
const QJsonObject clientsTable = model->getContent(Proto::OpenVpn);
error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container);
error = serverController.startupContainerWorker(credentials, container, containerConfig);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
set_busyIndicatorIsRunning(false);
}
void ClientInfoLogic::onRevokeWireGuardKeyClicked()
{
set_busyIndicatorIsRunning(true);
ErrorCode error;
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
ServerController serverController(m_settings);
const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
const QModelIndex modelIndex = model->index(m_currentClientIndex);
const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString();
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
for (auto &section : configSections) {
if (section.contains(key)) {
configSections.removeOne(section);
}
}
QString newWireGuardConfig = configSections.join("[");
newWireGuardConfig.insert(0, "[");
error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig,
protocols::wireguard::serverConfigPath,
libssh::SftpOverwriteMode::SftpOverwriteExisting);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
model->removeRows(m_currentClientIndex);
const QJsonObject clientsTable = model->getContent(Proto::WireGuard);
error = setClientsList(credentials, container, Proto::WireGuard, clientsTable);
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'";
error = serverController.runScript(credentials,
serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
if (isErrorOccured(error)) {
set_busyIndicatorIsRunning(false);
return;
}
set_busyIndicatorIsRunning(false);
}
ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns)
{
const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString);
ServerController serverController(m_settings);
ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile);
return error;
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef CLIENTINFOLOGIC_H
#define CLIENTINFOLOGIC_H
#include "PageLogicBase.h"
#include "core/defs.h"
#include "containers/containers_defs.h"
#include "protocols/protocols_defs.h"
class UiLogic;
class ClientInfoLogic : public PageLogicBase
{
Q_OBJECT
AUTO_PROPERTY(QString, lineEditNameAliasText)
AUTO_PROPERTY(QString, labelOpenVpnCertId)
AUTO_PROPERTY(QString, textAreaOpenVpnCertData)
AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
AUTO_PROPERTY(QString, textAreaWireGuardKeyData)
AUTO_PROPERTY(bool, busyIndicatorIsRunning);
AUTO_PROPERTY(bool, pageContentVisible);
public:
ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr);
~ClientInfoLogic() = default;
void setCurrentClientId(int index);
public slots:
void onUpdatePage() override;
void onLineEditNameAliasEditingFinished();
void onRevokeOpenVpnCertificateClicked();
void onRevokeWireGuardKeyClicked();
private:
ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns);
int m_currentClientIndex;
};
#endif // CLIENTINFOLOGIC_H
@@ -0,0 +1,143 @@
#include "ClientManagementLogic.h"
#include <QMessageBox>
#include "defines.h"
#include "core/errorstrings.h"
#include "core/servercontroller.h"
#include "ui/pages_logic/ClientInfoLogic.h"
#include "ui/models/clientManagementModel.h"
#include "ui/uilogic.h"
ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent):
PageLogicBase(logic, parent)
{
}
void ClientManagementLogic::onUpdatePage()
{
set_busyIndicatorIsRunning(true);
qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel())->clearData();
DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer);
set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName);
QJsonObject clients;
auto protocols = ContainerProps::protocolsForContainer(selectedContainer);
if (!protocols.empty()) {
m_currentMainProtocol = protocols.front();
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients);
if (error != ErrorCode::NoError) {
QMessageBox::warning(nullptr, APPLICATION_NAME,
tr("An error occurred while getting the list of clients.") + "\n" + errorString(error));
set_busyIndicatorIsRunning(false);
return;
}
}
QVector<QVariant> clientsArray;
for (auto &clientId : clients.keys()) {
clientsArray.push_back(clients[clientId].toObject());
}
qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel())->setContent(clientsArray);
set_busyIndicatorIsRunning(false);
}
void ClientManagementLogic::onClientItemClicked(int index)
{
uiLogic()->pageLogic<ClientInfoLogic>()->setCurrentClientId(index);
emit uiLogic()->goToClientInfoPage(m_currentMainProtocol);
}
ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns)
{
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
ServerController serverController(m_settings);
const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString);
const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
if (error != ErrorCode::NoError) {
return error;
}
QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object();
int count = 0;
if (mainProtocol == Proto::OpenVpn) {
const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container));
error = serverController.runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
return error;
}
if (!stdOut.isEmpty()) {
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
certsIds.removeAll("AmneziaReq.crt");
for (auto &openvpnCertId : certsIds) {
openvpnCertId.replace(".crt", "");
if (!clientsTable.contains(openvpnCertId)) {
QJsonObject client;
client["openvpnCertId"] = openvpnCertId;
client["clientName"] = QString("Client %1").arg(count);
client["openvpnCertData"] = "";
clientsTable[openvpnCertId] = client;
count++;
}
}
}
} else if (mainProtocol == Proto::WireGuard) {
const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (error != ErrorCode::NoError) {
return error;
}
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
QStringList wireguardKeys;
for (const auto &line : configLines) {
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
if (configPair.front() == "PublicKey") {
wireguardKeys.push_back(configPair.back());
}
}
for (auto &wireguardKey : wireguardKeys) {
if (!clientsTable.contains(wireguardKey)) {
QJsonObject client;
client["clientName"] = QString("Client %1").arg(count);
client["wireguardPublicKey"] = wireguardKey;
clientsTable[wireguardKey] = client;
count++;
}
}
}
const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson();
if (clientsTableString != newClientsTableString) {
error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
}
if (error != ErrorCode::NoError) {
return error;
}
clietns = clientsTable;
return error;
}
@@ -0,0 +1,33 @@
#ifndef CLIENTMANAGMENTLOGIC_H
#define CLIENTMANAGMENTLOGIC_H
#include "PageLogicBase.h"
#include "core/defs.h"
#include "containers/containers_defs.h"
#include "protocols/protocols_defs.h"
class UiLogic;
class ClientManagementLogic : public PageLogicBase
{
Q_OBJECT
AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
AUTO_PROPERTY(bool, busyIndicatorIsRunning);
public:
ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr);
~ClientManagementLogic() = default;
public slots:
void onUpdatePage() override;
void onClientItemClicked(int index);
private:
ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns);
amnezia::Proto m_currentMainProtocol;
};
#endif // CLIENTMANAGMENTLOGIC_H
@@ -12,7 +12,7 @@ ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, Q
m_labelWaitInfoVisible{true},
m_labelWaitInfoText{tr("Please wait, configuring process may take up to 5 minutes")},
m_progressBarVisible{true},
m_progressBarMaximium{100},
m_progressBarMaximum{100},
m_progressBarTextVisible{true},
m_progressBarText{tr("Configuring...")},
m_labelServerBusyVisible{false},
@@ -46,8 +46,8 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
progress.getValueFunc = [this] (void) -> int {
return progressBarValue();
};
progress.getMaximiumFunc = [this] (void) -> int {
return progressBarMaximium();
progress.getMaximumFunc = [this] (void) -> int {
return progressBarMaximum();
};
LabelFunc busyInfo;
@@ -150,7 +150,7 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
// just ui progressbar tweak
timer.stop();
int remainingVal = progress.getMaximiumFunc() - progress.getValueFunc();
int remainingVal = progress.getMaximumFunc() - progress.getValueFunc();
if (remainingVal > 0) {
QTimer timer1;
@@ -158,7 +158,7 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
connect(&timer1, &QTimer::timeout, [&](){
progress.setValueFunc(progress.getValueFunc() + 1);
if (progress.getValueFunc() >= progress.getMaximiumFunc()) {
if (progress.getValueFunc() >= progress.getMaximumFunc()) {
loop1.quit();
}
});
@@ -17,7 +17,7 @@ class ServerConfiguringProgressLogic : public PageLogicBase
AUTO_PROPERTY(bool, labelWaitInfoVisible)
AUTO_PROPERTY(QString, labelWaitInfoText)
AUTO_PROPERTY(bool, progressBarVisible)
AUTO_PROPERTY(int, progressBarMaximium)
AUTO_PROPERTY(int, progressBarMaximum)
AUTO_PROPERTY(bool, progressBarTextVisible)
AUTO_PROPERTY(QString, progressBarText)
AUTO_PROPERTY(bool, labelServerBusyVisible)
@@ -32,7 +32,7 @@ private:
std::function<void(bool)> setVisibleFunc;
std::function<void(int)> setValueFunc;
std::function<int(void)> getValueFunc;
std::function<int(void)> getMaximiumFunc;
std::function<int(void)> getMaximumFunc;
std::function<void(bool)> setTextVisibleFunc;
std::function<void(const QString&)> setTextFunc;
};
+1 -1
View File
@@ -218,7 +218,7 @@ void VpnLogic::onConnect()
}
if (container == DockerContainer::None) {
set_labelErrorText(tr("VPN Protocol not choosen"));
set_labelErrorText(tr("VPN Protocol not chosen"));
set_pushButtonConnectChecked(false);
return;
}
@@ -21,7 +21,7 @@ CloakLogic::CloakLogic(UiLogic *logic, QObject *parent):
m_labelInfoVisible{true},
m_labelInfoText{},
m_progressBarResetValue{0},
m_progressBarResetMaximium{100}
m_progressBarResetMaximum{100}
{
}
@@ -87,8 +87,8 @@ void CloakLogic::onPushButtonSaveClicked()
progressBarFunc.getValueFunc = [this] (void) -> int {
return progressBarResetValue();
};
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
return progressBarResetMaximium();
progressBarFunc.getMaximumFunc = [this] (void) -> int {
return progressBarResetMaximum();
};
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
set_progressBarTextVisible(visible);
+1 -1
View File
@@ -19,7 +19,7 @@ class CloakLogic : public PageProtocolLogicBase
AUTO_PROPERTY(bool, labelInfoVisible)
AUTO_PROPERTY(QString, labelInfoText)
AUTO_PROPERTY(int, progressBarResetValue)
AUTO_PROPERTY(int, progressBarResetMaximium)
AUTO_PROPERTY(int, progressBarResetMaximum)
AUTO_PROPERTY(bool, progressBarTextVisible)
AUTO_PROPERTY(QString, progressBarText)
@@ -33,7 +33,7 @@ OpenVpnLogic::OpenVpnLogic(UiLogic *logic, QObject *parent):
m_labelProtoOpenVpnInfoVisible{true},
m_labelProtoOpenVpnInfoText{},
m_progressBarResetValue{0},
m_progressBarResetMaximium{100}
m_progressBarResetMaximum{100}
{
}
@@ -51,17 +51,17 @@ void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerCo
set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address).
toString(protocols::openvpn::defaultSubnetAddress));
QString trasnsport;
QString transport;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
trasnsport = "tcp";
transport = "tcp";
set_radioButtonUdpEnabled(false);
set_radioButtonTcpEnabled(false);
} else {
trasnsport = openvpnConfig.value(config_key::transport_proto).
transport = openvpnConfig.value(config_key::transport_proto).
toString(protocols::openvpn::defaultTransportProto);
}
set_radioButtonUdpChecked(trasnsport == protocols::openvpn::defaultTransportProto);
set_radioButtonTcpChecked(trasnsport != protocols::openvpn::defaultTransportProto);
set_radioButtonUdpChecked(transport == protocols::openvpn::defaultTransportProto);
set_radioButtonTcpChecked(transport != protocols::openvpn::defaultTransportProto);
set_comboBoxVpnCipherText(openvpnConfig.value(config_key::cipher).
toString(protocols::openvpn::defaultCipher));
@@ -137,8 +137,8 @@ void OpenVpnLogic::onPushButtonSaveClicked()
progressBarFunc.getValueFunc = [this] (void) -> int {
return progressBarResetValue();
};
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
return progressBarResetMaximium();
progressBarFunc.getMaximumFunc = [this] (void) -> int {
return progressBarResetMaximum();
};
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
set_progressBarTextVisible(visible);
@@ -33,7 +33,7 @@ class OpenVpnLogic : public PageProtocolLogicBase
AUTO_PROPERTY(bool, labelProtoOpenVpnInfoVisible)
AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText)
AUTO_PROPERTY(int, progressBarResetValue)
AUTO_PROPERTY(int, progressBarResetMaximium)
AUTO_PROPERTY(int, progressBarResetMaximum)
AUTO_PROPERTY(bool, progressBarTextVisible)
AUTO_PROPERTY(QString, progressBarText)
@@ -19,7 +19,7 @@ ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent):
m_labelInfoVisible{true},
m_labelInfoText{},
m_progressBarResetValue{0},
m_progressBarResetMaximium{100}
m_progressBarResetMaximum{100}
{
}
@@ -79,8 +79,8 @@ void ShadowSocksLogic::onPushButtonSaveClicked()
progressBarFunc.getValueFunc = [this] (void) -> int {
return progressBarResetValue();
};
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
return progressBarResetMaximium();
progressBarFunc.getMaximumFunc = [this] (void) -> int {
return progressBarResetMaximum();
};
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
set_progressBarTextVisible(visible);
@@ -17,7 +17,7 @@ class ShadowSocksLogic : public PageProtocolLogicBase
AUTO_PROPERTY(bool, labelInfoVisible)
AUTO_PROPERTY(QString, labelInfoText)
AUTO_PROPERTY(int, progressBarResetValue)
AUTO_PROPERTY(int, progressBarResetMaximium)
AUTO_PROPERTY(int, progressBarResetMaximum)
AUTO_PROPERTY(bool, progressBarTextVisible)
AUTO_PROPERTY(QString, progressBarText)
@@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import ProtocolEnum 1.0
import "../"
import "../../Controls"
import "../../Config"
PageBase {
id: root
property var protocol: ProtocolEnum.Any
page: PageEnum.ClientInfo
logic: ClientInfoLogic
}
@@ -0,0 +1,115 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import ProtocolEnum 1.0
import "../"
import "../../Controls"
import "../../Config"
PageClientInfoBase {
id: root
protocol: ProtocolEnum.OpenVpn
BackButton {
id: back
enabled: !ClientInfoLogic.busyIndicatorIsRunning
}
Caption {
id: caption
text: qsTr("Client Info")
}
BusyIndicator {
z: 99
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
visible: ClientInfoLogic.busyIndicatorIsRunning
running: ClientInfoLogic.busyIndicatorIsRunning
}
FlickableType {
id: fl
anchors.top: caption.bottom
contentHeight: content.height
visible: ClientInfoLogic.pageContentVisible
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: GC.defaultMargin
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
text: ClientInfoLogic.labelCurrentVpnProtocolText
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
height: 21
text: qsTr("Client name")
}
TextFieldType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
Layout.preferredHeight: 31
text: ClientInfoLogic.lineEditNameAliasText
onEditingFinished: {
if (text !== ClientInfoLogic.lineEditNameAliasText) {
ClientInfoLogic.lineEditNameAliasText = text
ClientInfoLogic.onLineEditNameAliasEditingFinished()
}
}
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.topMargin: 20
height: 21
text: qsTr("Certificate id")
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: ClientInfoLogic.labelOpenVpnCertId
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.topMargin: 20
height: 21
text: qsTr("Certificate")
}
TextAreaType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.preferredHeight: 200
Layout.fillWidth: true
textArea.readOnly: true
textArea.wrapMode: TextEdit.WrapAnywhere
textArea.verticalAlignment: Text.AlignTop
textArea.text: ClientInfoLogic.textAreaOpenVpnCertData
}
BlueButtonType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
Layout.preferredHeight: 41
text: qsTr("Revoke Certificate")
onClicked: {
ClientInfoLogic.onRevokeOpenVpnCertificateClicked()
UiLogic.closePage()
}
}
}
}
}
@@ -0,0 +1,100 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import ProtocolEnum 1.0
import "../"
import "../../Controls"
import "../../Config"
PageClientInfoBase {
id: root
protocol: ProtocolEnum.WireGuard
BackButton {
id: back
enabled: !ClientInfoLogic.busyIndicatorIsRunning
}
Caption {
id: caption
text: qsTr("Client Info")
}
BusyIndicator {
z: 99
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
visible: ClientInfoLogic.busyIndicatorIsRunning
running: ClientInfoLogic.busyIndicatorIsRunning
}
FlickableType {
id: fl
anchors.top: caption.bottom
contentHeight: content.height
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: GC.defaultMargin
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
text: ClientInfoLogic.labelCurrentVpnProtocolText
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
height: 21
text: qsTr("Client name")
}
TextFieldType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
Layout.preferredHeight: 31
text: ClientInfoLogic.lineEditNameAliasText
onEditingFinished: {
if (text !== ClientInfoLogic.lineEditNameAliasText) {
ClientInfoLogic.lineEditNameAliasText = text
ClientInfoLogic.onLineEditNameAliasEditingFinished()
}
}
}
LabelType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.topMargin: 20
height: 21
text: qsTr("Public Key")
}
TextAreaType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.preferredHeight: 200
Layout.fillWidth: true
textArea.readOnly: true
textArea.wrapMode: TextEdit.WrapAnywhere
textArea.verticalAlignment: Text.AlignTop
textArea.text: ClientInfoLogic.textAreaWireGuardKeyData
}
BlueButtonType {
enabled: !ClientInfoLogic.busyIndicatorIsRunning
Layout.fillWidth: true
Layout.preferredHeight: 41
text: qsTr("Revoke Key")
onClicked: {
ClientInfoLogic.onRevokeWireGuardKeyClicked()
UiLogic.closePage()
}
}
}
}
}
@@ -88,6 +88,15 @@ PageBase {
}
}
BlueButtonType {
Layout.topMargin: 10
Layout.fillWidth: true
text: qsTr("Clients Management")
onClicked: {
UiLogic.goToPage(PageEnum.ClientManagement)
}
}
PopupWithQuestion {
id: popupClearServer
questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?"
@@ -0,0 +1,119 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes 1.4
import SortFilterProxyModel 0.2
import PageEnum 1.0
import "./"
import "../Controls"
import "../Config"
PageBase {
id: root
page: PageEnum.ClientManagement
logic: ClientManagementLogic
enabled: !ClientManagementLogic.busyIndicatorIsRunning
BackButton {
id: back
}
Caption {
id: caption
text: qsTr("Clients Management")
}
BusyIndicator {
z: 99
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
visible: ClientManagementLogic.busyIndicatorIsRunning
running: ClientManagementLogic.busyIndicatorIsRunning
}
FlickableType {
id: fl
anchors.top: caption.bottom
contentHeight: content.height
Column {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelType {
font.pixelSize: 20
leftPadding: -20
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: ClientManagementLogic.labelCurrentVpnProtocolText
}
SortFilterProxyModel {
id: proxyClientManagementModel
sourceModel: UiLogic.clientManagementModel
sorters: RoleSorter { roleName: "clientName" }
}
ListView {
id: lv_clients
width: parent.width
implicitHeight: contentHeight + 20
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 20
topMargin: 10
spacing: 10
clip: true
model: proxyClientManagementModel
highlightRangeMode: ListView.ApplyRange
highlightMoveVelocity: -1
delegate: Item {
implicitWidth: lv_clients.width
implicitHeight: 60
MouseArea {
id: ms
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index))
}
}
Rectangle {
anchors.fill: parent
gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse
LinearGradient {
id: gradient_notContainsMouse
x1: 0 ; y1:0
x2: 0 ; y2: height
stops: [
GradientStop { position: 0.0; color: "#FAFBFE" },
GradientStop { position: 1.0; color: "#ECEEFF" }
]
}
LinearGradient {
id: gradient_containsMouse
x1: 0 ; y1:0
x2: 0 ; y2: height
stops: [
GradientStop { position: 0.0; color: "#FAFBFE" },
GradientStop { position: 1.0; color: "#DCDEDF" }
]
}
}
LabelType {
x: 20
y: 20
font.pixelSize: 20
text: clientName
}
}
}
}
}
}
@@ -77,7 +77,7 @@ PageBase {
enabled: ServerConfiguringProgressLogic.pageEnabled
anchors.fill: pb_cancel
from: 0
to: ServerConfiguringProgressLogic.progressBarMaximium
to: ServerConfiguringProgressLogic.progressBarMaximum
value: ServerConfiguringProgressLogic.progressBarValue
visible: ServerConfiguringProgressLogic.progressBarVisible
background: Rectangle {
+1 -1
View File
@@ -16,6 +16,7 @@ PageBase {
BackButton {
id: back
}
Caption {
id: caption
text: qsTr("Server settings")
@@ -89,7 +90,6 @@ PageBase {
Layout.fillWidth: true
Layout.topMargin: 10
text: qsTr("Advanced server settings")
visible: ServerSettingsLogic.pushButtonShareFullVisible //todo
onClicked: {
UiLogic.goToPage(PageEnum.AdvancedServerSettings)
}
@@ -37,7 +37,7 @@ PageBase {
Layout.fillWidth: true
verticalAlignment: Text.AlignTop
text: qsTr('Optional.\n
You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every bloked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.')
You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.')
}
CheckBoxType {
@@ -133,7 +133,7 @@ PageProtocolBase {
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: pb_save
from: 0
to: logic.progressBarResetMaximium
to: logic.progressBarResetMaximum
value: logic.progressBarResetValue
background: Rectangle {
implicitWidth: parent.width
@@ -393,7 +393,7 @@ PageProtocolBase {
id: progress_save
anchors.fill: pb_save
from: 0
to: logic.progressBarResetMaximium
to: logic.progressBarResetMaximum
value: logic.progressBarResetValue
visible: logic.progressBarResetVisible
background: Rectangle {
@@ -113,7 +113,7 @@ PageProtocolBase {
id: progressBar_reset
anchors.fill: pb_save
from: 0
to: logic.progressBarResetMaximium
to: logic.progressBarResetMaximum
value: logic.progressBarResetValue
visible: logic.progressBarResetVisible
background: Rectangle {
@@ -15,7 +15,7 @@ PageShareProtocolBase {
Caption {
id: caption
text: qsTr("Share SFTF settings")
text: qsTr("Share SFTP settings")
}
}
+26 -4
View File
@@ -12,12 +12,14 @@ import "Controls"
import "Pages"
import "Pages/Protocols"
import "Pages/Share"
import "Pages/ClientInfo"
import "Config"
Window {
property var pages: ({})
property var protocolPages: ({})
property var sharePages: ({})
property var clientInfoPages: ({})
id: root
visible: true
@@ -38,6 +40,7 @@ Window {
if (type === PageType.Basic) p_obj = pages[page]
else if (type === PageType.Proto) p_obj = protocolPages[page]
else if (type === PageType.ShareProto) p_obj = sharePages[page]
else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page]
else return
//console.debug("QML gotoPage " + type + " " + page + " " + p_obj)
@@ -124,7 +127,7 @@ Window {
for (var i=0; i<folderModelPages.count; i++) {
createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic);
}
UiLogic.initalizeUiLogic()
UiLogic.initializeUiLogic()
}
}
@@ -154,9 +157,22 @@ Window {
}
}
FolderListModel {
id: folderModelClientInfo
folder: "qrc:/ui/qml/Pages/ClientInfo/"
nameFilters: ["*.qml"]
showDirs: false
onStatusChanged: if (status == FolderListModel.Ready) {
for (var i=0; i<folderModelClientInfo.count; i++) {
createPagesObjects(folderModelClientInfo.get(i, "filePath"), PageType.ClientInfo);
}
}
}
function createPagesObjects(file, type) {
if (file.indexOf("Base") !== -1) return; // skip Base Pages
//console.debug("Creating compenent " + file + " for " + type);
//console.debug("Creating component " + file + " for " + type);
var c = Qt.createComponent("qrc" + file);
@@ -177,8 +193,11 @@ Window {
else if (type === PageType.ShareProto) {
sharePages[obj.protocol] = obj
}
else if (type === PageType.ClientInfo) {
clientInfoPages[obj.protocol] = obj
}
// console.debug("Created compenent " + component.url + " for " + type);
// console.debug("Created component " + component.url + " for " + type);
}
} else if (component.status === Component.Error) {
console.debug("Error loading component:", component.errorString());
@@ -206,7 +225,10 @@ Window {
//console.debug("Qml Connections onGoToShareProtocolPage " + protocol);
root.gotoPage(PageType.ShareProto, protocol, reset, slide)
}
function onGoToClientInfoPage(protocol, reset, slide) {
//console.debug("Qml Connections onGoToClientInfoPage " + protocol);
root.gotoPage(PageType.ClientInfo, protocol, reset, slide)
}
function onClosePage() {
root.close_page()
+11 -3
View File
@@ -66,6 +66,8 @@
#include "pages_logic/VpnLogic.h"
#include "pages_logic/WizardLogic.h"
#include "pages_logic/AdvancedServerSettingsLogic.h"
#include "pages_logic/ClientManagementLogic.h"
#include "pages_logic/ClientInfoLogic.h"
#include "pages_logic/protocols/CloakLogic.h"
#include "pages_logic/protocols/OpenVpnLogic.h"
@@ -84,6 +86,7 @@ UiLogic::UiLogic(std::shared_ptr<Settings> settings, std::shared_ptr<VpnConfigur
{
m_containersModel = new ContainersModel(settings, this);
m_protocolsModel = new ProtocolsModel(settings, this);
m_clientManagementModel = new ClientManagementModel(this);
m_vpnConnection = new VpnConnection(settings, configurator);
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -124,7 +127,7 @@ UiLogic::~UiLogic()
qDebug() << "Application closed";
}
void UiLogic::initalizeUiLogic()
void UiLogic::initializeUiLogic()
{
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) {
@@ -178,6 +181,9 @@ void UiLogic::showOnStartup()
void UiLogic::onUpdateAllPages()
{
for (auto logic : m_logicMap) {
if (dynamic_cast<ClientInfoLogic*>(logic) || dynamic_cast<ClientManagementLogic*>(logic)) {
continue;
}
logic->onUpdatePage();
}
}
@@ -305,8 +311,8 @@ void UiLogic::installServer(QPair<DockerContainer, QJsonObject> &container)
progressBarFunc.getValueFunc = [this] (void) -> int {
return pageLogic<ServerConfiguringProgressLogic>()->progressBarValue();
};
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
return pageLogic<ServerConfiguringProgressLogic>()->progressBarMaximium();
progressBarFunc.getMaximumFunc = [this] (void) -> int {
return pageLogic<ServerConfiguringProgressLogic>()->progressBarMaximum();
};
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
pageLogic<ServerConfiguringProgressLogic>()->set_progressBarTextVisible(visible);
@@ -533,6 +539,8 @@ void UiLogic::registerPagesLogic()
registerPageLogic<ViewConfigLogic>();
registerPageLogic<VpnLogic>();
registerPageLogic<WizardLogic>();
registerPageLogic<ClientManagementLogic>();
registerPageLogic<ClientInfoLogic>();
registerPageLogic<AdvancedServerSettingsLogic>();
}
+8 -1
View File
@@ -19,6 +19,7 @@
#include "models/containers_model.h"
#include "models/protocols_model.h"
#include "models/clientManagementModel.h"
#include "notificationhandler.h"
@@ -43,6 +44,8 @@ class StartPageLogic;
class ViewConfigLogic;
class VpnLogic;
class WizardLogic;
class ClientManagementLogic;
class ClientInfoLogic;
class AdvancedServerSettingsLogic;
class PageProtocolLogicBase;
@@ -66,6 +69,7 @@ class UiLogic : public QObject
READONLY_PROPERTY(QObject *, containersModel)
READONLY_PROPERTY(QObject *, protocolsModel)
READONLY_PROPERTY(QObject *, clientManagementModel)
public:
explicit UiLogic(std::shared_ptr<Settings> settings, std::shared_ptr<VpnConfigurator> configurator, QObject *parent = nullptr);
@@ -88,6 +92,8 @@ public:
friend class ViewConfigLogic;
friend class VpnLogic;
friend class WizardLogic;
friend class ClientManagementLogic;
friend class ClientInfoLogic;
friend class AdvancedServerSettingsLogic;
friend class PageProtocolLogicBase;
@@ -102,7 +108,7 @@ public:
Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages
Q_INVOKABLE void onUpdateAllPages();
Q_INVOKABLE void initalizeUiLogic();
Q_INVOKABLE void initializeUiLogic();
Q_INVOKABLE void onCloseWindow();
Q_INVOKABLE QString containerName(int container);
@@ -129,6 +135,7 @@ signals:
void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true);
void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true);
void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true);
void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true);
void closePage();
void setStartPage(PageEnumNS::Page page, bool slide = true);
+3 -3
View File
@@ -120,9 +120,9 @@ QString Utils::getIPAddress(const QString& host)
return host;
}
QList<QHostAddress> adresses = QHostInfo::fromName(host).addresses();
if (!adresses.isEmpty()) {
return adresses.first().toString();
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
if (!addresses.isEmpty()) {
return addresses.first().toString();
}
qDebug() << "Unable to resolve address for " << host;
return "";
+2 -2
View File
@@ -307,7 +307,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex,
void VpnConnection::connectToVpn(int serverIndex,
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
{
qDebug() << QString("СonnectToVpn, Server index is %1, container is %2, route mode is")
qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is")
.arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode();
#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS)
@@ -317,7 +317,7 @@ void VpnConnection::connectToVpn(int serverIndex,
if (!m_IpcClient->isSocketConnected()) {
if (!IpcClient::init(m_IpcClient)) {
qWarning() << "Error occured when init IPC client";
qWarning() << "Error occurred when init IPC client";
emit serviceIsNotReady();
emit connectionStateChanged(VpnProtocol::Error);
return;
+1 -1
View File
@@ -20,7 +20,7 @@ APP_DOMAIN=org.amneziavpn.package
OUT_APP_DIR=$BUILD_DIR/client
BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
# Seacrh Qt
# Search Qt
if [ -z "${QT_VERSION+x}" ]; then
QT_VERSION=6.4.1;
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/$ANDROID_CURRENT_ARCH/bin
+1 -1
View File
@@ -24,7 +24,7 @@ BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
# Seacrh Qt
# Search Qt
if [ -z "${QT_VERSION+x}" ]; then
QT_VERSION=5.15.2;
QIF_VERSION=4.1
+1 -1
View File
@@ -33,7 +33,7 @@ INSTALLER_DATA_DIR=$PROJECT_DIR/deploy/installer/packages/$APP_DOMAIN/data
PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
# Seacrh Qt
# Search Qt
if [ -z "${QT_VERSION+x}" ]; then
QT_VERSION=5.15.2
if [ -f /opt/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
+4 -4
View File
@@ -36,7 +36,7 @@ PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg
# Seacrh Qt
# Search Qt
if [ -z "${QT_VERSION+x}" ]; then
QT_VERSION=6.4.1;
QIF_VERSION=4.1
@@ -102,7 +102,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
spctl -a -vvvv $BUNDLE_DIR || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notatizing App bundle..."
echo "Notarizing App bundle..."
/usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip
xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
rm $PROJECT_DIR/Bundle_to_notarize.zip
@@ -135,7 +135,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
/usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notatizing installer bundle..."
echo "Notarizing installer bundle..."
/usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip
xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
rm $PROJECT_DIR/Installer_bundle_to_notarize.zip
@@ -156,7 +156,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
/usr/bin/codesign --verify -vvvv $DMG_FILENAME || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notatizing DMG installer..."
echo "Notarizing DMG installer..."
xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD
sleep 600
xcrun stapler staple $DMG_FILENAME

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