Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vladimir.kuznetsov 2024-01-28 21:24:23 +07:00
commit 3240aa3cb3
92 changed files with 2368 additions and 1568 deletions

View file

@ -48,14 +48,18 @@ jobs:
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Linux_installer name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer path: deploy/AmneziaVPN_Linux_Installer.tar
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Linux_unpacked name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir path: deploy/AppDir
@ -110,13 +114,14 @@ jobs:
call deploy\\build_windows.bat call deploy\\build_windows.bat
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Windows_installer name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Windows_unpacked name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
@ -200,7 +205,7 @@ jobs:
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }} IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
# - name: 'Upload appstore .ipa and dSYMs to artifacts' # - name: 'Upload appstore .ipa and dSYMs to artifacts'
# uses: actions/upload-artifact@v3 # uses: actions/upload-artifact@v4
# with: # with:
# name: app-store ipa & dsyms # name: app-store ipa & dsyms
# path: | # path: |
@ -255,13 +260,14 @@ jobs:
bash deploy/build_macos.sh bash deploy/build_macos.sh
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_MacOS_installer name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg path: AmneziaVPN.dmg
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_MacOS_unpacked name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app path: deploy/build/client/AmneziaVPN.app
@ -375,32 +381,44 @@ jobs:
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
shell: bash shell: bash
run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Upload x86_64 apk' - name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-x86_64 name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload x86 apk' - name: 'Upload x86 apk'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-x86 name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload arm64-v8a apk' - name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-arm64-v8a name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload armeabi-v7a apk' - name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-armeabi-v7a name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload aab'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android
path: deploy/build/AmneziaVPN-release.aab
compression-level: 0
retention-days: 7 retention-days: 7

6
.gitmodules vendored
View file

@ -22,6 +22,6 @@
[submodule "client/3rd-prebuilt"] [submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/awg-apple"] [submodule "client/3rd/amneziawg-apple"]
path = client/3rd/awg-apple path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/awg-apple url = https://github.com/amnezia-vpn/amneziawg-apple

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.1.0.1 project(${PROJECT} VERSION 4.2.1.0
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 39) set(APP_ANDROID_VERSION_CODE 42)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")

View file

@ -18,7 +18,6 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English) [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian) [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
[https://signal.group/...](https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh) - Signal channel
## Tech ## Tech
@ -36,7 +35,7 @@ AmneziaVPN uses a number of open source projects to work:
Make sure to pull all submodules after checking out the repo. Make sure to pull all submodules after checking out the repo.
```bash ```bash
git submodule update --init git submodule update --init --recursive
``` ```
## Development ## Development
@ -50,7 +49,15 @@ Look deploy folder for build scripts.
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher. 1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. we need QT version 6.4. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) 2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
- macOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/) 3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
@ -66,10 +73,11 @@ gomobile init
5. Build project 5. Build project
```bash ```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin" export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin export PATH=$PATH:~/go/bin
mkdir build-ios mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_BIN_DIR $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
``` ```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment

@ -1 +1 @@
Subproject commit fcf3022a2724402f68cc11bcbed9b43ea9ffcc07 Subproject commit e568e7d0e8defe8fe009c0127323f2c55fd9be76

1
client/3rd/amneziawg-apple vendored Submodule

@ -0,0 +1 @@
Subproject commit f23eee4700ed4a2ef44a800d2c20466c9ab0222b

@ -1 +0,0 @@
Subproject commit 233eda6760962efddc860f177a0ce2bcdf533d85

@ -1 +1 @@
Subproject commit 8bbaa6d8302cf0747d9786ace4dd13c7fb746502 Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd

View file

@ -91,6 +91,13 @@ void AmneziaApplication::init()
initControllers(); initControllers();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
if(!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged,
AndroidController::instance(), &AndroidController::setSaveLogs);
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) { [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state); m_connectionController->onConnectionStateChanged(state);
@ -98,10 +105,7 @@ void AmneziaApplication::init()
m_vpnConnection->restoreConnection(); m_vpnConnection->restoreConnection();
}); });
if (!AndroidController::instance()->initialize()) { if (!AndroidController::instance()->initialize()) {
qCritical() << QString("Init failed"); qFatal("Android controller initialization failed");
if (m_vpnConnection)
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
return;
} }
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
@ -143,11 +147,13 @@ void AmneziaApplication::init()
m_engine->load(url); m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
#ifndef Q_OS_ANDROID
if (m_settings->isSaveLogs()) { if (m_settings->isSaveLogs()) {
if (!Logger::init()) { if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
#endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (m_parser.isSet("a")) if (m_parser.isSet("a"))
@ -168,16 +174,19 @@ void AmneziaApplication::init()
} }
#endif #endif
// Android TextField clipboard workaround // Android TextArea clipboard workaround
// https://bugreports.qt.io/browse/QTBUG-113461 // Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) { connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
if (state == Qt::ApplicationActive) { auto clipboard = QGuiApplication::clipboard();
if (qApp->clipboard()->mimeData()->formats().contains("text/html")) { if (clipboard->mimeData()->hasHtml()) {
QTextDocument doc; clipboard->setText(clipboard->text());
doc.setHtml(qApp->clipboard()->mimeData()->html());
qApp->clipboard()->setText(doc.toPlainText());
}
} }
}); });
#endif #endif
@ -322,12 +331,15 @@ void AmneziaApplication::initModels()
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked,
m_serversModel.get(), &ServersModel::clearCachedProfile);
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
[this](const QString &clientId, const QString &clientName, const DockerContainer container, [this](const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) { ServerCredentials credentials) {
m_serversModel->reloadContainerConfig(); m_serversModel->reloadContainerConfig();
m_clientManagementModel->appendClient(clientId, clientName, container, credentials); m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated();
}); });
} }
@ -358,7 +370,7 @@ void AmneziaApplication::initControllers()
m_settings, m_configurator)); m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });

View file

@ -1,6 +1,8 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- Leave package attribute for androiddeployqt --> <!-- Leave package attribute for androiddeployqt -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn" package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --"
@ -22,7 +24,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Enable when VPN-per-app mode will be implemented --> <!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> --> <!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
@ -32,13 +33,17 @@
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon" android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round" android:roundIcon="@mipmap/icon_round"
android:theme="@style/NoActionBar"> android:theme="@style/NoActionBar"
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">
<activity <activity
android:name=".AmneziaActivity" android:name=".AmneziaActivity"
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -147,7 +152,7 @@
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>

View file

@ -99,7 +99,7 @@ class AwgConfig private constructor(
fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 } fun setH4(h4: Long) = apply { this.h4 = h4 }
override fun build(): AwgConfig = AwgConfig(this) override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
} }
companion object { companion object {

View file

@ -108,7 +108,6 @@ dependencies {
implementation(project(":cloak")) implementation(project(":cloak"))
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit) implementation(libs.google.mlkit)

View file

@ -3,6 +3,9 @@ package org.amnezia.vpn.protocol.cloak
import android.util.Base64 import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject import org.json.JSONObject
/** /**
@ -51,6 +54,13 @@ class Cloak : OpenVpn() {
return openVpnConfig return openVpnConfig
} }
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1) cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn") cloakConfigJson.put("ProxyMethod", "openvpn")

View file

@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol.openvpn
import android.content.Context import android.content.Context
import android.net.VpnService.Builder import android.net.VpnService.Builder
import android.os.Build
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -14,7 +13,6 @@ import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks import org.amnezia.vpn.util.net.getLocalNetworks
import org.json.JSONObject import org.json.JSONObject
@ -79,16 +77,8 @@ open class OpenVpn : Protocol() {
if (evalConfig.error) { if (evalConfig.error) {
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
} }
configBuilder.apply { configPluggableTransport(configBuilder, config)
// fix for split tunneling configBuilder.configSplitTunneling(config)
// The exclude split tunneling OpenVpn configuration does not contain a default route.
// It is required for split tunneling in newer versions of Android.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
configSplitTunneling(config)
}
scope.launch { scope.launch {
val status = client.connect() val status = client.connect()
@ -122,6 +112,8 @@ open class OpenVpn : Protocol() {
return openVpnConfig return openVpnConfig
} }
protected open fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {}
private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder -> private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder ->
val openVpnConfig = configBuilder.build() val openVpnConfig = configBuilder.build()
buildVpnInterface(openVpnConfig, vpnBuilder) buildVpnInterface(openVpnConfig, vpnBuilder)

View file

@ -52,7 +52,7 @@ class OpenVpnClient(
// Callback to construct a new tun builder // Callback to construct a new tun builder
// Should be called first. // Should be called first.
override fun tun_builder_new(): Boolean { override fun tun_builder_new(): Boolean {
Log.v(TAG, "tun_builder_new") Log.d(TAG, "tun_builder_new")
configBuilder.clearAddresses() configBuilder.clearAddresses()
return true return true
} }
@ -60,7 +60,7 @@ class OpenVpnClient(
// Callback to set MTU of the VPN interface // Callback to set MTU of the VPN interface
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_mtu(mtu: Int): Boolean { override fun tun_builder_set_mtu(mtu: Int): Boolean {
Log.v(TAG, "tun_builder_set_mtu: $mtu") Log.d(TAG, "tun_builder_set_mtu: $mtu")
configBuilder.setMtu(mtu) configBuilder.setMtu(mtu)
return true return true
} }
@ -71,7 +71,7 @@ class OpenVpnClient(
address: String, prefix_length: Int, address: String, prefix_length: Int,
gateway: String, ipv6: Boolean, net30: Boolean gateway: String, ipv6: Boolean, net30: Boolean
): Boolean { ): Boolean {
Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30")
configBuilder.addAddress(InetNetwork(address, prefix_length)) configBuilder.addAddress(InetNetwork(address, prefix_length))
return true return true
} }
@ -80,7 +80,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6")
if (address == "remote_host") return false if (address == "remote_host") return false
configBuilder.addRoute(InetNetwork(address, prefix_length)) configBuilder.addRoute(InetNetwork(address, prefix_length))
return true return true
@ -90,10 +90,8 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { configBuilder.excludeRoute(InetNetwork(address, prefix_length))
configBuilder.excludeRoute(InetNetwork(address, prefix_length))
}
return true return true
} }
@ -104,7 +102,7 @@ class OpenVpnClient(
// domain should be routed. // domain should be routed.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6") Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6")
configBuilder.addDnsServer(parseInetAddress(address)) configBuilder.addDnsServer(parseInetAddress(address))
return true return true
} }
@ -119,28 +117,28 @@ class OpenVpnClient(
// ignored for that family // ignored for that family
// See also Android's VPNService.Builder.allowFamily method // See also Android's VPNService.Builder.allowFamily method
/* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { /* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean {
Log.v(TAG, "tun_builder_set_allow_family: $af, $allow") Log.d(TAG, "tun_builder_set_allow_family: $af, $allow")
return true return true
} */ } */
// Callback to set address of remote server // Callback to set address of remote server
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6") Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6")
return true return true
} }
// Optional callback that indicates OSI layer, should be 2 or 3. // Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3. // Defaults to 3.
override fun tun_builder_set_layer(layer: Int): Boolean { override fun tun_builder_set_layer(layer: Int): Boolean {
Log.v(TAG, "tun_builder_set_layer: $layer") Log.d(TAG, "tun_builder_set_layer: $layer")
return layer == 3 return layer == 3
} }
// Callback to set the session name // Callback to set the session name
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_session_name(name: String): Boolean { override fun tun_builder_set_session_name(name: String): Boolean {
Log.v(TAG, "tun_builder_set_session_name: $name") Log.d(TAG, "tun_builder_set_session_name: $name")
return true return true
} }
@ -149,7 +147,7 @@ class OpenVpnClient(
// if the tunnel could not be established. // if the tunnel could not be established.
// Always called last after tun_builder session has been configured. // Always called last after tun_builder session has been configured.
override fun tun_builder_establish(): Int { override fun tun_builder_establish(): Int {
Log.v(TAG, "tun_builder_establish") Log.d(TAG, "tun_builder_establish")
return establish(configBuilder) return establish(configBuilder)
} }
@ -159,7 +157,7 @@ class OpenVpnClient(
// flags are defined in RGWFlags (rgwflags.hpp). // flags are defined in RGWFlags (rgwflags.hpp).
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean {
Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags")
if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true
if (ipv4) { if (ipv4) {
configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) configBuilder.addRoute(InetNetwork("0.0.0.0", 0))
@ -176,7 +174,7 @@ class OpenVpnClient(
// reroute_dns parameter. // reroute_dns parameter.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_search_domain(domain: String): Boolean { override fun tun_builder_add_search_domain(domain: String): Boolean {
Log.v(TAG, "tun_builder_add_search_domain: $domain") Log.d(TAG, "tun_builder_add_search_domain: $domain")
configBuilder.setSearchDomain(domain) configBuilder.setSearchDomain(domain)
return true return true
} }
@ -184,7 +182,7 @@ class OpenVpnClient(
// Callback to set the HTTP proxy // Callback to set the HTTP proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean {
Log.v(TAG, "tun_builder_set_proxy_http: $host, $port") Log.d(TAG, "tun_builder_set_proxy_http: $host, $port")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try { try {
configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port))
@ -199,7 +197,7 @@ class OpenVpnClient(
// Callback to set the HTTPS proxy // Callback to set the HTTPS proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean {
Log.v(TAG, "tun_builder_set_proxy_https: $host, $port") Log.d(TAG, "tun_builder_set_proxy_https: $host, $port")
return false return false
} }
@ -208,7 +206,7 @@ class OpenVpnClient(
// to exclude them from the VPN network are generated // to exclude them from the VPN network are generated
// This should be a list of CIDR networks (e.g. 192.168.0.0/24) // This should be a list of CIDR networks (e.g. 192.168.0.0/24)
override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec { override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec {
Log.v(TAG, "tun_builder_get_local_networks: $ipv6") Log.d(TAG, "tun_builder_get_local_networks: $ipv6")
val networks = ClientAPI_StringVec() val networks = ClientAPI_StringVec()
for (address in getLocalNetworks(ipv6)) { for (address in getLocalNetworks(ipv6)) {
networks.add(address.toString()) networks.add(address.toString())
@ -222,21 +220,21 @@ class OpenVpnClient(
// tun_builder_reroute_gw. Route metric is ignored // tun_builder_reroute_gw. Route metric is ignored
// if < 0. // if < 0.
/* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { /* override fun tun_builder_set_route_metric_default(metric: Int): Boolean {
Log.v(TAG, "tun_builder_set_route_metric_default: $metric") Log.d(TAG, "tun_builder_set_route_metric_default: $metric")
return super.tun_builder_set_route_metric_default(metric) return super.tun_builder_set_route_metric_default(metric)
} */ } */
// Callback to add a host which should bypass the proxy // Callback to add a host which should bypass the proxy
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
/* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { /* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean {
Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host") Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host")
return super.tun_builder_add_proxy_bypass(bypass_host) return super.tun_builder_add_proxy_bypass(bypass_host)
} */ } */
// Callback to set the proxy "Auto Config URL" // Callback to set the proxy "Auto Config URL"
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
/* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { /* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean {
Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url") Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url")
return super.tun_builder_set_proxy_auto_config_url(url) return super.tun_builder_set_proxy_auto_config_url(url)
} */ } */
@ -245,7 +243,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session. // May be called more than once per tun_builder session.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
/* override fun tun_builder_add_wins_server(address: String): Boolean { /* override fun tun_builder_add_wins_server(address: String): Boolean {
Log.v(TAG, "tun_builder_add_wins_server: $address") Log.d(TAG, "tun_builder_add_wins_server: $address")
return super.tun_builder_add_wins_server(address) return super.tun_builder_add_wins_server(address)
} */ } */
@ -254,7 +252,7 @@ class OpenVpnClient(
// set the "Connection-specific DNS Suffix" property on // set the "Connection-specific DNS Suffix" property on
// the TAP driver. // the TAP driver.
/* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { /* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean {
Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name") Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name")
return super.tun_builder_set_adapter_domain_suffix(name) return super.tun_builder_set_adapter_domain_suffix(name)
} */ } */
@ -266,13 +264,13 @@ class OpenVpnClient(
// tun_builder_establish_lite() will be called. Otherwise, // tun_builder_establish_lite() will be called. Otherwise,
// tun_builder_establish() will be called. // tun_builder_establish() will be called.
/* override fun tun_builder_persist(): Boolean { /* override fun tun_builder_persist(): Boolean {
Log.v(TAG, "tun_builder_persist") Log.d(TAG, "tun_builder_persist")
return super.tun_builder_persist() return super.tun_builder_persist()
} */ } */
// Indicates a reconnection with persisted tun state. // Indicates a reconnection with persisted tun state.
/* override fun tun_builder_establish_lite() { /* override fun tun_builder_establish_lite() {
Log.v(TAG, "tun_builder_establish_lite") Log.d(TAG, "tun_builder_establish_lite")
super.tun_builder_establish_lite() super.tun_builder_establish_lite()
} */ } */
@ -280,7 +278,7 @@ class OpenVpnClient(
// If disconnect == true, then the teardown is occurring // If disconnect == true, then the teardown is occurring
// prior to final disconnect. // prior to final disconnect.
/* override fun tun_builder_teardown(disconnect: Boolean) { /* override fun tun_builder_teardown(disconnect: Boolean) {
Log.v(TAG, "tun_builder_teardown: $disconnect") Log.d(TAG, "tun_builder_teardown: $disconnect")
super.tun_builder_teardown(disconnect) super.tun_builder_teardown(disconnect)
} */ } */
@ -290,7 +288,7 @@ class OpenVpnClient(
// Parse OpenVPN configuration file. // Parse OpenVPN configuration file.
override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig {
Log.v(TAG, "eval_config") Log.d(TAG, "eval_config")
return super.eval_config(arg0) return super.eval_config(arg0)
} }
@ -299,7 +297,7 @@ class OpenVpnClient(
// to event() and log() functions. Make sure to call eval_config() // to event() and log() functions. Make sure to call eval_config()
// and possibly provide_creds() as well before this function. // and possibly provide_creds() as well before this function.
override fun connect(): ClientAPI_Status { override fun connect(): ClientAPI_Status {
Log.v(TAG, "connect") Log.d(TAG, "connect")
return super.connect() return super.connect()
} }
@ -307,7 +305,7 @@ class OpenVpnClient(
// Will be called from the thread executing connect(). // Will be called from the thread executing connect().
// The remote and ipv6 are the remote host this socket will connect to // The remote and ipv6 are the remote host this socket will connect to
override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean {
Log.v(TAG, "socket_protect: $socket, $remote, $ipv6") Log.d(TAG, "socket_protect: $socket, $remote, $ipv6")
return protect(socket) return protect(socket)
} }
@ -315,7 +313,7 @@ class OpenVpnClient(
// May be called asynchronously from a different thread // May be called asynchronously from a different thread
// when connect() is running. // when connect() is running.
override fun stop() { override fun stop() {
Log.v(TAG, "stop") Log.d(TAG, "stop")
super.stop() super.stop()
} }
@ -323,21 +321,21 @@ class OpenVpnClient(
// when network is down. May be called from a different thread // when network is down. May be called from a different thread
// when connect() is running. // when connect() is running.
override fun pause(reason: String) { override fun pause(reason: String) {
Log.v(TAG, "pause: $reason") Log.d(TAG, "pause: $reason")
super.pause(reason) super.pause(reason)
} }
// Resume the client after it has been paused. May be called from a // Resume the client after it has been paused. May be called from a
// different thread when connect() is running. // different thread when connect() is running.
override fun resume() { override fun resume() {
Log.v(TAG, "resume") Log.d(TAG, "resume")
super.resume() super.resume()
} }
// Do a disconnect/reconnect cycle n seconds from now. May be called // Do a disconnect/reconnect cycle n seconds from now. May be called
// from a different thread when connect() is running. // from a different thread when connect() is running.
override fun reconnect(seconds: Int) { override fun reconnect(seconds: Int) {
Log.v(TAG, "reconnect") Log.d(TAG, "reconnect: $seconds")
super.reconnect(seconds) super.reconnect(seconds)
} }
@ -346,14 +344,14 @@ class OpenVpnClient(
// CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE
// state. // state.
override fun pause_on_connection_timeout(): Boolean { override fun pause_on_connection_timeout(): Boolean {
Log.v(TAG, "pause_on_connection_timeout") Log.d(TAG, "pause_on_connection_timeout")
return false return false
} }
// Return information about the most recent connection. Should be called // Return information about the most recent connection. Should be called
// after an event of type "CONNECTED". // after an event of type "CONNECTED".
/* override fun connection_info(): ClientAPI_ConnectionInfo { /* override fun connection_info(): ClientAPI_ConnectionInfo {
Log.v(TAG, "connection_info") Log.d(TAG, "connection_info")
return super.connection_info() return super.connection_info()
} */ } */
@ -366,7 +364,7 @@ class OpenVpnClient(
override fun event(event: ClientAPI_Event) { override fun event(event: ClientAPI_Event) {
val name = event.name val name = event.name
val info = event.info val info = event.info
Log.v(TAG, "OpenVpn event: $name: $info") Log.d(TAG, "OpenVpn event: $name: $info")
when (name) { when (name) {
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
"CONNECTED" -> state.value = CONNECTED "CONNECTED" -> state.value = CONNECTED
@ -398,31 +396,31 @@ class OpenVpnClient(
// return transport stats only // return transport stats only
override fun transport_stats(): ClientAPI_TransportStats { override fun transport_stats(): ClientAPI_TransportStats {
Log.v(TAG, "transport_stats") Log.d(TAG, "transport_stats")
return super.transport_stats() return super.transport_stats()
} }
// return a stats value, index should be >= 0 and < stats_n() // return a stats value, index should be >= 0 and < stats_n()
/* override fun stats_value(index: Int): Long { /* override fun stats_value(index: Int): Long {
Log.v(TAG, "stats_value: $index") Log.d(TAG, "stats_value: $index")
return super.stats_value(index) return super.stats_value(index)
} */ } */
// return all stats in a bundle // return all stats in a bundle
/* override fun stats_bundle(): ClientAPI_LLVector { /* override fun stats_bundle(): ClientAPI_LLVector {
Log.v(TAG, "stats_bundle") Log.d(TAG, "stats_bundle")
return super.stats_bundle() return super.stats_bundle()
} */ } */
// return tun stats only // return tun stats only
/* override fun tun_stats(): ClientAPI_InterfaceStats { /* override fun tun_stats(): ClientAPI_InterfaceStats {
Log.v(TAG, "tun_stats") Log.d(TAG, "tun_stats")
return super.tun_stats() return super.tun_stats()
} */ } */
// post control channel message // post control channel message
/* override fun post_cc_msg(msg: String) { /* override fun post_cc_msg(msg: String) {
Log.v(TAG, "post_cc_msg: $msg") Log.d(TAG, "post_cc_msg: $msg")
super.post_cc_msg(msg) super.post_cc_msg(msg)
} */ } */
} }

View file

@ -11,7 +11,7 @@ class OpenVpnConfig private constructor(
class Builder : ProtocolConfig.Builder(false) { class Builder : ProtocolConfig.Builder(false) {
override var mtu: Int = OPENVPN_DEFAULT_MTU override var mtu: Int = OPENVPN_DEFAULT_MTU
override fun build(): OpenVpnConfig = OpenVpnConfig(this) override fun build(): OpenVpnConfig = configBuild().run { OpenVpnConfig(this@Builder) }
} }
companion object { companion object {

View file

@ -14,8 +14,6 @@ import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
import org.json.JSONObject import org.json.JSONObject
private const val TAG = "Protocol" private const val TAG = "Protocol"
@ -53,40 +51,16 @@ abstract class Protocol {
val splitTunnelType = config.optInt("splitTunnelType") val splitTunnelType = config.optInt("splitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelSites = config.getJSONArray("splitTunnelSites") val splitTunnelSites = config.getJSONArray("splitTunnelSites")
when (splitTunnelType) { val addressHandlerFunc = when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> { SPLIT_TUNNEL_INCLUDE -> ::includeAddress
// remove default routes, if any SPLIT_TUNNEL_EXCLUDE -> ::excludeAddress
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
// add routes from config
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
addRoute(address)
}
}
SPLIT_TUNNEL_EXCLUDE -> { else -> throw BadConfigException("Unexpected value of the 'splitTunnelType' parameter: $splitTunnelType")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { }
// exclude routes from config
for (i in 0 until splitTunnelSites.length()) { for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i)) val address = InetNetwork.parse(splitTunnelSites.getString(i))
excludeRoute(address) addressHandlerFunc(address)
}
} else {
// For older versions of Android, build a list of subnets without excluded addresses
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
ipRangeSet.remove(IpRange(address))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
} }
} }

View file

@ -5,6 +5,8 @@ import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import java.net.InetAddress import java.net.InetAddress
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
open class ProtocolConfig protected constructor( open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>, val addresses: Set<InetNetwork>,
@ -12,6 +14,8 @@ open class ProtocolConfig protected constructor(
val searchDomain: String?, val searchDomain: String?,
val routes: Set<InetNetwork>, val routes: Set<InetNetwork>,
val excludedRoutes: Set<InetNetwork>, val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val excludedApplications: Set<String>, val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?, val httpProxy: ProxyInfo?,
val allowAllAF: Boolean, val allowAllAF: Boolean,
@ -25,6 +29,8 @@ open class ProtocolConfig protected constructor(
builder.searchDomain, builder.searchDomain,
builder.routes, builder.routes,
builder.excludedRoutes, builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.excludedApplications, builder.excludedApplications,
builder.httpProxy, builder.httpProxy,
builder.allowAllAF, builder.allowAllAF,
@ -37,6 +43,8 @@ open class ProtocolConfig protected constructor(
internal val dnsServers: MutableSet<InetAddress> = hashSetOf() internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf() internal val routes: MutableSet<InetNetwork> = hashSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf() internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf() internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null internal var searchDomain: String? = null
@ -71,12 +79,15 @@ open class ProtocolConfig protected constructor(
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) } fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun clearRoutes() = apply { this.routes.clear() } fun clearRoutes() = apply { this.routes.clear() }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route } fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes } fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun excludeApplication(application: String) = apply { this.excludedApplications += application } fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications } fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@ -91,6 +102,48 @@ open class ProtocolConfig protected constructor(
fun setMtu(mtu: Int) = apply { this.mtu = mtu } fun setMtu(mtu: Int) = apply { this.mtu = mtu }
private fun processSplitTunneling() {
if (includedAddresses.isNotEmpty() && excludedAddresses.isNotEmpty()) {
throw BadConfigException("Config contains addresses for inclusive and exclusive split tunneling at the same time")
}
if (includedAddresses.isNotEmpty()) {
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, add the default route to the excluded routes
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses)
} else if (excludedAddresses.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// default routes are required for split tunneling in newer versions of Android
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
excludeRoutes(excludedAddresses)
}
}
private fun processExcludedRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, build a list of subnets without excluded routes
// and add them to routes
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
excludedRoutes.forEach {
ipRangeSet.remove(IpRange(it))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
private fun validate() { private fun validate() {
val errorMessage = StringBuilder() val errorMessage = StringBuilder()
@ -103,7 +156,13 @@ open class ProtocolConfig protected constructor(
if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString()) if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString())
} }
open fun build(): ProtocolConfig = validate().run { ProtocolConfig(this@Builder) } protected fun configBuild() {
processSplitTunneling()
processExcludedRoutes()
validate()
}
open fun build(): ProtocolConfig = configBuild().run { ProtocolConfig(this@Builder) }
} }
companion object { companion object {

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="." />
</full-backup-content>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="." />
</device-transfer>
</data-extraction-rules>

View file

@ -2,8 +2,10 @@ package org.amnezia.vpn
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
@ -12,11 +14,13 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -35,6 +39,7 @@ private const val TAG = "AmneziaActivity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2 private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@ -59,6 +64,7 @@ class AmneziaActivity : QtActivity() {
ServiceEvent.DISCONNECTED -> { ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected() QtAndroidController.onVpnDisconnected()
doUnbindService()
} }
ServiceEvent.RECONNECTING -> { ServiceEvent.RECONNECTING -> {
@ -139,7 +145,7 @@ class AmneziaActivity : QtActivity() {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Create Amnezia activity: $intent") Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger( vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService, onDeadObjectException = ::doUnbindService,
@ -150,7 +156,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent") Log.d(TAG, "onNewIntent: $intent")
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@ -170,7 +176,7 @@ class AmneziaActivity : QtActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Log.v(TAG, "Start Amnezia activity") Log.d(TAG, "Start Amnezia activity")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
doBindService() doBindService()
@ -178,13 +184,13 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
Log.v(TAG, "Stop Amnezia activity") Log.d(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
super.onStop() super.onStop()
} }
override fun onDestroy() { override fun onDestroy() {
Log.v(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
@ -201,10 +207,19 @@ class AmneziaActivity : QtActivity() {
} }
} }
OPEN_FILE_ACTION_CODE -> {
when (resultCode) {
RESULT_OK -> data?.data?.toString() ?: ""
else -> ""
}.let { uri ->
QtAndroidController.onFileOpened(uri)
}
}
CHECK_VPN_PERMISSION_ACTION_CODE -> { CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> {
Log.v(TAG, "Vpn permission granted") Log.d(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() } checkVpnPermissionCallbacks?.run { onSuccess() }
} }
@ -227,7 +242,7 @@ class AmneziaActivity : QtActivity() {
*/ */
@MainThread @MainThread
private fun doBindService() { private fun doBindService() {
Log.v(TAG, "Bind service") Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also { Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT) bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
} }
@ -238,7 +253,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun doUnbindService() { private fun doUnbindService() {
if (isInBoundState) { if (isInBoundState) {
Log.v(TAG, "Unbind service") Log.d(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
@ -273,7 +288,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
Log.v(TAG, "Check VPN permission") Log.d(TAG, "Check VPN permission")
VpnService.prepare(applicationContext)?.let { VpnService.prepare(applicationContext)?.let {
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
@ -294,7 +309,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun connectToVpn(vpnConfig: String) { private fun connectToVpn(vpnConfig: String) {
Log.v(TAG, "Connect to VPN") Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.CONNECT.packToMessage { Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig) putString(VPN_CONFIG, vpnConfig)
@ -303,7 +318,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun startVpnService(vpnConfig: String) { private fun startVpnService(vpnConfig: String) {
Log.v(TAG, "Start VPN service") Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig) putExtra(VPN_CONFIG, vpnConfig)
}.also { }.also {
@ -312,7 +327,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun disconnectFromVpn() { private fun disconnectFromVpn() {
Log.v(TAG, "Disconnect from VPN") Log.d(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT) vpnServiceMessenger.send(Action.DISCONNECT)
} }
@ -356,7 +371,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.v(TAG, "Save file $fileName") Log.d(TAG, "Save file $fileName")
mainScope.launch { mainScope.launch {
tmpFileContentToSave = data tmpFileContentToSave = data
@ -371,17 +386,43 @@ class AmneziaActivity : QtActivity() {
} }
@Suppress("unused") @Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) { fun openFile(filter: String?) {
Log.v(TAG, "Set notification text") Log.v(TAG, "Open file with filter: $filter")
Log.w(TAG, "Not yet implemented")
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
mime.getMimeTypeFromExtension(it.value.drop(2))
}.filterNotNull().toSet()
} else emptySet()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
}
}.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
}
} }
@Suppress("unused") @Suppress("unused")
fun cleanupLogs() { fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Cleanup logs") Log.v(TAG, "Set notification text")
Log.w(TAG, "Not yet implemented")
} }
@Suppress("unused")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@Suppress("unused") @Suppress("unused")
fun startQrCodeReader() { fun startQrCodeReader() {
Log.v(TAG, "Start camera") Log.v(TAG, "Start camera")
@ -389,4 +430,29 @@ class AmneziaActivity : QtActivity() {
startActivity(it) startActivity(it)
} }
} }
@Suppress("unused")
fun setSaveLogs(enabled: Boolean) {
Log.d(TAG, "Set save logs: $enabled")
mainScope.launch {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
}
}
}
}
@Suppress("unused")
fun exportLogsFile(fileName: String) {
Log.v(TAG, "Export logs file")
saveFile(fileName, Log.getLogs())
}
@Suppress("unused")
fun clearLogs() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
} }

View file

@ -5,14 +5,20 @@ import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig import androidx.camera.core.CameraXConfig
import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtApplication import org.qtproject.qt.android.bindings.QtApplication
private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider { class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Prefs.init(this)
Log.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel() createNotificationChannel()
} }

View file

@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState import org.amnezia.vpn.util.net.NetworkState
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -58,6 +59,8 @@ private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG" const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG" const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
@ -118,7 +121,7 @@ class AmneziaVpnService : VpnService() {
Action.CONNECT -> { Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG) val vpnConfig = msg.data.getString(VPN_CONFIG)
saveConfigToPrefs(vpnConfig) Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
@ -135,6 +138,10 @@ class AmneziaVpnService : VpnService() {
} }
} }
} }
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
}
} }
} }
} }
@ -179,7 +186,7 @@ class AmneziaVpnService : VpnService() {
*/ */
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.v(TAG, "Create Amnezia VPN service") Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client") clientMessenger = IpcMessenger(messengerName = "Client")
@ -193,15 +200,15 @@ class AmneziaVpnService : VpnService() {
else intent?.component?.packageName != packageName else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) { if (isAlwaysOnCompat) {
Log.v(TAG, "Start service via Always-on") Log.d(TAG, "Start service via Always-on")
connect(loadConfigFromPrefs()) connect(Prefs.load(PREFS_CONFIG_KEY))
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.v(TAG, "Start service after permission check") Log.d(TAG, "Start service after permission check")
connect(loadConfigFromPrefs()) connect(Prefs.load(PREFS_CONFIG_KEY))
} else { } else {
Log.v(TAG, "Start service") Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG) val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
saveConfigToPrefs(vpnConfig) Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
@ -237,7 +244,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onRevoke() { override fun onRevoke() {
Log.v(TAG, "onRevoke") Log.d(TAG, "onRevoke")
// Calls to onRevoke() method may not happen on the main thread of the process // Calls to onRevoke() method may not happen on the main thread of the process
mainScope.launch { mainScope.launch {
disconnect() disconnect()
@ -245,7 +252,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onDestroy() { override fun onDestroy() {
Log.v(TAG, "Destroy service") Log.d(TAG, "Destroy service")
runBlocking { runBlocking {
disconnect() disconnect()
disconnectionJob?.join() disconnectionJob?.join()
@ -256,7 +263,7 @@ class AmneziaVpnService : VpnService() {
} }
private fun stopService() { private fun stopService() {
Log.v(TAG, "Stop service") Log.d(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call // the coroutine below will be canceled during the onDestroy call
mainScope.launch { mainScope.launch {
delay(STOP_SERVICE_TIMEOUT) delay(STOP_SERVICE_TIMEOUT)
@ -272,7 +279,7 @@ class AmneziaVpnService : VpnService() {
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
protocolState.collect { protocolState -> protocolState.collect { protocolState ->
Log.d(TAG, "Protocol state: $protocolState") Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) { when (protocolState) {
CONNECTED -> { CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED) clientMessenger.send(ServiceEvent.CONNECTED)
@ -305,7 +312,7 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun launchSendingStatistics() { private fun launchSendingStatistics() {
if (isServiceBound && isConnected) { /* if (isServiceBound && isConnected) {
statisticsSendingJob = mainScope.launch { statisticsSendingJob = mainScope.launch {
while (true) { while (true) {
clientMessenger.send { clientMessenger.send {
@ -316,7 +323,7 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT) delay(STATISTICS_SENDING_TIMEOUT)
} }
} }
} } */
} }
@MainThread @MainThread
@ -328,7 +335,7 @@ class AmneziaVpnService : VpnService() {
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String?) {
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.v(TAG, "Start VPN connection") Log.d(TAG, "Start VPN connection")
protocolState.value = CONNECTING protocolState.value = CONNECTING
@ -357,7 +364,7 @@ class AmneziaVpnService : VpnService() {
private fun disconnect() { private fun disconnect() {
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.v(TAG, "Stop VPN connection") Log.d(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING protocolState.value = DISCONNECTING
@ -383,7 +390,7 @@ class AmneziaVpnService : VpnService() {
private fun reconnect() { private fun reconnect() {
if (!isConnected) return if (!isConnected) return
Log.v(TAG, "Reconnect VPN") Log.d(TAG, "Reconnect VPN")
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
@ -439,10 +446,4 @@ class AmneziaVpnService : VpnService() {
} else { } else {
true true
} }
private fun loadConfigFromPrefs(): String? =
Prefs.get(this).getString(PREFS_CONFIG_KEY, null)
private fun saveConfigToPrefs(config: String?) =
Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply()
} }

View file

@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Create Import Config Activity: $intent") Log.d(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent") Log.d(TAG, "onNewIntent: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
private fun readConfig(intent: Intent) { private fun readConfig(intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_SEND -> { ACTION_SEND -> {
Log.v(TAG, "Process SEND action, type: ${intent.type}") Log.d(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) { when (intent.type) {
"application/octet-stream" -> { "application/octet-stream" -> {
intent.getUriCompat()?.let { uri -> intent.getUriCompat()?.let { uri ->
@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
} }
ACTION_VIEW -> { ACTION_VIEW -> {
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) { when (intent.scheme) {
"file", "content" -> { "file", "content" -> {
intent.data?.let { uri -> intent.data?.let { uri ->
@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() {
private fun startMainActivity(config: String) { private fun startMainActivity(config: String) {
if (config.isNotBlank()) { if (config.isNotBlank()) {
Log.v(TAG, "startMainActivity") Log.d(TAG, "startMainActivity")
Intent(applicationContext, AmneziaActivity::class.java).apply { Intent(applicationContext, AmneziaActivity::class.java).apply {
action = ACTION_IMPORT_CONFIG action = ACTION_IMPORT_CONFIG
addCategory(CATEGORY_DEFAULT) addCategory(CATEGORY_DEFAULT)

View file

@ -32,7 +32,8 @@ enum class Action : IpcMessage {
REGISTER_CLIENT, REGISTER_CLIENT,
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS REQUEST_STATUS,
SET_SAVE_LOGS
} }
fun <T> T.packToMessage(): Message fun <T> T.packToMessage(): Message

View file

@ -1,25 +0,0 @@
package org.amnezia.vpn
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import org.amnezia.vpn.util.Log
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences =
try {
EncryptedSharedPreferences(
appContext,
SECURE_PREFS_FILE,
MasterKey(appContext)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback")
appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
}

View file

@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Start request activity") Log.d(TAG, "Start request activity")
val requestIntent = VpnService.prepare(applicationContext) val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) { if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {

View file

@ -15,6 +15,8 @@ object QtAndroidController {
external fun onVpnReconnecting() external fun onVpnReconnecting()
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String)
external fun onConfigImported(data: String) external fun onConfigImported(data: String)
external fun decodeQrCode(data: String): Boolean external fun decodeQrCode(data: String): Boolean

View file

@ -15,3 +15,7 @@ android {
buildConfig = true buildConfig = true
} }
} }
dependencies {
implementation(libs.androidx.security.crypto)
}

View file

@ -1,33 +1,252 @@
package org.amnezia.vpn.util package org.amnezia.vpn.util
import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
import org.amnezia.vpn.util.Log.Priority.F
import org.amnezia.vpn.util.Log.Priority.I
import org.amnezia.vpn.util.Log.Priority.V
import org.amnezia.vpn.util.Log.Priority.W
import android.util.Log as NativeLog import android.util.Log as NativeLog
class Log { private const val TAG = "Log"
companion object { private const val LOG_FILE_NAME = "amneziaVPN.log"
fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v) private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log"
private const val LOCK_FILE_NAME = ".lock"
private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS"
private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS"
private const val LOG_MAX_FILE_SIZE = 1024 * 1024
fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d) /**
* | Priority | Save to file | Logcat logging |
* |-------------------|--------------|----------------------------------------------|
* | Verbose | Don't save | Only in Debug build |
* | Debug | Save | In Debug build or if log saving is enabled |
* | Info, Warn, Error | Save | Enabled |
* | Fatal (Assert) | Save | Enabled. Depending on system configuration, |
* | | | create a report and/or terminate the process |
*/
object Log {
private val dateTimeFormat: Any =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
}
fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i) private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) }
fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w) private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel }
private val threadLock: ReentrantLock by lazy { ReentrantLock() }
fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e) @Volatile
private var _saveLogs: Boolean = false
var saveLogs: Boolean
get() = _saveLogs
set(value) {
if (_saveLogs != value) {
if (value && !logDir.exists() && !logDir.mkdir()) {
NativeLog.e(TAG, "Failed to create dir: $logDir")
return
}
_saveLogs = value
Prefs.save(PREFS_SAVE_LOGS_KEY, value)
}
}
fun v(tag: String, msg: Any?) = v(tag, msg.toString()) @JvmStatic
fun v(tag: String, msg: String) = log(tag, msg, V)
fun d(tag: String, msg: Any?) = d(tag, msg.toString()) @JvmStatic
fun d(tag: String, msg: String) = log(tag, msg, D)
fun i(tag: String, msg: Any?) = i(tag, msg.toString()) @JvmStatic
fun i(tag: String, msg: String) = log(tag, msg, I)
fun w(tag: String, msg: Any?) = w(tag, msg.toString()) @JvmStatic
fun w(tag: String, msg: String) = log(tag, msg, W)
fun e(tag: String, msg: Any?) = e(tag, msg.toString()) @JvmStatic
fun e(tag: String, msg: String) = log(tag, msg, E)
private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg) @JvmStatic
fun f(tag: String, msg: String) = log(tag, msg, F)
private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) { fun v(tag: String, msg: Any?) = v(tag, msg.toString())
if (BuildConfig.DEBUG) delegate(tag, msg)
fun d(tag: String, msg: Any?) = d(tag, msg.toString())
fun i(tag: String, msg: Any?) = i(tag, msg.toString())
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
fun f(tag: String, msg: Any?) = f(tag, msg.toString())
fun init(context: Context) {
v(TAG, "Init Log")
logDir = File(context.cacheDir, "logs")
saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY)
}
fun getLogs(): String =
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
fun clearLogs() {
withLock {
logFile.delete()
rotateLogFile.delete()
} }
} }
private fun log(tag: String, msg: String, priority: Priority) {
if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority))
if (priority == F) {
NativeLog.wtf(tag, msg)
} else if (
(priority != V && priority != D) ||
(priority == V && BuildConfig.DEBUG) ||
(priority == D && (BuildConfig.DEBUG || saveLogs))
) {
NativeLog.println(priority.level, tag, msg)
}
}
private fun saveLogMsg(msg: String) {
withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) {
logFile.renameTo(rotateLogFile)
}
try {
logFile.appendText(msg)
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to write log: $e")
}
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
private fun deviceInfo(): String {
val sb = StringBuilder()
sb.append("Model: ").appendLine(Build.MODEL)
sb.append("Brand: ").appendLine(Build.BRAND)
sb.append("Product: ").appendLine(Build.PRODUCT)
sb.append("Device: ").appendLine(Build.DEVICE)
sb.append("Codename: ").appendLine(Build.VERSION.CODENAME)
sb.append("Release: ").appendLine(Build.VERSION.RELEASE)
sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT)
sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString())
return sb.toString()
}
private fun readLogs(): String {
var logText = ""
withLock {
try {
if (rotateLogFile.exists()) logText = rotateLogFile.readText()
if (logFile.exists()) logText += logFile.readText()
} catch (e: IOException) {
val errorMsg = "Failed to read log: $e"
NativeLog.e(TAG, errorMsg)
logText += errorMsg
}
}
return logText
}
private fun getLogcat(): String {
try {
val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start()
return process.inputStream.reader().readText()
} catch (e: IOException) {
val errorMsg = "Failed to get logcat log: $e"
NativeLog.e(TAG, errorMsg)
return errorMsg
}
}
private fun withLock(block: () -> Unit) {
threadLock.lock()
try {
var l: FileLock? = null
try {
l = fileLock.lock()
block()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file lock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file lock: $e")
}
}
} finally {
threadLock.unlock()
}
}
private fun withTryLock(condition: () -> Boolean, block: () -> Unit) {
if (condition()) {
if (threadLock.tryLock()) {
try {
if (condition()) {
var l: FileLock? = null
try {
l = fileLock.tryLock()
if (l != null) {
if (condition()) {
block()
}
}
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file tryLock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file tryLock: $e")
}
}
}
} finally {
threadLock.unlock()
}
}
}
}
private enum class Priority(val level: Int) {
V(2),
D(3),
I(4),
W(5),
E(6),
F(7)
}
} }

View file

@ -0,0 +1,58 @@
package org.amnezia.vpn.util
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlin.reflect.typeOf
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
private lateinit var app: Application
val prefs: SharedPreferences
get() = try {
EncryptedSharedPreferences(
app,
SECURE_PREFS_FILE,
MasterKey(app)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback")
app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
fun init(app: Application) {
Log.v(TAG, "Init Prefs")
this.app = app
}
fun save(key: String, value: Boolean) =
prefs.edit().putBoolean(key, value).apply()
fun save(key: String, value: String?) =
prefs.edit().putString(key, value).apply()
fun save(key: String, value: Int) =
prefs.edit().putInt(key, value).apply()
fun save(key: String, value: Long) =
prefs.edit().putLong(key, value).apply()
fun save(key: String, value: Float) =
prefs.edit().putFloat(key, value).apply()
inline fun <reified T> load(key: String): T {
return when (typeOf<T>()) {
typeOf<Boolean>() -> prefs.getBoolean(key, false)
typeOf<String>() -> prefs.getString(key, "")
typeOf<Int>() -> prefs.getInt(key, 0)
typeOf<Long>() -> prefs.getLong(key, 0L)
typeOf<Float>() -> prefs.getFloat(key, 0f)
else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf<T>()}")
} as T
}
}

View file

@ -82,7 +82,7 @@ class NetworkState(
fun bindNetworkListener() { fun bindNetworkListener() {
if (isListenerBound) return if (isListenerBound) return
Log.v(TAG, "Bind network listener") Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -95,7 +95,7 @@ class NetworkState(
fun unbindNetworkListener() { fun unbindNetworkListener() {
if (!isListenerBound) return if (!isListenerBound) return
Log.v(TAG, "Unbind network listener") Log.d(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback) connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false isListenerBound = false
currentNetwork = null currentNetwork = null

View file

@ -99,7 +99,10 @@ open class Wireguard : Protocol() {
} }
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) { protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
configData["Address"]?.let { addAddress(InetNetwork.parse(it)) } configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
configData["DNS"]?.split(",")?.map { dns -> configData["DNS"]?.split(",")?.map { dns ->
parseInetAddress(dns.trim()) parseInetAddress(dns.trim())
}?.forEach(::addDnsServer) }?.forEach(::addDnsServer)

View file

@ -11,7 +11,7 @@ open class WireguardConfig protected constructor(
val endpoint: InetEndpoint, val endpoint: InetEndpoint,
val persistentKeepalive: Int, val persistentKeepalive: Int,
val publicKeyHex: String, val publicKeyHex: String,
val preSharedKeyHex: String, val preSharedKeyHex: String?,
val privateKeyHex: String val privateKeyHex: String
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
@ -43,7 +43,8 @@ open class WireguardConfig protected constructor(
appendLine("endpoint=$endpoint") appendLine("endpoint=$endpoint")
if (persistentKeepalive != 0) if (persistentKeepalive != 0)
appendLine("persistent_keepalive_interval=$persistentKeepalive") appendLine("persistent_keepalive_interval=$persistentKeepalive")
appendLine("preshared_key=$preSharedKeyHex") if (preSharedKeyHex != null)
appendLine("preshared_key=$preSharedKeyHex")
} }
open class Builder : ProtocolConfig.Builder(true) { open class Builder : ProtocolConfig.Builder(true) {
@ -56,7 +57,7 @@ open class WireguardConfig protected constructor(
internal lateinit var publicKeyHex: String internal lateinit var publicKeyHex: String
private set private set
internal lateinit var preSharedKeyHex: String internal var preSharedKeyHex: String? = null
private set private set
internal lateinit var privateKeyHex: String internal lateinit var privateKeyHex: String
@ -74,7 +75,7 @@ open class WireguardConfig protected constructor(
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex } fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
override fun build(): WireguardConfig = WireguardConfig(this) override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
} }
companion object { companion object {

View file

@ -90,7 +90,7 @@ include_directories(
${LIBSSH_ROOT_DIR}/include ${LIBSSH_ROOT_DIR}/include
${CLIENT_ROOT_DIR}/3rd/libssh/include ${CLIENT_ROOT_DIR}/3rd/libssh/include
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include ${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
) )

View file

@ -27,7 +27,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
) )
@ -35,7 +35,7 @@ set(HEADERS ${HEADERS}
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
) )

View file

@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
) )
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift

View file

@ -118,31 +118,33 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
{ {
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
QString config = json[config_key::config].toString(); QString config = json[config_key::config].toString();
QRegularExpression regex("redirect-gateway.*"); if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
config.replace(regex, ""); QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (m_settings->routeMode() == Settings::VpnAllSites) { if (m_settings->routeMode() == Settings::VpnAllSites) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
} }
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway // no redirect-gateway
} }
if (m_settings->routeMode() == Settings::VpnAllExceptSites) { if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif #endif
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
}
} }
#ifndef MZ_WINDOWS #ifndef MZ_WINDOWS

View file

@ -26,7 +26,7 @@ public:
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig); QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
QString processConfigWithExportSettings(QString jsonConfig); QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container, ErrorCode signCert(DockerContainer container,

View file

@ -92,7 +92,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
processConfigWithDnsSettings(serverIndex, container, proto, config); processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) { if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config); config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
} }
return config; return config;
} }

View file

@ -46,6 +46,7 @@ public:
signals: signals:
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container, void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials); ServerCredentials credentials);
void clientModelUpdated();
}; };
#endif // VPN_CONFIGURATOR_H #endif // VPN_CONFIGURATOR_H

View file

@ -168,7 +168,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
} else } else
return ErrorCode::NotImplementedError; return ErrorCode::NotImplementedError;
if (stdOut.contains("Error: No such container:")) { if (stdOut.contains("Error") && stdOut.contains("No such container")) {
return ErrorCode::ServerContainerMissingError; return ErrorCode::ServerContainerMissingError;
} }
@ -211,8 +211,14 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data); localFile.write(data);
localFile.close(); localFile.close();
#ifdef Q_OS_WINDOWS
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toLocal8Bit().toStdString(), remotePath.toStdString(),
"non_desc");
#else
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(),
"non_desc"); "non_desc");
#endif
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
return error; return error;
} }

View file

@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
target_sources(networkextension PRIVATE target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift

View file

@ -1,6 +1,6 @@
#include "wireguard-go-version.h" #include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h" #include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>

View file

@ -29,7 +29,7 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
} }
// Skip annoying messages from Qt // Skip annoying messages from Qt
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) { if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) {
return; return;
} }

View file

@ -1,8 +1,10 @@
#include <QCoreApplication>
#include <QJniEnvironment> #include <QJniEnvironment>
#include <QJsonDocument> #include <QJsonDocument>
#include <QQmlFile>
#include <QEventLoop>
#include "android_controller.h" #include "android_controller.h"
#include "android_utils.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
namespace namespace
@ -10,13 +12,15 @@ namespace
AndroidController *s_instance = nullptr; AndroidController *s_instance = nullptr;
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log";
constexpr auto TAG = "AmneziaQt";
} // namespace } // namespace
AndroidController::AndroidController() : QObject() AndroidController::AndroidController() : QObject()
{ {
connect(this, &AndroidController::status, this, connect(this, &AndroidController::status, this,
[this](AndroidController::ConnectionState state) { [this](AndroidController::ConnectionState state) {
qDebug() << "Android event: status; state:" << textConnectionState(state); qDebug() << "Android event: status =" << textConnectionState(state);
if (isWaitingStatus) { if (isWaitingStatus) {
qDebug() << "Initialization by service status"; qDebug() << "Initialization by service status";
isWaitingStatus = false; isWaitingStatus = false;
@ -106,6 +110,7 @@ bool AndroidController::initialize()
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)}, {"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)}, {"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)} {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
}; };
@ -123,24 +128,19 @@ bool AndroidController::initialize()
// static // static
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
auto AndroidController::callActivityMethod(const char *methodName, const char *signature, auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
const std::function<Ret()> &defValue, Args &&...args)
{ {
qDebug() << "Call activity method:" << methodName; qDebug() << "Call activity method:" << methodName;
QJniObject activity = QNativeInterface::QAndroidApplication::context(); QJniObject activity = AndroidUtils::getActivity();
if (activity.isValid()) { Q_ASSERT(activity.isValid());
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...); return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else {
qCritical() << "Activity is not valid";
return defValue();
}
} }
// static // static
template <typename ...Args> template <typename ...Args>
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{ {
callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...); callActivityMethod<void>(methodName, signature, std::forward<Args>(args)...);
} }
ErrorCode AndroidController::start(const QJsonObject &vpnConfig) ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
@ -165,6 +165,24 @@ void AndroidController::saveFile(const QString &fileName, const QString &data)
QJniObject::fromString(data).object<jstring>()); QJniObject::fromString(data).object<jstring>());
} }
QString AndroidController::openFile(const QString &filter)
{
QEventLoop wait;
QString fileName;
connect(this, &AndroidController::fileOpened, this,
[&fileName, &wait](const QString &uri) {
qDebug() << "Android event: file opened; uri:" << uri;
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
qDebug() << "Android opened filename:" << fileName;
wait.quit();
},
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
callActivityMethod("openFile", "(Ljava/lang/String;)V",
QJniObject::fromString(filter).object<jstring>());
wait.exec();
return fileName;
}
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
{ {
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V", callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
@ -173,11 +191,114 @@ void AndroidController::setNotificationText(const QString &title, const QString
(jint) timerSec); (jint) timerSec);
} }
bool AndroidController::isCameraPresent()
{
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
}
void AndroidController::startQrReaderActivity() void AndroidController::startQrReaderActivity()
{ {
callActivityMethod("startQrCodeReader", "()V"); callActivityMethod("startQrCodeReader", "()V");
} }
void AndroidController::setSaveLogs(bool enabled)
{
callActivityMethod("setSaveLogs", "(Z)V", enabled);
}
void AndroidController::exportLogsFile(const QString &fileName)
{
callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V",
QJniObject::fromString(fileName).object<jstring>());
}
void AndroidController::clearLogs()
{
callActivityMethod("clearLogs", "()V");
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
jmethodID AndroidController::logInfo;
jmethodID AndroidController::logWarning;
jmethodID AndroidController::logError;
jmethodID AndroidController::logFatal;
// static
bool AndroidController::initLogging()
{
QJniEnvironment env;
log = env.findClass(ANDROID_LOG_CLASS);
if (log == nullptr) {
qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found";
return false;
}
auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V";
logDebug = env.findStaticMethod(log, "d", logMethodSignature);
if (logDebug == nullptr) {
qCritical() << "Android debug log method not found";
return false;
}
logInfo = env.findStaticMethod(log, "i", logMethodSignature);
if (logInfo == nullptr) {
qCritical() << "Android info log method not found";
return false;
}
logWarning = env.findStaticMethod(log, "w", logMethodSignature);
if (logWarning == nullptr) {
qCritical() << "Android warning log method not found";
return false;
}
logError = env.findStaticMethod(log, "e", logMethodSignature);
if (logError == nullptr) {
qCritical() << "Android error log method not found";
return false;
}
logFatal = env.findStaticMethod(log, "f", logMethodSignature);
if (logFatal == nullptr) {
qCritical() << "Android fatal log method not found";
return false;
}
qInstallMessageHandler(messageHandler);
return true;
}
// static
void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
jmethodID logMethod = logDebug;
switch (type) {
case QtDebugMsg:
logMethod = logDebug;
break;
case QtInfoMsg:
logMethod = logInfo;
break;
case QtWarningMsg:
logMethod = logWarning;
break;
case QtCriticalMsg:
logMethod = logError;
break;
case QtFatalMsg:
logMethod = logFatal;
break;
}
QString formattedMessage = qFormatLogMessage(type, context, message);
QJniObject::callStaticMethod<void>(log, logMethod,
QJniObject::fromString(TAG).object<jstring>(),
QJniObject::fromString(formattedMessage).object<jstring>());
}
void AndroidController::qtAndroidControllerInitialized() void AndroidController::qtAndroidControllerInitialized()
{ {
callActivityMethod("qtAndroidControllerInitialized", "()V"); callActivityMethod("qtAndroidControllerInitialized", "()V");
@ -285,20 +406,19 @@ void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBy
} }
// static // static
void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) void AndroidController::onFileOpened(JNIEnv *env, jobject thiz, jstring uri)
{ {
Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
const char *buffer = env->GetStringUTFChars(data, nullptr); emit AndroidController::instance()->fileOpened(AndroidUtils::convertJString(env, uri));
if (!buffer) { }
return;
}
QString config(buffer); // static
env->ReleaseStringUTFChars(data, buffer); void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data)
{
Q_UNUSED(thiz);
emit AndroidController::instance()->configImported(config); emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data));
} }
// static // static
@ -306,12 +426,5 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
{ {
Q_UNUSED(thiz); Q_UNUSED(thiz);
const char *buffer = env->GetStringUTFChars(data, nullptr); return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
if (!buffer) {
return false;
}
QString code(buffer);
env->ReleaseStringUTFChars(data, buffer);
return ImportController::decodeQrCode(code);
} }

View file

@ -18,7 +18,8 @@ public:
bool initialize(); bool initialize();
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState // keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState { enum class ConnectionState
{
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
DISCONNECTED, DISCONNECTED,
@ -30,8 +31,16 @@ public:
ErrorCode start(const QJsonObject &vpnConfig); ErrorCode start(const QJsonObject &vpnConfig);
void stop(); void stop();
void setNotificationText(const QString &title, const QString &message, int timerSec); void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString& fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
bool isCameraPresent();
void startQrReaderActivity(); void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
signals: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
@ -43,6 +52,7 @@ signals:
void vpnDisconnected(); void vpnDisconnected();
void vpnReconnecting(); void vpnReconnecting();
void statisticsUpdated(quint64 rxBytes, quint64 txBytes); void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri);
void configImported(QString config); void configImported(QString config);
void importConfigFromOutside(QString config); void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state); void initConnectionState(Vpn::ConnectionState state);
@ -50,6 +60,13 @@ signals:
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
static jclass log;
static jmethodID logDebug;
static jmethodID logInfo;
static jmethodID logWarning;
static jmethodID logError;
static jmethodID logFatal;
void qtAndroidControllerInitialized(); void qtAndroidControllerInitialized();
static Vpn::ConnectionState convertState(ConnectionState state); static Vpn::ConnectionState convertState(ConnectionState state);
@ -65,11 +82,11 @@ private:
static void onVpnReconnecting(JNIEnv *env, jobject thiz); static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
const std::function<Ret()> &defValue, Args &&...args);
template <typename ...Args> template <typename ...Args>
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
}; };

View file

@ -0,0 +1,30 @@
#include <QCoreApplication>
#include "android_utils.h"
namespace AndroidUtils
{
QJniObject getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
QString convertJString(JNIEnv *env, jstring data)
{
int len = env->GetStringLength(data);
QString res(len, Qt::Uninitialized);
env->GetStringRegion(data, 0, len, reinterpret_cast<jchar *>(res.data()));
return res;
}
void runOnAndroidThreadSync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void runOnAndroidThreadAsync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
}

View file

@ -0,0 +1,16 @@
#ifndef ANDROID_UTILS_H
#define ANDROID_UTILS_H
#include <QJniObject>
namespace AndroidUtils
{
QJniObject getActivity();
QString convertJString(JNIEnv *env, jstring data);
void runOnAndroidThreadSync(const std::function<void()> &runnable);
void runOnAndroidThreadAsync(const std::function<void()> &runnable);
};
#endif // ANDROID_UTILS_H

View file

@ -1,183 +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/. */
#include "androidutils.h"
#include <QGuiApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkCookieJar>
#include <QTimer>
#include <QUrlQuery>
#include "jni.h"
namespace
{
AndroidUtils *s_instance = nullptr;
} // namespace
// static
QString AndroidUtils::GetDeviceName()
{
QJniEnvironment env;
jclass BUILD = env->FindClass("android/os/Build");
jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(BUILD, model);
if (!value) {
return QString("Android Device");
}
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
return QString("Android Device");
}
QString res(buffer);
env->ReleaseStringUTFChars(value, buffer);
return res;
};
// static
AndroidUtils *AndroidUtils::instance()
{
if (!s_instance) {
Q_ASSERT(qApp);
s_instance = new AndroidUtils(qApp);
}
return s_instance;
}
AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent)
{
Q_ASSERT(!s_instance);
s_instance = this;
}
AndroidUtils::~AndroidUtils()
{
Q_ASSERT(s_instance == this);
s_instance = nullptr;
}
// static
void AndroidUtils::dispatchToMainThread(std::function<void()> callback)
{
QTimer *timer = new QTimer();
timer->moveToThread(qApp->thread());
timer->setSingleShot(true);
QObject::connect(timer, &QTimer::timeout, [=]() {
callback();
timer->deleteLater();
});
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
}
// static
QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQByteArrayFromJString - failed to parse data.";
return QByteArray();
}
QByteArray out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQStringFromJString - failed to parse data.";
return QString();
}
QString out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data)
{
QByteArray raw(getQByteArrayFromJString(env, data));
QJsonParseError jsonError;
QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError);
if (QJsonParseError::NoError != jsonError.error) {
qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error
<< "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw;
return QJsonObject();
}
if (!json.isObject()) {
qDebug() << "getQJsonObjectFromJString - object expected.";
return QJsonObject();
}
return json.object();
}
QJniObject AndroidUtils::getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
int AndroidUtils::GetSDKVersion()
{
QJniEnvironment env;
jclass versionClass = env->FindClass("android/os/Build$VERSION");
jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID);
return sdk;
}
QString AndroidUtils::GetManufacturer()
{
QJniEnvironment env;
jclass buildClass = env->FindClass("android/os/Build");
jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField);
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
qDebug() << "Failed to fetch MANUFACTURER";
return QByteArray();
}
QString res(buffer);
qDebug() << "MANUFACTURER: " << res;
env->ReleaseStringUTFChars(value, buffer);
return res;
}
void AndroidUtils::runOnAndroidThreadSync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void AndroidUtils::runOnAndroidThreadAsync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
// Static
// Creates a copy of the passed QByteArray in the JVM and passes back a ref
jbyteArray AndroidUtils::tojByteArray(const QByteArray &data)
{
QJniEnvironment env;
jbyteArray out = env->NewByteArray(data.size());
env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast<const jbyte *>(data.constData()));
return out;
}

View file

@ -1,49 +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/. */
#ifndef ANDROIDUTILS_H
#define ANDROIDUTILS_H
#include <jni.h>
#include <QJniEnvironment>
#include <QJniObject>
#include <QObject>
#include <QString>
#include <QUrl>
class AndroidUtils final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(AndroidUtils)
public:
static QString GetDeviceName();
static int GetSDKVersion();
static QString GetManufacturer();
static AndroidUtils* instance();
static void dispatchToMainThread(std::function<void()> callback);
static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data);
static jbyteArray tojByteArray(const QByteArray& data);
static QString getQStringFromJString(JNIEnv* env, jstring data);
static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data);
static QJniObject getActivity();
static void runOnAndroidThreadSync(const std::function<void()> runnable);
static void runOnAndroidThreadAsync(const std::function<void()> runnable);
private:
AndroidUtils(QObject* parent);
~AndroidUtils();
};
#endif // ANDROIDUTILS_H

View file

@ -43,6 +43,7 @@ bool MobileUtils::shareText(const QStringList& filesToSend) {
UIPopoverPresentationController *popController = activityController.popoverPresentationController; UIPopoverPresentationController *popController = activityController.popoverPresentationController;
if (popController) { if (popController) {
popController.sourceView = qtController.view; popController.sourceView = qtController.view;
popController.sourceRect = CGRectMake(100, 100, 100, 100);
} }
QEventLoop wait; QEventLoop wait;

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>

View file

@ -249,8 +249,107 @@ void IosController::vpnStatusDidChange(void *pNotification)
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification; NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
if (session /* && session == TunnelManager.session */ ) { if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session; qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
emit connectionStateChanged(iosStatusToState(session.status));
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time.";
break;
case NEVPNConnectionErrorNoNetworkAvailable:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network.";
break;
case NEVPNConnectionErrorUnrecoverableNetworkChange:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained.";
break;
case NEVPNConnectionErrorConfigurationFailed:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. ";
break;
case NEVPNConnectionErrorServerAddressResolutionFailed:
qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined.";
break;
case NEVPNConnectionErrorServerNotResponding:
qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed.";
break;
case NEVPNConnectionErrorServerDead:
qDebug() << "Disconnect error info" << "The VPN server is no longer functioning.";
break;
case NEVPNConnectionErrorAuthenticationFailed:
qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server.";
break;
case NEVPNConnectionErrorClientCertificateInvalid:
qDebug() << "Disconnect error info" << "The client certificate is invalid.";
break;
case NEVPNConnectionErrorClientCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorClientCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed.";
break;
case NEVPNConnectionErrorPluginFailed:
qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly.";
break;
case NEVPNConnectionErrorConfigurationNotFound:
qDebug() << "Disconnect error info" << "The VPN configuration could not be found.";
break;
case NEVPNConnectionErrorPluginDisabled:
qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated.";
break;
case NEVPNConnectionErrorNegotiationFailed:
qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed.";
break;
case NEVPNConnectionErrorServerDisconnected:
qDebug() << "Disconnect error info" << "The VPN server terminated the connection.";
break;
case NEVPNConnectionErrorServerCertificateInvalid:
qDebug() << "Disconnect error info" << "The server certificate is invalid.";
break;
case NEVPNConnectionErrorServerCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorServerCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed.";
break;
default:
qDebug() << "Disconnect error info" << "Unknown code.";
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
qDebug() << "Disconnect underlying error" << "General. Use sysdiagnose.";
break;
case 2:
qDebug() << "Disconnect underlying error" << "Plug-in unavailable. Use sysdiagnose.";
break;
default:
qDebug() << "Disconnect underlying error" << "Unknown code. Use sysdiagnose.";
break;
}
}
}
} else {
qDebug() << "Disconnect error is absent";
}
}];
} else {
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
} }
} }
@ -352,6 +451,15 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel() void IosController::startTunnel()
{ {
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0; m_rxBytes = 0;
m_txBytes = 0; m_txBytes = 0;
@ -373,7 +481,7 @@ void IosController::startTunnel()
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) { if (loadError) {
qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String; qDebug().nospace() << "IosController::start" << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
return; return;
} }
@ -401,11 +509,11 @@ void IosController::startTunnel()
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) { if (!started || startError) {
qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error" qDebug().nospace() << "IosController::start" << protocolName << " : Connect " << protocolName << " Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : ""); << (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
} else { } else {
qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded"; qDebug().nospace() << "IosController::start" << protocolName << " : Starting the tunnel succeeded";
} }
}]; }];
}); });

View file

@ -14,12 +14,14 @@ constexpr const char *keyChainName = "AmneziaVPN-Keychain";
class SecureQSettings : public QObject class SecureQSettings : public QObject
{ {
Q_OBJECT
public: public:
explicit SecureQSettings(const QString &organization, const QString &application = QString(), explicit SecureQSettings(const QString &organization, const QString &application = QString(),
QObject *parent = nullptr); QObject *parent = nullptr);
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value); Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
void remove(const QString &key); void remove(const QString &key);
void sync(); void sync();

View file

@ -1,5 +1,6 @@
if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\ if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\ elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\
elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\ elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\ else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi

View file

@ -1,6 +1,7 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\ else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\

View file

@ -1,4 +1,8 @@
#include "settings.h" #include "settings.h"
#include "QThread"
#include "QCoreApplication"
#include "utilities.h" #include "utilities.h"
#include "version.h" #include "version.h"
@ -12,10 +16,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
{ {
// Import old settings // Import old settings
if (serversCount() == 0) { if (serversCount() == 0) {
QString user = m_settings.value("Server/userName").toString(); QString user = value("Server/userName").toString();
QString password = m_settings.value("Server/password").toString(); QString password = value("Server/password").toString();
QString serverName = m_settings.value("Server/serverName").toString(); QString serverName = value("Server/serverName").toString();
int port = m_settings.value("Server/serverPort").toInt(); int port = value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
QJsonObject server; QJsonObject server;
@ -211,7 +215,8 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled) void Settings::setSaveLogs(bool enabled)
{ {
m_settings.setValue("Conf/saveLogs", enabled); setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) { if (!isSaveLogs()) {
Logger::deInit(); Logger::deInit();
} else { } else {
@ -219,7 +224,8 @@ void Settings::setSaveLogs(bool enabled)
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
emit saveLogsChanged(); #endif
emit saveLogsChanged(enabled);
} }
QString Settings::routeModeString(RouteMode mode) const QString Settings::routeModeString(RouteMode mode) const
@ -233,7 +239,7 @@ QString Settings::routeModeString(RouteMode mode) const
Settings::RouteMode Settings::routeMode() const Settings::RouteMode Settings::routeMode() const
{ {
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt()); return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
} }
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
@ -321,12 +327,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
QString Settings::primaryDns() const QString Settings::primaryDns() const
{ {
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); return value("Conf/primaryDns", cloudFlareNs1).toString();
} }
QString Settings::secondaryDns() const QString Settings::secondaryDns() const
{ {
return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); return value("Conf/secondaryDns", cloudFlareNs2).toString();
} }
void Settings::clearSettings() void Settings::clearSettings()
@ -351,3 +357,30 @@ ServerCredentials Settings::serverCredentials(int index) const
return credentials; return credentials;
} }
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
QVariant returnValue;
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
returnValue = m_settings.value(key, defaultValue);
} else {
QMetaObject::invokeMethod(&m_settings, "value",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, returnValue),
Q_ARG(const QString&, key),
Q_ARG(const QVariant&, defaultValue));
}
return returnValue;
}
void Settings::setValue(const QString &key, const QVariant &value)
{
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
m_settings.setValue(key, value);
} else {
QMetaObject::invokeMethod(&m_settings, "setValue",
Qt::BlockingQueuedConnection,
Q_ARG(const QString&, key),
Q_ARG(const QVariant&, value));
}
}

View file

@ -29,11 +29,11 @@ public:
QJsonArray serversArray() const QJsonArray serversArray() const
{ {
return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
} }
void setServersArray(const QJsonArray &servers) void setServersArray(const QJsonArray &servers)
{ {
m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); setValue("Servers/serversList", QJsonDocument(servers).toJson());
} }
// Servers section // Servers section
@ -45,11 +45,11 @@ public:
int defaultServerIndex() const int defaultServerIndex() const
{ {
return m_settings.value("Servers/defaultServerIndex", 0).toInt(); return value("Servers/defaultServerIndex", 0).toInt();
} }
void setDefaultServer(int index) void setDefaultServer(int index)
{ {
m_settings.setValue("Servers/defaultServerIndex", index); setValue("Servers/defaultServerIndex", index);
} }
QJsonObject defaultServer() const QJsonObject defaultServer() const
{ {
@ -78,25 +78,25 @@ public:
// App settings section // App settings section
bool isAutoConnect() const bool isAutoConnect() const
{ {
return m_settings.value("Conf/autoConnect", false).toBool(); return value("Conf/autoConnect", false).toBool();
} }
void setAutoConnect(bool enabled) void setAutoConnect(bool enabled)
{ {
m_settings.setValue("Conf/autoConnect", enabled); setValue("Conf/autoConnect", enabled);
} }
bool isStartMinimized() const bool isStartMinimized() const
{ {
return m_settings.value("Conf/startMinimized", false).toBool(); return value("Conf/startMinimized", false).toBool();
} }
void setStartMinimized(bool enabled) void setStartMinimized(bool enabled)
{ {
m_settings.setValue("Conf/startMinimized", enabled); setValue("Conf/startMinimized", enabled);
} }
bool isSaveLogs() const bool isSaveLogs() const
{ {
return m_settings.value("Conf/saveLogs", false).toBool(); return value("Conf/saveLogs", false).toBool();
} }
void setSaveLogs(bool enabled); void setSaveLogs(bool enabled);
@ -110,15 +110,15 @@ public:
QString routeModeString(RouteMode mode) const; QString routeModeString(RouteMode mode) const;
RouteMode routeMode() const; RouteMode routeMode() const;
void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); } void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
QVariantMap vpnSites(RouteMode mode) const QVariantMap vpnSites(RouteMode mode) const
{ {
return m_settings.value("Conf/" + routeModeString(mode)).toMap(); return value("Conf/" + routeModeString(mode)).toMap();
} }
void setVpnSites(RouteMode mode, const QVariantMap &sites) void setVpnSites(RouteMode mode, const QVariantMap &sites)
{ {
m_settings.setValue("Conf/" + routeModeString(mode), sites); setValue("Conf/" + routeModeString(mode), sites);
m_settings.sync(); m_settings.sync();
} }
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
@ -132,11 +132,11 @@ public:
bool useAmneziaDns() const bool useAmneziaDns() const
{ {
return m_settings.value("Conf/useAmneziaDns", true).toBool(); return value("Conf/useAmneziaDns", true).toBool();
} }
void setUseAmneziaDns(bool enabled) void setUseAmneziaDns(bool enabled)
{ {
m_settings.setValue("Conf/useAmneziaDns", enabled); setValue("Conf/useAmneziaDns", enabled);
} }
QString primaryDns() const; QString primaryDns() const;
@ -145,13 +145,13 @@ public:
// QString primaryDns() const { return m_primaryDns; } // QString primaryDns() const { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns) void setPrimaryDns(const QString &primaryDns)
{ {
m_settings.setValue("Conf/primaryDns", primaryDns); setValue("Conf/primaryDns", primaryDns);
} }
// QString secondaryDns() const { return m_secondaryDns; } // QString secondaryDns() const { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns) void setSecondaryDns(const QString &secondaryDns)
{ {
m_settings.setValue("Conf/secondaryDns", secondaryDns); setValue("Conf/secondaryDns", secondaryDns);
} }
static const char cloudFlareNs1[]; static const char cloudFlareNs1[];
@ -171,29 +171,32 @@ public:
QLocale getAppLanguage() QLocale getAppLanguage()
{ {
return m_settings.value("Conf/appLanguage", QLocale()).toLocale(); return value("Conf/appLanguage", QLocale()).toLocale();
}; };
void setAppLanguage(QLocale locale) void setAppLanguage(QLocale locale)
{ {
m_settings.setValue("Conf/appLanguage", locale); setValue("Conf/appLanguage", locale);
}; };
bool isScreenshotsEnabled() const bool isScreenshotsEnabled() const
{ {
return m_settings.value("Conf/screenshotsEnabled", false).toBool(); return value("Conf/screenshotsEnabled", false).toBool();
} }
void setScreenshotsEnabled(bool enabled) void setScreenshotsEnabled(bool enabled)
{ {
m_settings.setValue("Conf/screenshotsEnabled", enabled); setValue("Conf/screenshotsEnabled", enabled);
} }
void clearSettings(); void clearSettings();
signals: signals:
void saveLogsChanged(); void saveLogsChanged(bool enabled);
private: private:
SecureQSettings m_settings; QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
mutable SecureQSettings m_settings;
}; };
#endif // SETTINGS_H #endif // SETTINGS_H

File diff suppressed because it is too large Load diff

View file

@ -15,15 +15,13 @@
<context> <context>
<name>AndroidController</name> <name>AndroidController</name>
<message> <message>
<location filename="../platforms/android/android_controller.cpp" line="236"/>
<source>AmneziaVPN</source> <source>AmneziaVPN</source>
<translation>AmneziaVPN</translation> <translation type="vanished">AmneziaVPN</translation>
</message> </message>
<message> <message>
<location filename="../platforms/android/android_controller.cpp" line="239"/>
<source>VPN Connected</source> <source>VPN Connected</source>
<extracomment>Refers to the app - which is currently running the background and waiting</extracomment> <extracomment>Refers to the app - which is currently running the background and waiting</extracomment>
<translation>VPN Подключен</translation> <translation type="vanished">VPN Подключен</translation>
</message> </message>
</context> </context>
<context> <context>
@ -92,7 +90,7 @@
<message> <message>
<location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="38"/> <location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="38"/>
<source>Configure your server</source> <source>Configure your server</source>
<translation>Настроить ваш сервер</translation> <translation>Настроить свой сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="52"/> <location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="52"/>
@ -151,7 +149,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="435"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>Отсканировано %1 из%2.</translation> <translation>Отсканировано %1 из%2.</translation>
</message> </message>
@ -174,7 +172,7 @@
<location filename="../ui/controllers/installController.cpp" line="149"/> <location filename="../ui/controllers/installController.cpp" line="149"/>
<source> <source>
Added containers that were already installed on the server</source> Added containers that were already installed on the server</source>
<translation type="unfinished"></translation> <translation type="unfinished">Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="213"/> <location filename="../ui/controllers/installController.cpp" line="213"/>
@ -196,7 +194,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="315"/> <location filename="../ui/controllers/installController.cpp" line="315"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation>Все протоклы и сервисы были удалены с сервера &apos;%1&apos;</translation> <translation>Все протоколы и сервисы были удалены с сервера &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="332"/> <location filename="../ui/controllers/installController.cpp" line="332"/>
@ -418,7 +416,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="77"/> <location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="77"/>
<source>OpenVPN settings</source> <source>OpenVPN settings</source>
<translation>Настройки OpenVPN</translation> <translation>OpenVPN настройки</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/> <location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/>
@ -946,6 +944,11 @@ Already installed containers were found on the server. All installed containers
<source>Show other methods on Github</source> <source>Show other methods on Github</source>
<translation>Показать другие способы на Github</translation> <translation>Показать другие способы на Github</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="104"/>
<source>https://github.com/amnezia-vpn/amnezia-client#donate</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/>
<source>Contacts</source> <source>Contacts</source>
@ -1012,17 +1015,17 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="52"/> <location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="52"/>
<source>Allow application screenshots</source> <source>Allow application screenshots</source>
<translation type="unfinished"></translation> <translation type="unfinished">Разрешить скриншоты приложения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="72"/> <location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="72"/>
<source>Auto start</source> <source>Auto start</source>
<translation type="unfinished"></translation> <translation type="unfinished">Автозапуск</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="73"/> <location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="73"/>
<source>Launch the application every time the device is starts</source> <source>Launch the application every time the device is starts</source>
<translation type="unfinished"></translation> <translation type="unfinished">Открывать приложение при запуске устройства</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="93"/> <location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="93"/>
@ -1189,7 +1192,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="86"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="86"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>Эти серверы будут использоваться, если не включен AmneziaDNS</translation> <translation>Эти адреса будут использоваться, если не включен AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="101"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="101"/>
@ -1353,7 +1356,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>Удалить кэш Amnezia с сервера?</translation> <translation>Удалить кэш Amnezia?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="25"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="25"/>
@ -1428,22 +1431,22 @@ Already installed containers were found on the server. All installed containers
<translation>Имя сервера</translation> <translation>Имя сервера</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="111"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="142"/>
<source>Protocols</source> <source>Protocols</source>
<translation>Протоколы</translation> <translation>Протоколы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="143"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="148"/>
<source>Services</source> <source>Services</source>
<translation>Сервисы</translation> <translation>Сервисы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="152"/>
<source>Data</source> <source>Data</source>
<translation>Данные</translation> <translation>Данные</translation>
</message> </message>
@ -1680,7 +1683,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="120"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="120"/>
<source>All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties</source> <source>All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished"></translation> <translation type="unfinished">Все введенные вами данные останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или третьим лицам</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="129"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="129"/>
@ -1703,7 +1706,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="67"/>
<source>What is the level of internet control in your region?</source> <source>What is the level of internet control in your region?</source>
<translation>Какой уровень контроля интеренета в вашем регионе?</translation> <translation>Какой уровень контроля интернета в вашем регионе?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/>
@ -1854,6 +1857,11 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<source>I have nothing</source> <source>I have nothing</source>
<translation>У меня ничего нет</translation> <translation>У меня ничего нет</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="138"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PageSetupWizardTextKey</name> <name>PageSetupWizardTextKey</name>
@ -1941,8 +1949,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки.</translation> <translation type="vanished">Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="279"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="280"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="280"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="281"/>
<source>Server</source> <source>Server</source>
<translation>Сервер</translation> <translation>Сервер</translation>
</message> </message>
@ -1978,12 +1986,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="68"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="68"/>
<source>Save ShadowSocks config</source> <source>Save ShadowSocks config</source>
<translation type="unfinished"></translation> <translation type="unfinished">Сохранить конфигурацию ShadowSocks</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="75"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="75"/>
<source>Save Cloak config</source> <source>Save Cloak config</source>
<translation type="unfinished"></translation> <translation type="unfinished">Сохранить конфигурацию Cloak</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="105"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="105"/>
@ -1993,12 +2001,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="120"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="120"/>
<source>ShadowSocks native format</source> <source>ShadowSocks native format</source>
<translation type="unfinished"></translation> <translation type="unfinished">ShadowSocks нативный формат</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="125"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="125"/>
<source>Cloak native format</source> <source>Cloak native format</source>
<translation type="unfinished"></translation> <translation type="unfinished">Cloak нативный формат</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="150"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="150"/>
@ -2008,66 +2016,71 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="178"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="178"/>
<source>Share full access to the server and VPN</source> <source>Share full access to the server and VPN</source>
<translation type="unfinished"></translation> <translation type="unfinished">Поделиться полным доступом к серверу</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="179"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="179"/>
<source>Use for your own devices, or share with those you trust to manage the server.</source> <source>Use for your own devices, or share with those you trust to manage the server.</source>
<translation type="unfinished"></translation> <translation type="unfinished">Используйте для собственных устройств или передайте управление сервером тем, кому вы доверяете.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="231"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="231"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="483"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="486"/>
<source>Users</source> <source>Users</source>
<translation type="unfinished"></translation> <translation type="unfinished">Пользователи</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="262"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="262"/>
<source>User name</source> <source>User name</source>
<translation type="unfinished"></translation> <translation type="unfinished">Имя пользователя</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="499"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="502"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="595"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="584"/>
<source>Rename</source> <source>Creation date: </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="624"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="598"/>
<source>Rename</source>
<translation type="unfinished">Переименовать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Client name</source> <source>Client name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="632"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="636"/>
<source>Save</source> <source>Save</source>
<translation type="unfinished">Сохранить</translation> <translation type="unfinished">Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="660"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="668"/>
<source>Revoke</source> <source>Revoke</source>
<translation type="unfinished"></translation> <translation type="unfinished">Отозвать</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="671"/>
<source>Revoke the config for a user - </source> <source>Revoke the config for a user - %1?</source>
<translation type="unfinished"></translation> <translation type="unfinished">Отозвать доступ для пользователя - %1?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="664"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished"></translation> <translation type="unfinished">Пользователь больше не сможет подключаться к вашему серверу</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="665"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="673"/>
<source>Continue</source> <source>Continue</source>
<translation type="unfinished">Продолжить</translation> <translation type="unfinished">Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="666"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="674"/>
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Отменить</translation> <translation type="unfinished">Отменить</translation>
</message> </message>
@ -2081,20 +2094,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Поделиться доступом к VPN, без возможности управления сервером</translation> <translation>Поделиться доступом к VPN, без возможности управления сервером</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="331"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="332"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="332"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="333"/>
<source>Protocol</source> <source>Protocol</source>
<translation>Протокол</translation> <translation>Протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="428"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="429"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="429"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="430"/>
<source>Connection format</source> <source>Connection format</source>
<translation>Формат подключения</translation> <translation>Формат подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="186"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="186"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="468"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="469"/>
<source>Share</source> <source>Share</source>
<translation>Поделиться</translation> <translation>Поделиться</translation>
</message> </message>
@ -2104,32 +2117,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="49"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="49"/>
<source>Full access to the server and VPN</source> <source>Full access to the server and VPN</source>
<translation type="unfinished"></translation> <translation type="unfinished">Полный доступ к серверу и VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="57"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="57"/>
<source>We recommend that you use full access to the server only for your own additional devices. <source>We recommend that you use full access to the server only for your own additional devices.
</source> </source>
<translation type="unfinished"></translation> <translation type="unfinished">Мы рекомендуем использовать полный доступ к серверу только для собственных устройств.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="58"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="58"/>
<source>If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. </source> <source>If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. </source>
<translation type="unfinished"></translation> <translation type="unfinished">Если вы поделитесь полным доступом с другими людьми, то они смогут удалять и добавлять протоколы и сервисы на сервер, что приведет к некорректной работе VPN для всех пользователей.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="73"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="73"/>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="74"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="74"/>
<source>Server</source> <source>Server</source>
<translation type="unfinished"></translation> <translation type="unfinished">Сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="102"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="100"/>
<source>Accessing </source> <source>Accessing </source>
<translation type="unfinished">Доступ </translation> <translation type="unfinished">Доступ </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="103"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="101"/>
<source>File with accessing settings to </source> <source>File with accessing settings to </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2160,38 +2173,38 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::DeletePasswordJobPrivate</name> <name>QKeychain::DeletePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="104"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="104"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation>Password entry not found</translation> <translation>Password entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="108"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation>Could not decrypt data</translation> <translation>Could not decrypt data</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="552"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="585"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="560"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="593"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="578"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="614"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="177"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="177"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="173"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="173"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="179"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="179"/>
<source>Could not remove private key from keystore</source> <source>Could not remove private key from keystore</source>
<translation>Could not remove private key from keystore</translation> <translation>Could not remove private key from keystore</translation>
</message> </message>
@ -2199,12 +2212,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::JobPrivate</name> <name>QKeychain::JobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="265"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="295"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="509"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="542"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
@ -2212,27 +2225,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::PlainTextStore</name> <name>QKeychain::PlainTextStore</name>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="65"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source> <source>Could not store data in settings: access error</source>
<translation>Could not store data in settings: access error</translation> <translation>Could not store data in settings: access error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="67"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source> <source>Could not store data in settings: format error</source>
<translation>Could not store data in settings: format error</translation> <translation>Could not store data in settings: format error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="85"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source> <source>Could not delete data from settings: access error</source>
<translation>Could not delete data from settings: access error</translation> <translation>Could not delete data from settings: access error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="87"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source> <source>Could not delete data from settings: format error</source>
<translation>Could not delete data from settings: format error</translation> <translation>Could not delete data from settings: format error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="104"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="104"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
@ -2240,80 +2253,80 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::ReadPasswordJobPrivate</name> <name>QKeychain::ReadPasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="32"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="32"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation>Password entry not found</translation> <translation>Password entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="36"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="36"/>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="139"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation>Could not decrypt data</translation> <translation>Could not decrypt data</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="178"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="205"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus is not running</translation> <translation>D-Bus is not running</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="187"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="214"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="197"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="224"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="286"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="316"/>
<source>No keychain service available</source> <source>No keychain service available</source>
<translation>No keychain service available</translation> <translation>No keychain service available</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="288"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="318"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="333"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="363"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="354"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="384"/>
<source>Could not determine data type: %1; %2</source> <source>Could not determine data type: %1; %2</source>
<translation>Could not determine data type: %1; %2</translation> <translation>Could not determine data type: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="363"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="393"/>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="52"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="52"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="372"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="402"/>
<source>Unsupported entry type &apos;Map&apos;</source> <source>Unsupported entry type &apos;Map&apos;</source>
<translation>Unsupported entry type &apos;Map&apos;</translation> <translation>Unsupported entry type &apos;Map&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="375"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="405"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source> <source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Unknown kwallet entry type &apos;%1&apos;</translation> <translation>Unknown kwallet entry type &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="96"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="96"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="60"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="60"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="68"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="68"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation>Could not retrieve private key from keystore</translation> <translation>Could not retrieve private key from keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="75"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="75"/>
<source>Could not create decryption cipher</source> <source>Could not create decryption cipher</source>
<translation>Could not create decryption cipher</translation> <translation>Could not create decryption cipher</translation>
</message> </message>
@ -2321,73 +2334,73 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::WritePasswordJobPrivate</name> <name>QKeychain::WritePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="78"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source> <source>Credential size exceeds maximum size of %1</source>
<translation>Credential size exceeds maximum size of %1</translation> <translation>Credential size exceeds maximum size of %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="87"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source> <source>Credential key exceeds maximum size of %1</source>
<translation>Credential key exceeds maximum size of %1</translation> <translation>Credential key exceeds maximum size of %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="92"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source> <source>Writing credentials failed: Win32 error code %1</source>
<translation>Writing credentials failed: Win32 error code %1</translation> <translation>Writing credentials failed: Win32 error code %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="162"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="162"/>
<source>Encryption failed</source> <source>Encryption failed</source>
<translation>Encryption failed</translation> <translation>Encryption failed</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="415"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="445"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus is not running</translation> <translation>D-Bus is not running</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="425"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="455"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="452"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="482"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="468"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="501"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="144"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="144"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="95"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="95"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="124"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="124"/>
<source>Could not create private key generator</source> <source>Could not create private key generator</source>
<translation>Could not create private key generator</translation> <translation>Could not create private key generator</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="131"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="131"/>
<source>Could not generate new private key</source> <source>Could not generate new private key</source>
<translation>Could not generate new private key</translation> <translation>Could not generate new private key</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="139"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="139"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation>Could not retrieve private key from keystore</translation> <translation>Could not retrieve private key from keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="147"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="147"/>
<source>Could not create encryption cipher</source> <source>Could not create encryption cipher</source>
<translation>Could not create encryption cipher</translation> <translation>Could not create encryption cipher</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="155"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="155"/>
<source>Could not encrypt data</source> <source>Could not encrypt data</source>
<translation>Could not encrypt data</translation> <translation>Could not encrypt data</translation>
</message> </message>
@ -2537,7 +2550,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../core/errorstrings.cpp" line="60"/> <location filename="../core/errorstrings.cpp" line="60"/>
<source>The config does not contain any containers and credentials for connecting to the server</source> <source>The config does not contain any containers and credentials for connecting to the server</source>
<translation type="unfinished"></translation> <translation type="unfinished">Конфиг не содержит контейнеров и учетных данных для подключения к серверу</translation>
</message> </message>
<message> <message>
<source>Failed to save config to disk</source> <source>Failed to save config to disk</source>
@ -2594,7 +2607,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>VPN pool error: no available addresses</translation> <translation>VPN pool error: no available addresses</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="64"/> <location filename="../core/errorstrings.cpp" line="63"/>
<source>VPN connection error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="67"/>
<source>Internal error</source> <source>Internal error</source>
<translation>Internal error</translation> <translation>Internal error</translation>
</message> </message>
@ -2814,7 +2832,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<message> <message>
<location filename="../containers/containers_defs.cpp" line="216"/> <location filename="../containers/containers_defs.cpp" line="216"/>
<source>Sftp file sharing service - is secure FTP service</source> <source>Sftp file sharing service - is secure FTP service</source>
<translation>Сервис обмена файлами Sftp - безопасный FTP-сервис</translation> <translation>Файловое хранилище для безопасного хранения данных</translation>
</message> </message>
<message> <message>
<location filename="../protocols/protocols_defs.cpp" line="77"/> <location filename="../protocols/protocols_defs.cpp" line="77"/>
@ -2822,62 +2840,62 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Сервис SFTP</translation> <translation>Сервис SFTP</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/libsecret.cpp" line="119"/> <location filename="../3rd/qtkeychain/qtkeychain/libsecret.cpp" line="119"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="225"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="255"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="227"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="257"/>
<source>No keyring daemon</source> <source>No keyring daemon</source>
<translation>No keyring daemon</translation> <translation>No keyring daemon</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="229"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="259"/>
<source>Already unlocked</source> <source>Already unlocked</source>
<translation>Already unlocked</translation> <translation>Already unlocked</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="231"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="261"/>
<source>No such keyring</source> <source>No such keyring</source>
<translation>No such keyring</translation> <translation>No such keyring</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="233"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="263"/>
<source>Bad arguments</source> <source>Bad arguments</source>
<translation>Bad arguments</translation> <translation>Bad arguments</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="235"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="265"/>
<source>I/O error</source> <source>I/O error</source>
<translation>I/O error</translation> <translation>I/O error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="237"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="267"/>
<source>Cancelled</source> <source>Cancelled</source>
<translation>Cancelled</translation> <translation>Cancelled</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="239"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="269"/>
<source>Keyring already exists</source> <source>Keyring already exists</source>
<translation>Keyring already exists</translation> <translation>Keyring already exists</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="241"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="271"/>
<source>No match</source> <source>No match</source>
<translation>No match</translation> <translation>No match</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="246"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="276"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="72"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="72"/>
<source>error 0x%1: %2</source> <source>error 0x%1: %2</source>
<translation>error 0x%1: %2</translation> <translation>error 0x%1: %2</translation>
</message> </message>
@ -2893,13 +2911,13 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>Settings</name> <name>Settings</name>
<message> <message>
<location filename="../settings.cpp" line="26"/> <location filename="../settings.cpp" line="30"/>
<source>Server #1</source> <source>Server #1</source>
<translation>Server #1</translation> <translation>Server #1</translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="202"/> <location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="209"/> <location filename="../settings.cpp" line="213"/>
<source>Server</source> <source>Server</source>
<translation>Server</translation> <translation>Server</translation>
</message> </message>
@ -2907,22 +2925,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="25"/> <location filename="../ui/controllers/settingsController.cpp" line="26"/>
<source>Software version</source> <source>Software version</source>
<translation>Версия ПО</translation> <translation>Версия ПО</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="137"/> <location filename="../ui/controllers/settingsController.cpp" line="139"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation> <translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="143"/> <location filename="../ui/controllers/settingsController.cpp" line="145"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>Кэш профиля очищен</translation> <translation>Кэш профиля очищен</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="122"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>Backup файл поврежден</translation> <translation>Backup файл поврежден</translation>
</message> </message>
@ -2959,7 +2977,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="150"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="150"/>
<source>Show connection settings</source> <source>Show connection settings</source>
<translation type="unfinished"></translation> <translation type="unfinished">Показать настройки подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="280"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="280"/>
@ -3054,7 +3072,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="429"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>

View file

@ -11,15 +11,9 @@
<context> <context>
<name>AndroidController</name> <name>AndroidController</name>
<message> <message>
<location filename="../platforms/android/android_controller.cpp" line="236"/>
<source>AmneziaVPN</source>
<translation></translation>
</message>
<message>
<location filename="../platforms/android/android_controller.cpp" line="239"/>
<source>VPN Connected</source> <source>VPN Connected</source>
<extracomment>Refers to the app - which is currently running the background and waiting</extracomment> <extracomment>Refers to the app - which is currently running the background and waiting</extracomment>
<translation>VPN已连接</translation> <translation type="vanished">VPN已连接</translation>
</message> </message>
</context> </context>
<context> <context>
@ -158,7 +152,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="435"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation> %1 of %2.</translation> <translation> %1 of %2.</translation>
</message> </message>
@ -997,6 +991,11 @@ And if you don&apos;t like the app, all the more support it - the donation will
<source>Show other methods on Github</source> <source>Show other methods on Github</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="104"/>
<source>https://github.com/amnezia-vpn/amnezia-client#donate</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/>
<source>Contacts</source> <source>Contacts</source>
@ -1511,22 +1510,22 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="111"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="142"/>
<source>Protocols</source> <source>Protocols</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="143"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="148"/>
<source>Services</source> <source>Services</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsServerInfo.qml" line="152"/>
<source>Data</source> <source>Data</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1957,6 +1956,11 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<source>I have nothing</source> <source>I have nothing</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="138"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PageSetupWizardTextKey</name> <name>PageSetupWizardTextKey</name>
@ -2078,7 +2082,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="231"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="231"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="483"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="486"/>
<source>Users</source> <source>Users</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2088,47 +2092,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation> VPN 访</translation> <translation> VPN 访</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="499"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="502"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="595"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="584"/>
<source>Creation date: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="598"/>
<source>Rename</source> <source>Rename</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="624"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Client name</source> <source>Client name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="632"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="636"/>
<source>Save</source> <source>Save</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="660"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="668"/>
<source>Revoke</source> <source>Revoke</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="671"/>
<source>Revoke the config for a user - </source> <source>Revoke the config for a user - %1?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="664"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="665"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="673"/>
<source>Continue</source> <source>Continue</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="666"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="674"/>
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2170,8 +2179,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="obsolete"></translation> <translation type="obsolete"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="279"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="280"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="280"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="281"/>
<source>Server</source> <source>Server</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2193,8 +2202,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="obsolete"></translation> <translation type="obsolete"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="331"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="332"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="332"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="333"/>
<source>Protocol</source> <source>Protocol</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2214,14 +2223,14 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="428"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="429"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="429"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="430"/>
<source>Connection format</source> <source>Connection format</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="186"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="186"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="468"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="469"/>
<source>Share</source> <source>Share</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2251,12 +2260,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="102"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="100"/>
<source>Accessing </source> <source>Accessing </source>
<translation type="unfinished">访</translation> <translation type="unfinished">访</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="103"/> <location filename="../ui/qml/Pages2/PageShareFullAccess.qml" line="101"/>
<source>File with accessing settings to </source> <source>File with accessing settings to </source>
<translation type="unfinished">访:</translation> <translation type="unfinished">访:</translation>
</message> </message>
@ -2287,38 +2296,38 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::DeletePasswordJobPrivate</name> <name>QKeychain::DeletePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="104"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="104"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="108"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="552"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="585"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="560"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="593"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="578"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="614"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>: %1; %2</translation> <translation>: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="177"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="177"/>
<source>Password not found</source> <source>Password not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="173"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="173"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="179"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="179"/>
<source>Could not remove private key from keystore</source> <source>Could not remove private key from keystore</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2326,12 +2335,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::JobPrivate</name> <name>QKeychain::JobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="265"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="295"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="509"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="542"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
@ -2339,27 +2348,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::PlainTextStore</name> <name>QKeychain::PlainTextStore</name>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="65"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source> <source>Could not store data in settings: access error</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="67"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source> <source>Could not store data in settings: format error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="85"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source> <source>Could not delete data from settings: access error</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="87"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source> <source>Could not delete data from settings: format error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/plaintextstore.cpp" line="104"/> <location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="104"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2367,80 +2376,80 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::ReadPasswordJobPrivate</name> <name>QKeychain::ReadPasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="32"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="32"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="36"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="36"/>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="139"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="178"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="205"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus未运行</translation> <translation>D-Bus未运行</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="187"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="214"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="197"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="224"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="286"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="316"/>
<source>No keychain service available</source> <source>No keychain service available</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="288"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="318"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>: %1; %2</translation> <translation>: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="333"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="363"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="354"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="384"/>
<source>Could not determine data type: %1; %2</source> <source>Could not determine data type: %1; %2</source>
<translation>: %1 %2</translation> <translation>: %1 %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="363"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="393"/>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="52"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="52"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="372"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="402"/>
<source>Unsupported entry type &apos;Map&apos;</source> <source>Unsupported entry type &apos;Map&apos;</source>
<translation> &apos;Map&apos;</translation> <translation> &apos;Map&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="375"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="405"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source> <source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation> &apos;%1&apos;</translation> <translation> &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="96"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="96"/>
<source>Password not found</source> <source>Password not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="60"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="60"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="68"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="68"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="75"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="75"/>
<source>Could not create decryption cipher</source> <source>Could not create decryption cipher</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2448,73 +2457,73 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::WritePasswordJobPrivate</name> <name>QKeychain::WritePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="78"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source> <source>Credential size exceeds maximum size of %1</source>
<translation>: %1</translation> <translation>: %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="87"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source> <source>Credential key exceeds maximum size of %1</source>
<translation>: %1</translation> <translation>: %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="92"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source> <source>Writing credentials failed: Win32 error code %1</source>
<translation>Win32错误码: %1</translation> <translation>Win32错误码: %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_win.cpp" line="162"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="162"/>
<source>Encryption failed</source> <source>Encryption failed</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="415"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="445"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus未运行</translation> <translation>D-Bus未运行</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="425"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="455"/>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="452"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="482"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="468"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="501"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>: %1; %2</translation> <translation>: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="144"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="144"/>
<source>Password not found</source> <source>Password not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="95"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="95"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="124"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="124"/>
<source>Could not create private key generator</source> <source>Could not create private key generator</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="131"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="131"/>
<source>Could not generate new private key</source> <source>Could not generate new private key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="139"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="139"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="147"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="147"/>
<source>Could not create encryption cipher</source> <source>Could not create encryption cipher</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_android.cpp" line="155"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="155"/>
<source>Could not encrypt data</source> <source>Could not encrypt data</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2666,6 +2675,11 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<source>Sftp error: No media was in remote drive</source> <source>Sftp error: No media was in remote drive</source>
<translation>Sftp 错误: 远程驱动器中没有媒介</translation> <translation>Sftp 错误: 远程驱动器中没有媒介</translation>
</message> </message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>VPN connection error</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Failed to save config to disk</source> <source>Failed to save config to disk</source>
<translation type="vanished"></translation> <translation type="vanished"></translation>
@ -2730,7 +2744,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished"></translation> <translation type="vanished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="64"/> <location filename="../core/errorstrings.cpp" line="67"/>
<source>Internal error</source> <source>Internal error</source>
<translation></translation> <translation></translation>
</message> </message>
@ -2970,62 +2984,62 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation>Sftp - FTP </translation> <translation>Sftp - FTP </translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/libsecret.cpp" line="119"/> <location filename="../3rd/qtkeychain/qtkeychain/libsecret.cpp" line="119"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="225"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="255"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="227"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="257"/>
<source>No keyring daemon</source> <source>No keyring daemon</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="229"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="259"/>
<source>Already unlocked</source> <source>Already unlocked</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="231"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="261"/>
<source>No such keyring</source> <source>No such keyring</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="233"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="263"/>
<source>Bad arguments</source> <source>Bad arguments</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="235"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="265"/>
<source>I/O error</source> <source>I/O error</source>
<translation>I/O错误</translation> <translation>I/O错误</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="237"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="267"/>
<source>Cancelled</source> <source>Cancelled</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="239"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="269"/>
<source>Keyring already exists</source> <source>Keyring already exists</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="241"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="271"/>
<source>No match</source> <source>No match</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_unix.cpp" line="246"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="276"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="72"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="72"/>
<source>error 0x%1: %2</source> <source>error 0x%1: %2</source>
<translation> 0x%1: %2</translation> <translation> 0x%1: %2</translation>
</message> </message>
@ -3041,13 +3055,13 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>Settings</name> <name>Settings</name>
<message> <message>
<location filename="../settings.cpp" line="26"/> <location filename="../settings.cpp" line="30"/>
<source>Server #1</source> <source>Server #1</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="202"/> <location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="209"/> <location filename="../settings.cpp" line="213"/>
<source>Server</source> <source>Server</source>
<translation></translation> <translation></translation>
</message> </message>
@ -3055,22 +3069,22 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="25"/> <location filename="../ui/controllers/settingsController.cpp" line="26"/>
<source>Software version</source> <source>Software version</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="122"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="137"/> <location filename="../ui/controllers/settingsController.cpp" line="139"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="143"/> <location filename="../ui/controllers/settingsController.cpp" line="145"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation></translation> <translation></translation>
</message> </message>
@ -3206,7 +3220,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="429"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation></translation> <translation></translation>
</message> </message>

View file

@ -15,7 +15,7 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "systemController.h" #include "systemController.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/androidutils.h" #include "platforms/android/android_utils.h"
#endif #endif
#include "qrcodegen.hpp" #include "qrcodegen.hpp"
@ -48,6 +48,22 @@ void ExportController::generateFullAccessConfig()
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
QJsonObject config = m_settings->server(serverIndex); QJsonObject config = m_settings->server(serverIndex);
QJsonArray containers = config.value(config_key::containers).toArray();
for (auto i = 0; i < containers.size(); i++) {
auto containerConfig = containers.at(i).toObject();
auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString());
for (auto protocol : ContainerProps::protocolsForContainer(containerType)) {
auto protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
protocolConfig.remove(config_key::last_config);
containerConfig[ProtocolProps::protoToString(protocol)] = protocolConfig;
}
containers.replace(i, containerConfig);
}
config[config_key::containers] = containers;
QByteArray compressedConfig = QJsonDocument(config).toJson(); QByteArray compressedConfig = QJsonDocument(config).toJson();
compressedConfig = qCompress(compressedConfig, 8); compressedConfig = qCompress(compressedConfig, 8);
m_config = QString("vpn://%1") m_config = QString("vpn://%1")

View file

@ -239,7 +239,12 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { // && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address"); lastConfig[config_key::client_ip] = configMap.value("Address");
lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); if (!configMap.value("PresharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PreSharedKey");
}
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else { // } else {
// qDebug() << "Failed to import profile"; // qDebug() << "Failed to import profile";

View file

@ -7,7 +7,7 @@
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "../../platforms/android/androidutils.h" #include "platforms/android/android_utils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
#if defined Q_OS_MAC #if defined Q_OS_MAC

View file

@ -7,19 +7,21 @@
#include "ui/qautostart.h" #include "ui/qautostart.h"
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "../../platforms/android/android_controller.h" #include "platforms/android/android_utils.h"
#include "../../platforms/android/androidutils.h" #include "platforms/android/android_controller.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
SettingsController::SettingsController(const QSharedPointer<ServersModel> &serversModel, SettingsController::SettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<LanguageModel> &languageModel, const QSharedPointer<LanguageModel> &languageModel,
const QSharedPointer<SitesModel> &sitesModel,
const std::shared_ptr<Settings> &settings, QObject *parent) const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), : QObject(parent),
m_serversModel(serversModel), m_serversModel(serversModel),
m_containersModel(containersModel), m_containersModel(containersModel),
m_languageModel(languageModel), m_languageModel(languageModel),
m_sitesModel(sitesModel),
m_settings(settings) m_settings(settings)
{ {
m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__);
@ -90,13 +92,21 @@ void SettingsController::openLogsFolder()
void SettingsController::exportLogsFile(const QString &fileName) void SettingsController::exportLogsFile(const QString &fileName)
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getLogFile()); SystemController::saveFile(fileName, Logger::getLogFile());
#endif
} }
void SettingsController::clearLogs() void SettingsController::clearLogs()
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(); Logger::clearLogs();
Logger::clearServiceLogs(); Logger::clearServiceLogs();
#endif
} }
void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::backupAppConfig(const QString &fileName)
@ -134,6 +144,7 @@ void SettingsController::clearSettings()
m_serversModel->resetModel(); m_serversModel->resetModel();
m_languageModel->changeLanguage( m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex())); static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
emit changeSettingsFinished(tr("All settings have been reset to default values")); emit changeSettingsFinished(tr("All settings have been reset to default values"));
} }
@ -195,3 +206,14 @@ void SettingsController::toggleScreenshotsEnabled(bool enable)
}); });
#endif #endif
} }
bool SettingsController::isCameraPresent()
{
#if defined Q_OS_IOS
return true;
#elif defined Q_OS_ANDROID
return AndroidController::instance()->isCameraPresent();
#else
return false;
#endif
}

View file

@ -6,6 +6,7 @@
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
#include "ui/models/sites_model.h"
class SettingsController : public QObject class SettingsController : public QObject
{ {
@ -14,6 +15,7 @@ public:
explicit SettingsController(const QSharedPointer<ServersModel> &serversModel, explicit SettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<LanguageModel> &languageModel, const QSharedPointer<LanguageModel> &languageModel,
const QSharedPointer<SitesModel> &sitesModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr); const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
@ -57,6 +59,8 @@ public slots:
bool isScreenshotsEnabled(); bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable); void toggleScreenshotsEnabled(bool enable);
bool isCameraPresent();
signals: signals:
void primaryDnsChanged(); void primaryDnsChanged();
void secondaryDnsChanged(); void secondaryDnsChanged();
@ -76,6 +80,7 @@ private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<LanguageModel> m_languageModel; QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<SitesModel> m_sitesModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
QString m_appVersion; QString m_appVersion;

View file

@ -60,6 +60,11 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{ {
QString fileName; QString fileName;
#ifdef Q_OS_ANDROID
Q_ASSERT(!isSaveMode);
return AndroidController::instance()->openFile(nameFilter);
#endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
MobileUtils mobileUtils; MobileUtils mobileUtils;
@ -108,20 +113,6 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
} }
fileName = mainFileDialog->property("selectedFile").toString(); fileName = mainFileDialog->property("selectedFile").toString();
#ifdef Q_OS_ANDROID
// patch for files containing spaces etc
const QString sep { "raw%3A%2F" };
if (fileName.startsWith("content://") && fileName.contains(sep)) {
QString contentUrl = fileName.split(sep).at(0);
QString rawUrl = fileName.split(sep).at(1);
rawUrl.replace(" ", "%20");
fileName = contentUrl + sep + rawUrl;
}
return fileName;
#endif
return QUrl(fileName).toLocalFile(); return QUrl(fileName).toLocalFile();
} }

View file

@ -15,6 +15,7 @@ namespace
constexpr char clientName[] = "clientName"; constexpr char clientName[] = "clientName";
constexpr char container[] = "container"; constexpr char container[] = "container";
constexpr char userData[] = "userData"; constexpr char userData[] = "userData";
constexpr char creationDate[] = "creationDate";
} }
} }
@ -40,11 +41,29 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
switch (role) { switch (role) {
case ClientNameRole: return userData.value(configKey::clientName).toString(); case ClientNameRole: return userData.value(configKey::clientName).toString();
case CreationDateRole: return userData.value(configKey::creationDate).toString();
} }
return QVariant(); return QVariant();
} }
void ClientManagementModel::migration(const QByteArray &clientsTableString)
{
QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object();
for (auto &clientId : clientsTable.keys()) {
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = clientsTable.value(clientId).toObject().value(configKey::clientName);
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
}
}
ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials) ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials)
{ {
beginResetModel(); beginResetModel();
@ -54,8 +73,14 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr
ErrorCode error = ErrorCode::NoError; ErrorCode error = ErrorCode::NoError;
const QString clientsTableFile = QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
const QByteArray clientsTableString = const QByteArray clientsTableString =
serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error); serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@ -67,6 +92,8 @@ ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCr
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array(); m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) { if (m_clientsTable.isEmpty()) {
migration(clientsTableString);
int count = 0; int count = 0;
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
@ -200,25 +227,31 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
for (int i = 0; i < m_clientsTable.size(); i++) { for (int i = 0; i < m_clientsTable.size(); i++) {
if (m_clientsTable.at(i).toObject().value(configKey::clientId) == clientId) { if (m_clientsTable.at(i).toObject().value(configKey::clientId) == clientId) {
return renameClient(i, clientName, container, credentials); return renameClient(i, clientName, container, credentials, true);
} }
} }
beginResetModel(); beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1);
QJsonObject client; QJsonObject client;
client[configKey::clientId] = clientId; client[configKey::clientId] = clientId;
QJsonObject userData; QJsonObject userData;
userData[configKey::clientName] = clientName; userData[configKey::clientName] = clientName;
userData[configKey::creationDate] = QDateTime::currentDateTime().toString();
client[configKey::userData] = userData; client[configKey::userData] = userData;
m_clientsTable.push_back(client); m_clientsTable.push_back(client);
endResetModel(); endInsertRows();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
const QString clientsTableFile = QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@ -229,11 +262,14 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
} }
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container, ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) ServerCredentials credentials, bool addTimeStamp)
{ {
auto client = m_clientsTable.at(row).toObject(); auto client = m_clientsTable.at(row).toObject();
auto userData = client[configKey::userData].toObject(); auto userData = client[configKey::userData].toObject();
userData[configKey::clientName] = clientName; userData[configKey::clientName] = clientName;
if (addTimeStamp) {
userData[configKey::creationDate] = QDateTime::currentDateTime().toString();
}
client[configKey::userData] = userData; client[configKey::userData] = userData;
m_clientsTable.replace(row, client); m_clientsTable.replace(row, client);
@ -242,8 +278,13 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
const QString clientsTableFile = QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
ErrorCode error = ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
@ -257,13 +298,33 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials) ServerCredentials credentials)
{ {
ErrorCode errorCode = ErrorCode::NoError;
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) { || container == DockerContainer::Cloak) {
return revokeOpenVpn(row, container, credentials); errorCode = revokeOpenVpn(row, container, credentials);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
return revokeWireGuard(row, container, credentials); errorCode = revokeWireGuard(row, container, credentials);
} }
return ErrorCode::NoError;
if (errorCode == ErrorCode::NoError) {
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
const auto server = m_settings->defaultServer();
QJsonArray containers = server.value(config_key::containers).toArray();
for (auto i = 0; i < containers.size(); i++) {
auto containerConfig = containers.at(i).toObject();
auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString());
auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) {
emit adminConfigRevoked(container);
}
}
}
return errorCode;
} }
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
@ -294,8 +355,13 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile = QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server"; logger.error() << "Failed to upload the clientsTable file to the server";
@ -344,8 +410,13 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile = QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server"; logger.error() << "Failed to upload the clientsTable file to the server";
@ -369,5 +440,6 @@ QHash<int, QByteArray> ClientManagementModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[ClientNameRole] = "clientName"; roles[ClientNameRole] = "clientName";
roles[CreationDateRole] = "creationDate";
return roles; return roles;
} }

View file

@ -14,6 +14,7 @@ class ClientManagementModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
ClientNameRole = Qt::UserRole + 1, ClientNameRole = Qt::UserRole + 1,
CreationDateRole
}; };
ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr); ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
@ -26,15 +27,20 @@ public slots:
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials); ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials); ServerCredentials credentials, bool addTimeStamp = false);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
signals:
void adminConfigRevoked(const DockerContainer container);
private: private:
bool isClientExists(const QString &clientId); bool isClientExists(const QString &clientId);
void migration(const QByteArray &clientsTableString);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);

View file

@ -15,6 +15,10 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer); emit ServersModel::defaultContainerChanged(defaultContainer);
}); });
connect(this, &ServersModel::currentlyProcessedServerIndexChanged, this, [this](const int serverIndex) {
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer);
});
} }
int ServersModel::rowCount(const QModelIndex &parent) const int ServersModel::rowCount(const QModelIndex &parent) const
@ -247,10 +251,9 @@ void ServersModel::addServer(const QJsonObject &server)
void ServersModel::editServer(const QJsonObject &server) void ServersModel::editServer(const QJsonObject &server)
{ {
beginResetModel();
m_settings->editServer(m_currentlyProcessedServerIndex, server); m_settings->editServer(m_currentlyProcessedServerIndex, server);
m_servers = m_settings->serversArray(); m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex));
endResetModel(); emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0));
updateContainersModel(); updateContainersModel();
} }
@ -269,6 +272,7 @@ void ServersModel::removeServer()
if (m_settings->serversCount() == 0) { if (m_settings->serversCount() == 0) {
setDefaultServerIndex(-1); setDefaultServerIndex(-1);
} }
setCurrentlyProcessedServerIndex(m_defaultServerIndex);
endResetModel(); endResetModel();
} }
@ -479,6 +483,14 @@ void ServersModel::clearCachedProfiles()
updateContainersModel(); updateContainersModel();
} }
void ServersModel::clearCachedProfile(const DockerContainer container)
{
m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container);
m_servers.replace(m_currentlyProcessedServerIndex, m_settings->server(m_currentlyProcessedServerIndex));
updateContainersModel();
}
bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex) bool ServersModel::isAmneziaDnsContainerInstalled(const int serverIndex)
{ {
QJsonObject server = m_servers.at(serverIndex).toObject(); QJsonObject server = m_servers.at(serverIndex).toObject();
@ -518,3 +530,8 @@ void ServersModel::toggleAmneziaDns(bool enabled)
emit defaultServerDescriptionChanged(); emit defaultServerDescriptionChanged();
} }
bool ServersModel::isDefaultServerFromApi()
{
return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt();
}

View file

@ -84,6 +84,7 @@ public slots:
void addContainerConfig(const int containerIndex, const QJsonObject config); void addContainerConfig(const int containerIndex, const QJsonObject config);
void clearCachedProfiles(); void clearCachedProfiles();
void clearCachedProfile(const DockerContainer container);
ErrorCode removeContainer(const int containerIndex); ErrorCode removeContainer(const int containerIndex);
ErrorCode removeAllContainers(); ErrorCode removeAllContainers();
@ -96,6 +97,8 @@ public slots:
void toggleAmneziaDns(bool enabled); void toggleAmneziaDns(bool enabled);
bool isDefaultServerFromApi();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;

View file

@ -124,8 +124,13 @@ void Autostart::setAutostart(bool autostart) {
if (file.open(QIODevice::ReadWrite)) { if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file); QTextStream stream(&file);
stream << "[Desktop Entry]" << Qt::endl; stream << "[Desktop Entry]" << Qt::endl;
stream << "Exec=" << appPath() << Qt::endl; stream << "Exec=AmneziaVPN" << Qt::endl;
stream << "Type=Application" << Qt::endl; stream << "Type=Application" << Qt::endl;
stream << "Name=AmneziaVPN" << Qt::endl;
stream << "Comment=Client of your self-hosted VPN" << Qt::endl;
stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl;
stream << "Categories=Network;Qt;Security;" << Qt::endl;
stream << "Terminal=false" << Qt::endl;
} }
} }
} }

View file

@ -26,6 +26,24 @@ ListView {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
if (ContainersModel.getDefaultContainer()) {
menuContent.checkCurrentItem()
}
}
}
function checkCurrentItem() {
var item = menuContent.itemAtIndex(currentIndex)
if (item !== null) {
var radioButton = item.children[0].children[0]
radioButton.checked = true
}
}
delegate: Item { delegate: Item {
implicitWidth: rootWidth implicitWidth: rootWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@ -60,9 +78,8 @@ ListView {
} }
if (checked) { if (checked) {
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
containersDropDown.menuVisible = false containersDropDown.menuVisible = false
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
} else { } else {
if (!isSupported && isInstalled) { if (!isSupported && isInstalled) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))

View file

@ -25,7 +25,7 @@ DrawerType {
property string configExtension: ".vpn" property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config") property string configCaption: qsTr("Save AmneziaVPN config")
property string configFileName: "amnezia_config.vpn" property string configFileName: "amnezia_config"
width: parent.width width: parent.width
height: parent.height * 0.9 height: parent.height * 0.9

View file

@ -31,6 +31,7 @@ PageType {
containersDropDown.rootButtonClickedFunction() containersDropDown.rootButtonClickedFunction()
} }
} }
function onForceCloseDrawer() { function onForceCloseDrawer() {
buttonContent.state = "collapsed" buttonContent.state = "collapsed"
} }

View file

@ -101,7 +101,7 @@ PageType {
text: qsTr("Show other methods on Github") text: qsTr("Show other methods on Github")
onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client#donate") onClicked: Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client#donate"))
} }
ParagraphTextType { ParagraphTextType {

View file

@ -28,6 +28,14 @@ PageType {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height contentHeight: content.height
enabled: !ServersModel.isDefaultServerFromApi()
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support custom dns"))
}
}
ColumnLayout { ColumnLayout {
id: content id: content

View file

@ -66,7 +66,8 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3 Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3
visible: !GC.isMobile()
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -90,7 +91,7 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3 Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -131,7 +132,7 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3 Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter

View file

@ -55,6 +55,9 @@ PageType {
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxyContainersModel id: proxyContainersModel
sourceModel: ContainersModel sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
]
} }
Component.onCompleted: updateContainersModelFilters() Component.onCompleted: updateContainersModelFilters()

View file

@ -21,7 +21,13 @@ PageType {
id: root id: root
property bool pageEnabled: { property bool pageEnabled: {
return !ConnectionController.isConnected return !ConnectionController.isConnected && !ServersModel.isDefaultServerFromApi()
}
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function"))
}
} }
Connections { Connections {

View file

@ -90,7 +90,7 @@ It's okay as long as it's from someone you trust.")
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
visible: GC.isMobile() visible: SettingsController.isCameraPresent()
text: qsTr("QR-code") text: qsTr("QR-code")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
@ -105,7 +105,7 @@ It's okay as long as it's from someone you trust.")
} }
DividerType { DividerType {
visible: GC.isMobile() visible: SettingsController.isCameraPresent()
} }
LabelWithButtonType { LabelWithButtonType {

View file

@ -112,17 +112,19 @@ PageType {
var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer)
containers.dockerContainer = dockerContainer containers.dockerContainer = dockerContainer
containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto) containers.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto)
containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto)
} }
} }
} }
}
Component.onCompleted: { Component.onCompleted: {
if (index === containers.currentIndex) { var item = containers.itemAtIndex(containers.currentIndex)
card.checked = true if (item !== null) {
card.clicked() var button = item.children[0].children[0]
} button.checked = true
button.clicked()
} }
} }
} }

View file

@ -42,6 +42,7 @@ PageType {
function onInstallServerFinished(finishedMessage) { function onInstallServerFinished(finishedMessage) {
if (!ConnectionController.isConnected) { if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
} }
PageController.goToStartPage() PageController.goToStartPage()

View file

@ -135,7 +135,7 @@ PageType {
text: qsTr("I have nothing") text: qsTr("I have nothing")
onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide") onClicked: Qt.openUrlExternally(qsTr("https://amnezia.org/instructions/0_starter-guide"))
} }
} }

View file

@ -26,6 +26,7 @@ PageType {
function onImportFinished() { function onImportFinished() {
if (!ConnectionController.isConnected) { if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
} }
PageController.goToStartPage() PageController.goToStartPage()

View file

@ -581,7 +581,7 @@ PageType {
Layout.bottomMargin: 24 Layout.bottomMargin: 24
headerText: clientName headerText: clientName
descriptionText: serverSelector.text descriptionText: qsTr("Creation date: ") + creationDate
} }
BasicButtonType { BasicButtonType {

View file

@ -4,6 +4,7 @@
#include <QFile> #include <QFile>
#include <QHostInfo> #include <QHostInfo>
#include <QJsonObject> #include <QJsonObject>
#include <QEventLoop>
#include <configurators/cloak_configurator.h> #include <configurators/cloak_configurator.h>
#include <configurators/openvpn_configurator.h> #include <configurators/openvpn_configurator.h>
@ -63,24 +64,26 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->resetIpStack();
IpcClient::Interface()->flushDns(); IpcClient::Interface()->flushDns();
if (m_settings->routeMode() != Settings::VpnAllSites) { if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) {
IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); if (m_settings->routeMode() != Settings::VpnAllSites) {
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
} // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); }
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
QTimer::singleShot(1000, m_vpnProtocol.data(), QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
}
} }
} else if (state == Vpn::ConnectionState::Error) { } else if (state == Vpn::ConnectionState::Error) {
@ -250,7 +253,13 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser
m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); m_settings->setProtocolConfig(serverIndex, container, proto, protoObject);
} }
emit m_configurator->newVpnConfigCreated(clientId, "unnamed client", container, credentials); if ((container != DockerContainer::Cloak && container != DockerContainer::ShadowSocks) ||
((container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) && proto == Proto::OpenVpn)) {
QEventLoop wait;
emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials);
QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit);
wait.exec();
}
} }
return configData; return configData;
@ -292,6 +301,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerC
vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString(); vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString();
vpnConfiguration[config_key::description] = server.value(config_key::description).toString(); vpnConfiguration[config_key::description] = server.value(config_key::description).toString();
vpnConfiguration[config_key::configVersion] = server.value(config_key::configVersion).toInt();
// TODO: try to get hostName, port, description for 3rd party configs // TODO: try to get hostName, port, description for 3rd party configs
// vpnConfiguration[config_key::port] = ...; // vpnConfiguration[config_key::port] = ...;

View file

@ -7,15 +7,18 @@ usage() {
cat <<EOT cat <<EOT
Usage: Usage:
build_android [options] build_android [options] <artifact_types>
Build AmneziaVPN android client. By default, a signed Android App Bundle (AAB) is built. Build AmneziaVPN android client.
Options: Artifact types:
-d, --debug Build debug version -u, --aab Build Android App Bundle (AAB)
-a, --apk (<abi_list> | all) Build APKs for the specified ABIs or for all available ABIs -a, --apk (<abi_list> | all) Build APKs for the specified ABIs or for all available ABIs
Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
<abi_list> - list of ABIs delimited by ';' <abi_list> - list of ABIs delimited by ';'
Options:
-d, --debug Build debug version
-b, --build-platform <platform> The SDK platform used for building the Java code of the application -b, --build-platform <platform> The SDK platform used for building the Java code of the application
By default, the latest available platform is used By default, the latest available platform is used
-m, --move Move the build result to the root of the build directory -m, --move Move the build result to the root of the build directory
@ -25,14 +28,14 @@ EOT
} }
BUILD_TYPE="release" BUILD_TYPE="release"
AAB=1
opts=$(getopt -l debug,apk:,build-platform:,move,help -o "da:b:mh" -- "$@") opts=$(getopt -l debug,aab,apk:,build-platform:,move,help -o "dua:b:mh" -- "$@")
eval set -- "$opts" eval set -- "$opts"
while true; do while true; do
case "$1" in case "$1" in
-d | --debug) BUILD_TYPE="debug"; shift;; -d | --debug) BUILD_TYPE="debug"; shift;;
-a | --apk) ABIS=$2; unset AAB; shift 2;; -u | --aab) AAB=1; shift;;
-a | --apk) ABIS=$2; shift 2;;
-b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;; -b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;;
-m | --move) MOVE_RESULT=1; shift;; -m | --move) MOVE_RESULT=1; shift;;
-h | --help) usage; exit 0;; -h | --help) usage; exit 0;;
@ -49,6 +52,11 @@ if [[ -v ABIS && \
exit 1 exit 1
fi fi
# At least one artifact type must be specified
if [[ ! (-v AAB || -v ABIS) ]]; then
usage; exit 0
fi
echo "Build script started..." echo "Build script started..."
PROJECT_DIR=$(pwd) PROJECT_DIR=$(pwd)
@ -137,7 +145,8 @@ gradle_opts=()
if [ -v AAB ]; then if [ -v AAB ]; then
gradle_opts+=(bundle"${BUILD_TYPE^}") gradle_opts+=(bundle"${BUILD_TYPE^}")
else fi
if [ -v ABIS ]; then
gradle_opts+=(assemble"${BUILD_TYPE^}") gradle_opts+=(assemble"${BUILD_TYPE^}")
fi fi
@ -151,7 +160,9 @@ if [[ -v CI || -v MOVE_RESULT ]]; then
if [ -v AAB ]; then if [ -v AAB ]; then
mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/AmneziaVPN-$BUILD_TYPE.aab \ mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/AmneziaVPN-$BUILD_TYPE.aab \
$PROJECT_DIR/deploy/build/ $PROJECT_DIR/deploy/build/
else fi
if [ -v ABIS ]; then
if [ "$ABIS" = "all" ]; then if [ "$ABIS" = "all" ]; then
ABIS="x86;x86_64;armeabi-v7a;arm64-v8a" ABIS="x86;x86_64;armeabi-v7a;arm64-v8a"
fi fi

View file

@ -83,6 +83,4 @@ ldd $CQTDEPLOYER_DIR/bin/binarycreator
cp -r $PROJECT_DIR/deploy/installer $BUILD_DIR cp -r $PROJECT_DIR/deploy/installer $BUILD_DIR
$CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer $CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer.bin

View file

@ -2,7 +2,7 @@
# Mac name-resolution updater based on @cl's script here: # Mac name-resolution updater based on @cl's script here:
# https://blog.netnerds.net/2011/10/openvpn-update-client-dns-on-mac-os-x-using-from-the-command-line/ # https://blog.netnerds.net/2011/10/openvpn-update-client-dns-on-mac-os-x-using-from-the-command-line/
# Openvpn envvar parsing taken from the script in debian's openvpn package. # Openvpn envar parsing taken from the script in debian's openvpn package.
# Smushed together and improved by @andrewgdotcom. # Smushed together and improved by @andrewgdotcom.
# Parses DHCP options from openvpn to update resolv.conf # Parses DHCP options from openvpn to update resolv.conf
@ -10,6 +10,8 @@
# up /etc/openvpn/update-resolv-conf # up /etc/openvpn/update-resolv-conf
# down /etc/openvpn/update-resolv-conf # down /etc/openvpn/update-resolv-conf
echo "*** starting update-resolv-config script ***"
[ "$script_type" ] || exit 0 [ "$script_type" ] || exit 0
[ "$dev" ] || exit 0 [ "$dev" ] || exit 0
@ -34,11 +36,11 @@ update_all_dns()
echo updating dns for $adapter echo updating dns for $adapter
# set dns server to the vpn dns server # set dns server to the vpn dns server
if [[ "${SRCHS[@]}" ]]; then if [[ "${SRCHS[@]}" ]]; then
networksetup -setsearchdomains "$adapter" "${SRCHS[@]}" networksetup -setsearchdomains "$adapter" "${SRCHS[@]}"
fi fi
if [[ "${NMSRVRS[@]}" ]]; then if [[ "${NMSRVRS[@]}" ]]; then
networksetup -setdnsservers "$adapter" "${NMSRVRS[@]}" networksetup -setdnsservers "$adapter" "${NMSRVRS[@]}"
fi fi
done done
} }
@ -61,7 +63,7 @@ case "$script_type" in
if [ "$part1" = "dhcp-option" ] ; then if [ "$part1" = "dhcp-option" ] ; then
if [ "$part2" = "DNS" ] ; then if [ "$part2" = "DNS" ] ; then
NMSRVRS=(${NMSRVRS[@]} $part3) NMSRVRS=(${NMSRVRS[@]} $part3)
elif [ "$part2" = "DOMAIN" ] ; then elif [ "$part2" = "DOMAIN" ] || [ "$part2" = "DOMAIN-SEARCH" ]; then
SRCHS=(${SRCHS[@]} $part3) SRCHS=(${SRCHS[@]} $part3)
fi fi
fi fi
@ -72,3 +74,5 @@ case "$script_type" in
clear_all_dns clear_all_dns
;; ;;
esac esac
echo "*** finished update-resolv-config script ***"