Merge branch 'dev' into update_server_scripts

This commit is contained in:
pokamest 2024-03-18 21:43:55 +00:00
commit b19009b669
96 changed files with 5314 additions and 1008 deletions

View file

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

@ -1 +1 @@
Subproject commit 2fa21880b9e5059cf9e8856c778bad97dcad3c91
Subproject commit ab4e6b680dc3d3d81809886c2c25b77750659c1a

View file

@ -67,6 +67,7 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)

View file

@ -24,6 +24,7 @@
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@ -95,7 +96,18 @@ void AmneziaApplication::init()
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
connect(m_settings.get(), &Settings::saveLogsChanged,
AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged,
AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved,
AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared,
[](){ AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) {
@ -127,6 +139,14 @@ void AmneziaApplication::init()
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this](){
AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled());
});
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) {
AmneziaVPN::toggleScreenshots(enabled);
});
#endif
m_notificationHandler.reset(NotificationHandler::create(nullptr));

View file

@ -56,6 +56,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
@ -146,6 +150,22 @@
</intent-filter>
</service>
<service
android:name=".AmneziaTileService"
android:process=":amneziaTileService"
android:icon="@drawable/ic_amnezia_round"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.qtprovider"

View file

@ -64,7 +64,7 @@ class Awg : Wireguard() {
val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build {
configWireguard(configData)
configWireguard(configData, configDataJson)
configSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) }

View file

@ -111,4 +111,5 @@ dependencies {
implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
}

View file

@ -6,6 +6,7 @@ androidx-activity = "1.8.1"
androidx-annotation = "1.7.0"
androidx-camera = "1.3.0"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3"
google-mlkit = "17.2.0"
@ -18,6 +19,7 @@ androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.r
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }

View file

@ -2,9 +2,9 @@ package org.amnezia.vpn.protocol
// keep synchronized with client/platforms/android/android_controller.h ConnectionState
enum class ProtocolState {
DISCONNECTED,
CONNECTED,
CONNECTING,
DISCONNECTED,
DISCONNECTING,
RECONNECTING,
UNKNOWN

View file

@ -28,6 +28,10 @@ fun Bundle.putStatus(status: Status) {
putInt(STATE_KEY, status.state.ordinal)
}
fun Bundle.putStatus(state: ProtocolState) {
putInt(STATE_KEY, state.ordinal)
}
fun Bundle.getStatus(): Status =
Status.build {
setState(ProtocolState.entries[getInt(STATE_KEY)])

View file

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Подключение</string>
<string name="disconnecting">Отключение</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Connecting</string>
<string name="disconnecting">Disconnecting</string>
</resources>

View file

@ -14,6 +14,7 @@ import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.os.Messenger
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.annotation.MainThread
@ -26,9 +27,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController
@ -36,11 +35,11 @@ import org.amnezia.vpn.util.Log
import org.qtproject.qt.android.bindings.QtActivity
private const val TAG = "AmneziaActivity"
const val ACTIVITY_MESSENGER_NAME = "Activity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() {
@ -58,25 +57,17 @@ class AmneziaActivity : QtActivity() {
val event = msg.extractIpcMessage<ServiceEvent>()
Log.d(TAG, "Handle event: $event")
when (event) {
ServiceEvent.CONNECTED -> {
QtAndroidController.onVpnConnected()
}
ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected()
doUnbindService()
}
ServiceEvent.RECONNECTING -> {
QtAndroidController.onVpnReconnecting()
ServiceEvent.STATUS_CHANGED -> {
msg.data?.getStatus()?.let { (state) ->
Log.d(TAG, "Handle protocol state: $state")
QtAndroidController.onVpnStateChanged(state.ordinal)
}
}
ServiceEvent.STATUS -> {
if (isWaitingStatus) {
isWaitingStatus = false
msg.data?.getStatus()?.let { (state) ->
QtAndroidController.onStatus(state.ordinal)
}
msg.data?.getStatus()?.let { QtAndroidController.onStatus(it) }
}
}
@ -87,7 +78,7 @@ class AmneziaActivity : QtActivity() {
}
ServiceEvent.ERROR -> {
msg.data?.getString(ERROR_MSG)?.let { error ->
msg.data?.getString(MSG_ERROR)?.let { error ->
Log.e(TAG, "From VpnService: $error")
}
// todo: add error reporting to Qt
@ -109,14 +100,15 @@ class AmneziaActivity : QtActivity() {
// get a messenger from the service to send actions to the service
vpnServiceMessenger.set(Messenger(service))
// send a messenger to the service to process service events
vpnServiceMessenger.send {
Action.REGISTER_CLIENT.packToMessage().apply {
replyTo = activityMessenger
}
}
vpnServiceMessenger.send(
Action.REGISTER_CLIENT.packToMessage {
putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
},
replyTo = activityMessenger
)
isServiceConnected = true
if (isWaitingStatus) {
vpnServiceMessenger.send(Action.REQUEST_STATUS)
vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger)
}
}
@ -126,6 +118,7 @@ class AmneziaActivity : QtActivity() {
vpnServiceMessenger.reset()
isWaitingStatus = true
QtAndroidController.onServiceDisconnected()
doBindService()
}
override fun onBindingDied(name: ComponentName?) {
@ -148,8 +141,11 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService,
messengerName = "VpnService"
"VpnService",
onDeadObjectException = {
doUnbindService()
doBindService()
}
)
intent?.let(::processIntent)
}
@ -244,10 +240,9 @@ class AmneziaActivity : QtActivity() {
private fun doBindService() {
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
}
isInBoundState = true
handleBindTimeout()
}
@MainThread
@ -256,26 +251,14 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Unbind service")
isWaitingStatus = true
QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset()
isServiceConnected = false
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
vpnServiceMessenger.reset()
isInBoundState = false
unbindService(serviceConnection)
}
}
private fun handleBindTimeout() {
mainScope.launch {
if (isWaitingStatus) {
delay(BIND_SERVICE_TIMEOUT)
if (isWaitingStatus && !isServiceConnected) {
Log.d(TAG, "Bind timeout, reset connection status")
isWaitingStatus = false
QtAndroidController.onStatus(ProtocolState.DISCONNECTED.ordinal)
}
}
}
}
/**
* Methods of starting and stopping VpnService
*/
@ -312,7 +295,7 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send {
Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig)
putString(MSG_VPN_CONFIG, vpnConfig)
}
}
}
@ -320,7 +303,7 @@ class AmneziaActivity : QtActivity() {
private fun startVpnService(vpnConfig: String) {
Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig)
putExtra(MSG_VPN_CONFIG, vpnConfig)
}.also {
ContextCompat.startForegroundService(this, it)
}
@ -369,6 +352,22 @@ class AmneziaActivity : QtActivity() {
}
}
@Suppress("unused")
fun resetLastServer(index: Int) {
Log.v(TAG, "Reset server: $index")
mainScope.launch {
VpnStateStore.store {
if (index == -1 || it.serverIndex == index) {
VpnState.defaultState
} else if (it.serverIndex > index) {
it.copy(serverIndex = it.serverIndex - 1)
} else {
it
}
}
}
}
@Suppress("unused")
fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName")
@ -438,7 +437,7 @@ class AmneziaActivity : QtActivity() {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
putBoolean(MSG_SAVE_LOGS, enabled)
}
}
}
@ -455,4 +454,13 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
@Suppress("unused")
fun setScreenshotsEnabled(enabled: Boolean) {
Log.v(TAG, "Set screenshots enabled: $enabled")
mainScope.launch {
val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE
window.setFlags(flag, LayoutParams.FLAG_SECURE)
}
}
}

View file

@ -18,6 +18,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
super.onCreate()
Prefs.init(this)
Log.init(this)
VpnStateStore.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel()
}

View file

@ -0,0 +1,272 @@
package org.amnezia.vpn
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.net.VpnService
import android.os.Build
import android.os.IBinder
import android.os.Messenger
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.core.content.ContextCompat
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.util.Log
private const val TAG = "AmneziaTileService"
private const val DEFAULT_TILE_LABEL = "AmneziaVPN"
class AmneziaTileService : TileService() {
private lateinit var scope: CoroutineScope
private var vpnStateListeningJob: Job? = null
private lateinit var vpnServiceMessenger: IpcMessenger
@Volatile
private var isServiceConnected = false
private var isInBoundState = false
@Volatile
private var isVpnConfigExists = false
private val serviceConnection: ServiceConnection by lazy(NONE) {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "Service ${name?.flattenToString()} was connected")
vpnServiceMessenger.set(Messenger(service))
isServiceConnected = true
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.w(TAG, "Service ${name?.flattenToString()} was unexpectedly disconnected")
isServiceConnected = false
vpnServiceMessenger.reset()
updateVpnState(DISCONNECTED)
}
override fun onBindingDied(name: ComponentName?) {
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
doUnbindService()
doBindService()
}
}
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Create Amnezia Tile Service")
scope = CoroutineScope(SupervisorJob())
vpnServiceMessenger = IpcMessenger(
"VpnService",
onDeadObjectException = ::doUnbindService
)
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia Tile Service")
doUnbindService()
scope.cancel()
super.onDestroy()
}
// Workaround for some bugs
override fun onBind(intent: Intent?): IBinder? =
try {
super.onBind(intent)
} catch (e: Throwable) {
Log.e(TAG, "Failed to bind AmneziaTileService: $e")
null
}
override fun onStartListening() {
super.onStartListening()
Log.d(TAG, "Start listening")
if (AmneziaVpnService.isRunning(applicationContext)) {
Log.d(TAG, "Vpn service is running")
doBindService()
} else {
Log.d(TAG, "Vpn service is not running")
isServiceConnected = false
updateVpnState(DISCONNECTED)
}
vpnStateListeningJob = launchVpnStateListening()
}
override fun onStopListening() {
Log.d(TAG, "Stop listening")
vpnStateListeningJob?.cancel()
vpnStateListeningJob = null
doUnbindService()
super.onStopListening()
}
override fun onClick() {
Log.d(TAG, "onClick")
if (isLocked) {
unlockAndRun { onClickInternal() }
} else {
onClickInternal()
}
}
private fun onClickInternal() {
if (isVpnConfigExists) {
Log.d(TAG, "Change VPN state")
if (qsTile.state == Tile.STATE_INACTIVE) {
Log.d(TAG, "Start VPN")
updateVpnState(CONNECTING)
startVpn()
} else if (qsTile.state == Tile.STATE_ACTIVE) {
Log.d(TAG, "Stop vpn")
updateVpnState(DISCONNECTING)
stopVpn()
}
} else {
Log.d(TAG, "Start Activity")
Intent(this, AmneziaActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
}
}
private fun doBindService() {
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
}
isInBoundState = true
}
private fun doUnbindService() {
if (isInBoundState) {
Log.d(TAG, "Unbind service")
isServiceConnected = false
vpnServiceMessenger.reset()
isInBoundState = false
unbindService(serviceConnection)
}
}
private fun startVpn() {
if (isServiceConnected) {
connectToVpn()
} else {
if (checkPermission()) {
startVpnService()
doBindService()
} else {
updateVpnState(DISCONNECTED)
}
}
}
private fun checkPermission() =
if (VpnService.prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
false
} else {
true
}
private fun startVpnService() =
ContextCompat.startForegroundService(
applicationContext,
Intent(this, AmneziaVpnService::class.java)
)
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
private fun stopVpn() = vpnServiceMessenger.send(Action.DISCONNECT)
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun startActivityAndCollapseCompat(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(
PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
)
} else {
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
}
}
private fun updateVpnState(state: ProtocolState) {
scope.launch {
VpnStateStore.store { it.copy(protocolState = state) }
}
}
private fun launchVpnStateListening() =
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
private fun updateTile(vpnState: VpnState) {
Log.d(TAG, "Update tile: $vpnState")
isVpnConfigExists = vpnState.serverName != null
val tile = qsTile ?: return
tile.apply {
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
when (vpnState.protocolState) {
CONNECTED -> {
state = Tile.STATE_ACTIVE
subtitleCompat = null
}
DISCONNECTED, UNKNOWN -> {
state = Tile.STATE_INACTIVE
subtitleCompat = null
}
CONNECTING, RECONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.connecting)
}
DISCONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.disconnecting)
}
}
updateTile()
}
// double update to fix weird visual glitches
tile.updateTile()
}
private var Tile.subtitleCompat: CharSequence?
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.subtitle = value
}
}
get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return this.subtitle
}
return null
}
}

View file

@ -1,7 +1,10 @@
package org.amnezia.vpn
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
@ -16,6 +19,7 @@ import android.os.Process
import androidx.annotation.MainThread
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@ -26,6 +30,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@ -39,14 +44,11 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.Status
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.cloak.Cloak
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log
@ -57,12 +59,16 @@ import org.json.JSONObject
private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val MSG_VPN_CONFIG = "VPN_CONFIG"
const val MSG_ERROR = "ERROR"
const val MSG_SAVE_LOGS = "SAVE_LOGS"
const val MSG_CLIENT_NAME = "CLIENT_NAME"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
private const val NOTIFICATION_ID = 1337
private const val STATISTICS_SENDING_TIMEOUT = 1000L
private const val DISCONNECT_TIMEOUT = 5000L
@ -76,6 +82,8 @@ class AmneziaVpnService : VpnService() {
private var protocol: Protocol? = null
private val protocolCache = mutableMapOf<String, Protocol>()
private var protocolState = MutableStateFlow(UNKNOWN)
private var serverName: String? = null
private var serverIndex: Int = -1
private val isConnected
get() = protocolState.value == CONNECTED
@ -89,8 +97,11 @@ class AmneziaVpnService : VpnService() {
private var connectionJob: Job? = null
private var disconnectionJob: Job? = null
private var statisticsSendingJob: Job? = null
private lateinit var clientMessenger: IpcMessenger
private lateinit var networkState: NetworkState
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
private val isActivityConnected
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
protocolState.value = DISCONNECTED
@ -116,13 +127,22 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Handle action: $action")
when (action) {
Action.REGISTER_CLIENT -> {
clientMessenger.set(msg.replyTo)
val clientName = msg.data.getString(MSG_CLIENT_NAME)
val messenger = IpcMessenger(msg.replyTo, clientName)
clientMessengers[msg.replyTo] = messenger
Log.d(TAG, "Messenger client '$clientName' was registered")
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
}
Action.UNREGISTER_CLIENT -> {
clientMessengers.remove(msg.replyTo)?.let {
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
}
}
Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
connect(msg.data.getString(MSG_VPN_CONFIG))
}
Action.DISCONNECT -> {
@ -130,17 +150,17 @@ class AmneziaVpnService : VpnService() {
}
Action.REQUEST_STATUS -> {
clientMessenger.send {
ServiceEvent.STATUS.packToMessage {
putStatus(Status.build {
setState(this@AmneziaVpnService.protocolState.value)
})
clientMessengers[msg.replyTo]?.let { clientMessenger ->
clientMessenger.send {
ServiceEvent.STATUS.packToMessage {
putStatus(this@AmneziaVpnService.protocolState.value)
}
}
}
}
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
}
}
}
@ -189,7 +209,7 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client")
loadServerData()
launchProtocolStateHandler()
networkState = NetworkState(this, ::reconnect)
}
@ -201,15 +221,13 @@ class AmneziaVpnService : VpnService() {
if (isAlwaysOnCompat) {
Log.d(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY))
connect()
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.d(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY))
connect()
} else {
Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
}
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
return START_REDELIVER_INTENT
@ -219,17 +237,16 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onBind by $intent")
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
isServiceBound = true
if (isConnected) launchSendingStatistics()
return vpnServiceMessenger.binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind by $intent")
if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = false
stopSendingStatistics()
clientMessenger.reset()
if (isUnknown || isDisconnected) stopService()
if (clientMessengers.isEmpty()) {
isServiceBound = false
if (isUnknown || isDisconnected) stopService()
}
}
return true
}
@ -238,7 +255,6 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onRebind by $intent")
if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = true
if (isConnected) launchSendingStatistics()
}
super.onRebind(intent)
}
@ -278,17 +294,16 @@ class AmneziaVpnService : VpnService() {
*/
private fun launchProtocolStateHandler() {
mainScope.launch {
protocolState.collect { protocolState ->
// drop first default UNKNOWN state
protocolState.drop(1).collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) {
CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED)
networkState.bindNetworkListener()
if (isServiceBound) launchSendingStatistics()
if (isActivityConnected) launchSendingStatistics()
}
DISCONNECTED -> {
clientMessenger.send(ServiceEvent.DISCONNECTED)
networkState.unbindNetworkListener()
stopSendingStatistics()
if (!isServiceBound) stopService()
@ -300,12 +315,19 @@ class AmneziaVpnService : VpnService() {
}
RECONNECTING -> {
clientMessenger.send(ServiceEvent.RECONNECTING)
stopSendingStatistics()
}
CONNECTING, UNKNOWN -> {}
}
clientMessengers.send {
ServiceEvent.STATUS_CHANGED.packToMessage {
putStatus(protocolState)
}
}
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
}
}
}
@ -332,7 +354,17 @@ class AmneziaVpnService : VpnService() {
}
@MainThread
private fun connect(vpnConfig: String?) {
private fun connect(vpnConfig: String? = null) {
if (vpnConfig == null) {
connectToVpn(Prefs.load(PREFS_CONFIG_KEY))
} else {
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connectToVpn(vpnConfig)
}
}
@MainThread
private fun connectToVpn(vpnConfig: String) {
if (isConnected || protocolState.value == CONNECTING) return
Log.d(TAG, "Start VPN connection")
@ -340,6 +372,7 @@ class AmneziaVpnService : VpnService() {
protocolState.value = CONNECTING
val config = parseConfigToJson(vpnConfig)
saveServerData(config)
if (config == null) {
onError("Invalid VPN config")
protocolState.value = DISCONNECTED
@ -417,24 +450,38 @@ class AmneziaVpnService : VpnService() {
private fun onError(msg: String) {
Log.e(TAG, msg)
mainScope.launch {
clientMessenger.send {
clientMessengers.send {
ServiceEvent.ERROR.packToMessage {
putString(ERROR_MSG, msg)
putString(MSG_ERROR, msg)
}
}
}
}
private fun parseConfigToJson(vpnConfig: String?): JSONObject? =
try {
vpnConfig?.let {
JSONObject(it)
}
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
private fun parseConfigToJson(vpnConfig: String): JSONObject? =
if (vpnConfig.isBlank()) {
null
} else {
try {
JSONObject(vpnConfig)
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
null
}
}
private fun saveServerData(config: JSONObject?) {
serverName = config?.opt("description") as String?
serverIndex = config?.opt("serverIndex") as Int? ?: -1
Prefs.save(PREFS_SERVER_NAME, serverName)
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
}
private fun loadServerData() {
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
}
private fun checkPermission(): Boolean =
if (prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply {
@ -446,4 +493,12 @@ class AmneziaVpnService : VpnService() {
} else {
true
}
companion object {
fun isRunning(context: Context): Boolean =
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
.runningAppProcesses.any {
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
}
}
}

View file

@ -20,9 +20,7 @@ sealed interface IpcMessage {
}
enum class ServiceEvent : IpcMessage {
CONNECTED,
DISCONNECTED,
RECONNECTING,
STATUS_CHANGED,
STATUS,
STATISTICS_UPDATE,
ERROR
@ -30,6 +28,7 @@ enum class ServiceEvent : IpcMessage {
enum class Action : IpcMessage {
REGISTER_CLIENT,
UNREGISTER_CLIENT,
CONNECT,
DISCONNECT,
REQUEST_STATUS,

View file

@ -9,11 +9,21 @@ import org.amnezia.vpn.util.Log
private const val TAG = "IpcMessenger"
class IpcMessenger(
messengerName: String? = null,
private val onDeadObjectException: () -> Unit = {},
private val onRemoteException: () -> Unit = {},
private val messengerName: String = "Unknown"
private val onRemoteException: () -> Unit = {}
) {
private var messenger: Messenger? = null
val name = messengerName ?: "Unknown"
constructor(
messenger: Messenger,
messengerName: String? = null,
onDeadObjectException: () -> Unit = {},
onRemoteException: () -> Unit = {}
) : this(messengerName, onDeadObjectException, onRemoteException) {
this.messenger = messenger
}
fun set(messenger: Messenger) {
this.messenger = messenger
@ -25,19 +35,29 @@ class IpcMessenger(
fun send(msg: () -> Message) = messenger?.sendMsg(msg())
fun send(msg: Message, replyTo: Messenger) = messenger?.sendMsg(msg.apply { this.replyTo = replyTo })
fun <T> send(msg: T)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
fun <T> send(msg: T, replyTo: Messenger)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage().apply { this.replyTo = replyTo })
private fun Messenger.sendMsg(msg: Message) {
try {
send(msg)
} catch (e: DeadObjectException) {
Log.w(TAG, "$messengerName messenger is dead")
Log.w(TAG, "$name messenger is dead")
messenger = null
onDeadObjectException()
} catch (e: RemoteException) {
Log.w(TAG, "Sending a message to the $messengerName messenger failed: ${e.message}")
Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}")
onRemoteException()
}
}
}
fun Map<Messenger, IpcMessenger>.send(msg: () -> Message) = this.values.forEach { it.send(msg) }
fun <T> Map<Messenger, IpcMessenger>.send(msg: T)
where T : Enum<T>, T : IpcMessage = this.values.forEach { it.send(msg) }

View file

@ -0,0 +1,75 @@
package org.amnezia.vpn
import android.app.Application
import androidx.datastore.core.MultiProcessDataStoreFactory
import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.OutputStream
import java.io.Serializable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.util.Log
private const val TAG = "VpnState"
private const val STORE_FILE_NAME = "vpnState"
data class VpnState(
val protocolState: ProtocolState,
val serverName: String? = null,
val serverIndex: Int = -1
) : Serializable {
companion object {
private const val serialVersionUID: Long = -1760654961004181606
val defaultState: VpnState = VpnState(DISCONNECTED)
}
}
object VpnStateStore {
private lateinit var app: Application
private val dataStore = MultiProcessDataStoreFactory.create(
serializer = VpnStateSerializer(),
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
)
fun init(app: Application) {
Log.v(TAG, "Init VpnStateStore")
this.app = app
}
fun dataFlow(): Flow<VpnState> = dataStore.data
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
try {
dataStore.updateData(f)
} catch (e : Exception) {
Log.e(TAG, "Failed to store VpnState: $e")
}
}
}
private class VpnStateSerializer : Serializer<VpnState> {
override val defaultValue: VpnState = VpnState.defaultState
override suspend fun readFrom(input: InputStream): VpnState {
return withContext(Dispatchers.IO) {
ObjectInputStream(input).use {
it.readObject() as VpnState
}
}
}
override suspend fun writeTo(t: VpnState, output: OutputStream) {
withContext(Dispatchers.IO) {
ObjectOutputStream(output).use {
it.writeObject(t)
}
}
}
}

View file

@ -1,18 +1,23 @@
package org.amnezia.vpn.qt
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.Status
/**
* JNI functions of the AndroidController class from android_controller.cpp,
* called by events in the Android part of the client
*/
object QtAndroidController {
fun onStatus(status: Status) = onStatus(status.state)
fun onStatus(protocolState: ProtocolState) = onStatus(protocolState.ordinal)
external fun onStatus(stateCode: Int)
external fun onServiceDisconnected()
external fun onServiceError()
external fun onVpnPermissionRejected()
external fun onVpnConnected()
external fun onVpnDisconnected()
external fun onVpnReconnecting()
external fun onVpnStateChanged(stateCode: Int)
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String)

View file

@ -1,10 +0,0 @@
package org.amnezia.vpn.protocol.wireguard
object GoBackend {
external fun wgGetConfig(handle: Int): String?
external fun wgGetSocketV4(handle: Int): Int
external fun wgGetSocketV6(handle: Int): Int
external fun wgTurnOff(handle: Int)
external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun wgVersion(): String
}

View file

@ -0,0 +1,10 @@
package org.amnezia.awg
object GoBackend {
external fun awgGetConfig(handle: Int): String?
external fun awgGetSocketV4(handle: Int): Int
external fun awgGetSocketV6(handle: Int): Int
external fun awgTurnOff(handle: Int)
external fun awgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun awgVersion(): String
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.net.VpnService.Builder
import java.util.TreeMap
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.awg.GoBackend
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
@ -61,7 +62,7 @@ open class Wireguard : Protocol() {
override val statistics: Statistics
get() {
if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS
val config = GoBackend.wgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
val config = GoBackend.awgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
return Statistics.build {
var optsCount = 0
config.splitToSequence("\n").forEach { line ->
@ -92,12 +93,12 @@ open class Wireguard : Protocol() {
val configDataJson = config.getJSONObject("wireguard_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return WireguardConfig.build {
configWireguard(configData)
configWireguard(configData, configDataJson)
configSplitTunneling(config)
}
}
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
@ -118,7 +119,14 @@ open class Wireguard : Protocol() {
if (routes.any { it !in defRoutes }) disableSplitTunneling()
addRoutes(routes)
configData["MTU"]?.let { setMtu(it.toInt()) }
configDataJson.optString("mtu").let { mtu ->
if (mtu.isNotEmpty()) {
setMtu(mtu.toInt())
} else {
configData["MTU"]?.let { setMtu(it.toInt()) }
}
}
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
@ -149,8 +157,8 @@ open class Wireguard : Protocol() {
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}")
tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
Log.v(TAG, "Wg-go backend ${GoBackend.awgVersion()}")
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
}
if (tunnelHandle < 0) {
@ -158,8 +166,8 @@ open class Wireguard : Protocol() {
throw VpnStartException("Wireguard tunnel creation error")
}
if (!protect(GoBackend.wgGetSocketV4(tunnelHandle)) || !protect(GoBackend.wgGetSocketV6(tunnelHandle))) {
GoBackend.wgTurnOff(tunnelHandle)
if (!protect(GoBackend.awgGetSocketV4(tunnelHandle)) || !protect(GoBackend.awgGetSocketV6(tunnelHandle))) {
GoBackend.awgTurnOff(tunnelHandle)
tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
}
@ -172,7 +180,7 @@ open class Wireguard : Protocol() {
}
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.wgTurnOff(handleToClose)
GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED
}

View file

@ -45,13 +45,12 @@ foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libredsocks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libsslocal.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libtun2socks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libssl_3.so
)
endforeach()

View file

@ -107,6 +107,7 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
)
target_sources(${PROJECT} PRIVATE

View file

@ -41,6 +41,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().
value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson();
}

View file

@ -76,7 +76,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode)
*errorCode = ErrorCode::SshSftpFailureError;
*errorCode = ErrorCode::SshScpFailureError;
}
return connData;

View file

@ -176,7 +176,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::SftpOverwriteMode::SftpAppendToExisting);
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (e) {
if (errorCode)
@ -212,6 +212,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig;
jConfig[config_key::config] = config;
@ -223,6 +224,8 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
clientId = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();

View file

@ -118,7 +118,7 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode)
libssh::ScpOverwriteMode overwriteMode)
{
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
@ -139,7 +139,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e)
return e;
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)),
@ -147,7 +147,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e)
return e;
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)),
@ -199,7 +199,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
const QString &remotePath, libssh::ScpOverwriteMode overwriteMode)
{
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
@ -211,7 +211,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data);
localFile.close();
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
if (error != ErrorCode::NoError) {
return error;
@ -359,7 +359,33 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::Awg) {
return true;
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
!= newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize))
|| (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)
!= newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize))
|| (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)
!= newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize))
|| (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)
!= newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize))
|| (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)
!= newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader))
|| (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
return true;
}
if (container == DockerContainer::WireGuard){
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
return true;
}
return false;

View file

@ -38,7 +38,7 @@ public:
ErrorCode uploadTextFileToContainer(
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode = nullptr);
@ -82,7 +82,7 @@ private:
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials);

View file

@ -46,25 +46,12 @@ namespace amnezia
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Ssh sftp errors
SshSftpEofError = 400,
SshSftpNoSuchFileError = 401,
SshSftpPermissionDeniedError = 402,
SshSftpFailureError = 403,
SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Ssh scp errors
SshScpFailureError = 400,
// Local errors
OpenVpnConfigMissing = 500,
OpenVpnManagementServerError = 501,
ConfigMissing = 502,
// Distro errors
OpenVpnExecutableMissing = 600,
@ -92,7 +79,15 @@ namespace amnezia
// Api errors
ApiConfigDownloadError = 1100,
ApiConfigAlreadyAdded = 1101
ApiConfigAlreadyAdded = 1101,
// QFile errors
OpenError = 1200,
ReadError = 1201,
PermissionsError = 1202,
UnspecifiedError = 1203,
FatalError = 1204,
AbortError = 1205
};
} // namespace amnezia

View file

@ -28,20 +28,8 @@ QString errorString(ErrorCode code) {
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
// Libssh sftp errors
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Ssh scp errors
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
// Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
@ -68,6 +56,14 @@ QString errorString(ErrorCode code) {
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
// QFile errors
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
case(InternalError):
default:
errorMessage = QObject::tr("Internal error"); break;

View file

@ -10,16 +10,10 @@ const uint32_t S_IRWXU = 0644;
#endif
namespace libssh {
const QString libsshTimeoutError = "Timeout connecting to";
constexpr auto libsshTimeoutError{"Timeout connecting to"};
std::function<QString()> Client::m_passphraseCallback;
Client::Client(QObject *parent) : QObject(parent)
{ }
Client::~Client()
{ }
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
{
auto passphrase = m_passphraseCallback();
@ -171,13 +165,13 @@ namespace libssh {
return ErrorCode::NoError;
};
auto error = readOutput(false);
if (error != ErrorCode::NoError) {
return error;
auto errorCode = readOutput(false);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
error = readOutput(true);
if (error != ErrorCode::NoError) {
return error;
errorCode = readOutput(true);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
} else {
return closeChannel();
@ -222,100 +216,79 @@ namespace libssh {
return fromLibsshErrorCode();
}
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
{
m_sftpSession = sftp_new(m_session);
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
if (m_sftpSession == nullptr) {
return closeSftpSession();
if (m_scpSession == nullptr) {
return fromLibsshErrorCode();
}
int result = sftp_init(m_sftpSession);
if (result != SSH_OK) {
return closeSftpSession();
if (ssh_scp_init(m_scpSession) != SSH_OK) {
auto errorCode = fromLibsshErrorCode();
closeScpSession();
return errorCode;
}
QFutureWatcher<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished);
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
int accessType = O_WRONLY | O_CREAT | overwriteMode;
sftp_file file;
const size_t bufferSize = 16384;
char buffer[bufferSize];
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
const int localFileSize = QFileInfo(localPath).size();
file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU);
if (file == nullptr) {
return closeSftpSession();
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType);
if (result != SSH_OK) {
return fromLibsshErrorCode();
}
int localFileSize = QFileInfo(localPath).size();
int chunksCount = localFileSize / (bufferSize);
QFile fin(localPath);
if (fin.open(QIODevice::ReadOnly)) {
for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) {
QByteArray chunk = fin.read(bufferSize);
if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError;
constexpr size_t bufferSize = 16384;
int transferred = 0;
int currentChunkSize = bufferSize;
int bytesWritten = sftp_write(file, chunk.data(), chunk.size());
while (transferred < localFileSize) {
if (bytesWritten != chunk.size()) {
fin.close();
sftp_close(file);
return closeSftpSession();
// Last Chunk
if ((localFileSize - transferred) < bufferSize) {
currentChunkSize = localFileSize % bufferSize;
}
}
int lastChunkSize = localFileSize % bufferSize;
if (lastChunkSize != 0) {
QByteArray lastChunk = fin.read(lastChunkSize);
if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError;
int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize);
if (bytesWritten != lastChunkSize) {
fin.close();
sftp_close(file);
return closeSftpSession();
QByteArray chunk = fin.read(currentChunkSize);
if (chunk.size() != currentChunkSize) {
return fromFileErrorCode(fin.error());
}
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size());
if (result != SSH_OK) {
return fromLibsshErrorCode();
}
transferred += currentChunkSize;
}
} else {
sftp_close(file);
return closeSftpSession();
return fromFileErrorCode(fin.error());
}
fin.close();
int result = sftp_close(file);
if (result != SSH_OK) {
return closeSftpSession();
}
return closeSftpSession();
return ErrorCode::NoError;
});
watcher.setFuture(future);
QEventLoop wait;
QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit);
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec();
closeScpSession();
return watcher.result();
}
ErrorCode Client::closeSftpSession()
void Client::closeScpSession()
{
auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession));
if (m_sftpSession != nullptr) {
sftp_free(m_sftpSession);
m_sftpSession = nullptr;
if (m_scpSession != nullptr) {
ssh_scp_free(m_scpSession);
m_scpSession = nullptr;
}
qCritical() << ssh_get_error(m_session);
return errorCode;
}
ErrorCode Client::fromLibsshErrorCode()
@ -337,24 +310,17 @@ namespace libssh {
default: return ErrorCode::SshInternalError;
}
}
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
{
switch (errorCode) {
case(SSH_FX_OK): return ErrorCode::NoError;
case(SSH_FX_EOF): return ErrorCode::SshSftpEofError;
case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError;
case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError;
case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError;
case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError;
case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError;
case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError;
case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError;
case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError;
case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError;
case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError;
case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError;
case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError;
default: return ErrorCode::SshSftpFailureError;
switch (fileError) {
case QFileDevice::NoError: return ErrorCode::NoError;
case QFileDevice::ReadError: return ErrorCode::ReadError;
case QFileDevice::OpenError: return ErrorCode::OpenError;
case QFileDevice::PermissionsError: return ErrorCode::PermissionsError;
case QFileDevice::FatalError: return ErrorCode::FatalError;
case QFileDevice::AbortError: return ErrorCode::AbortError;
default: return ErrorCode::UnspecifiedError;
}
}

View file

@ -2,29 +2,29 @@
#define SSHCLIENT_H
#include <QObject>
#include <QFile>
#include <fcntl.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include "defs.h"
using namespace amnezia;
namespace libssh {
enum SftpOverwriteMode {
enum ScpOverwriteMode {
/*! Overwrite any existing files */
SftpOverwriteExisting = O_TRUNC,
ScpOverwriteExisting = O_TRUNC,
/*! Append new content if the file already exists */
SftpAppendToExisting = O_APPEND
ScpAppendToExisting = O_APPEND
};
class Client : public QObject
{
Q_OBJECT
public:
Client(QObject *parent = nullptr);
~Client();
Client() = default;
~Client() = default;
ErrorCode connectToHost(const ServerCredentials &credentials);
void disconnectFromHost();
@ -32,26 +32,26 @@ namespace libssh {
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
ErrorCode writeResponse(const QString &data);
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
ErrorCode scpFileCopy(const ScpOverwriteMode overwriteMode,
const QString &localPath,
const QString &remotePath,
const QString& fileDesc);
const QString &fileDesc);
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
private:
ErrorCode closeChannel();
ErrorCode closeSftpSession();
void closeScpSession();
ErrorCode fromLibsshErrorCode();
ErrorCode fromLibsshSftpErrorCode(int errorCode);
ErrorCode fromFileErrorCode(QFileDevice::FileError fileError);
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
ssh_session m_session = nullptr;
ssh_channel m_channel = nullptr;
sftp_session m_sftpSession = nullptr;
ssh_scp m_scpSession = nullptr;
static std::function<QString()> m_passphraseCallback;
signals:
void writeToChannelFinished();
void sftpFileCopyFinished();
void scpFileCopyFinished();
};
}

View file

@ -251,6 +251,19 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
GETVALUE("serverPskKey", config.m_serverPskKey, String);
GETVALUE("serverPort", config.m_serverPort, Double);
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
{
config.m_deviceMTU = 1420;
} else {
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
#ifdef Q_OS_WINDOWS
// For Windows min MTU value is 1280 (the smallest MTU legal with IPv6).
if (config.m_deviceMTU < 1280) {
config.m_deviceMTU = 1280;
}
#endif
}
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
if (config.m_deviceIpv4Address.isNull() &&

View file

@ -23,6 +23,7 @@ QJsonObject InterfaceConfig::toJson() const {
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
json.insert("serverPort", QJsonValue((double)m_serverPort));
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
(m_hopType == InterfaceConfig::SingleHop)) {
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
@ -85,8 +86,13 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
if (addresses.isEmpty()) {
return "";
}
out << "Address = " << addresses.join(", ") << "\n";
if (m_deviceMTU) {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_dnsServer.isNull()) {
QStringList dnsServers(m_dnsServer);
// If the DNS is not the Gateway, it's a user defined DNS

View file

@ -33,6 +33,7 @@ class InterfaceConfig {
QString m_serverIpv6AddrIn;
QString m_dnsServer;
int m_serverPort = 0;
int m_deviceMTU = 1420;
QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps;

View file

@ -26,9 +26,10 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(ASFW_ANY);
#endif
// QTBUG-95974 QTBUG-95764 QTBUG-102168
#ifdef Q_OS_ANDROID
// QTBUG-95974 QTBUG-95764 QTBUG-102168
qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1");
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@ -61,7 +62,7 @@ int main(int argc, char *argv[])
if (doExec) {
app.init();
qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION);
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
return app.exec();

View file

@ -132,8 +132,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("deviceMTU", wgConfig.value(amnezia::config_key::mtu));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));

View file

@ -56,26 +56,10 @@ AndroidController::AndroidController() : QObject()
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnConnected, this,
[this]() {
qDebug() << "Android event: VPN connected";
emit connectionStateChanged(Vpn::ConnectionState::Connected);
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnDisconnected, this,
[this]() {
qDebug() << "Android event: VPN disconnected";
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnReconnecting, this,
[this]() {
qDebug() << "Android event: VPN reconnecting";
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
this, &AndroidController::vpnStateChanged, this,
[this](AndroidController::ConnectionState state) {
qDebug() << "Android event: VPN state changed:" << textConnectionState(state);
emit connectionStateChanged(convertState(state));
},
Qt::QueuedConnection);
@ -106,9 +90,7 @@ bool AndroidController::initialize()
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
{"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)},
@ -158,6 +140,11 @@ void AndroidController::stop()
callActivityMethod("stop", "()V");
}
void AndroidController::resetLastServer(int serverIndex)
{
callActivityMethod("resetLastServer", "(I)V", serverIndex);
}
void AndroidController::saveFile(const QString &fileName, const QString &data)
{
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
@ -217,6 +204,11 @@ void AndroidController::clearLogs()
callActivityMethod("clearLogs", "()V");
}
void AndroidController::setScreenshotsEnabled(bool enabled)
{
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
@ -370,30 +362,14 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
}
// static
void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz)
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnConnected();
}
auto state = ConnectionState(stateCode);
// static
void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnDisconnected();
}
// static
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnReconnecting();
emit AndroidController::instance()->vpnStateChanged(state);
}
// static

View file

@ -20,9 +20,9 @@ public:
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState
{
DISCONNECTED,
CONNECTED,
CONNECTING,
DISCONNECTED,
DISCONNECTING,
RECONNECTING,
UNKNOWN
@ -30,6 +30,7 @@ public:
ErrorCode start(const QJsonObject &vpnConfig);
void stop();
void resetLastServer(int serverIndex);
void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
@ -38,6 +39,7 @@ public:
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
void setScreenshotsEnabled(bool enabled);
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@ -48,9 +50,7 @@ signals:
void serviceDisconnected();
void serviceError();
void vpnPermissionRejected();
void vpnConnected();
void vpnDisconnected();
void vpnReconnecting();
void vpnStateChanged(ConnectionState state);
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri);
void configImported(QString config);
@ -77,9 +77,7 @@ private:
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
static void onServiceError(JNIEnv *env, jobject thiz);
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
static void onVpnConnected(JNIEnv *env, jobject thiz);
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);

View file

@ -38,7 +38,7 @@ struct Log {
init(_ str: String) {
self.records = str.split(whereSeparator: \.isNewline)
.compactMap {
Record(String($0))!
Record(String($0))
}
}

View file

@ -1,4 +1,5 @@
import Foundation
import NetworkExtension
public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
let qtLog = Log(String(describing: qtString))
@ -24,3 +25,26 @@ public func swiftDeleteLog() {
public func toggleLogging(_ isEnabled: Bool) {
Log.isLoggingEnabled = isEnabled
}
public func clearSettings() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let error {
NSLog("clearSettings removeFromPreferences error: \(error.localizedDescription)")
return
}
managers?.forEach { manager in
manager.removeFromPreferences { error in
if let error {
NSLog("NE removeFromPreferences error: \(error.localizedDescription)")
} else {
manager.loadFromPreferences { error in
if let error {
NSLog("NE loadFromPreferences after remove error: \(error.localizedDescription)")
}
}
}
}
}
}
}

View file

@ -3,7 +3,6 @@
#include <QFile>
UIView *_screen;
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
@ -15,19 +14,6 @@ UIView *_screen;
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
@ -41,17 +27,6 @@ UIView *_screen;
NSLog(@"In the foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[_screen removeFromSuperview];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// We will add content here soon.
NSLog(@"In the completionHandler");

View file

@ -0,0 +1,87 @@
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
var keyWindows: [UIWindow] {
connectedScenes
.compactMap {
if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow
} else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
}
}
}
}
class ScreenProtection {
public static let shared = ScreenProtection()
var pairs = [ProtectionPair]()
private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation?
public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
view.subviews.forEach {
self.pairs.append(ProtectionPair(from: $0))
}
}
}
public func disable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.pairs.forEach {
$0.removeProtection()
}
self.pairs.removeAll()
}
}
}
struct ProtectionPair {
let textField: UITextField
let layer: CALayer
init(from view: UIView) {
let secureTextField = UITextField()
secureTextField.backgroundColor = .clear
secureTextField.translatesAutoresizingMaskIntoConstraints = false
secureTextField.isSecureTextEntry = true
view.insertSubview(secureTextField, at: 0)
secureTextField.isUserInteractionEnabled = false
view.layer.superlayer?.addSublayer(secureTextField.layer)
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
self.init(textField: secureTextField, layer: view.layer)
}
init(textField: UITextField, layer: CALayer) {
self.textField = textField
self.layer = layer
}
func removeProtection() {
textField.superview?.superview?.layer.addSublayer(layer)
textField.layer.removeFromSuperlayer()
textField.removeFromSuperview()
}
}

View file

@ -7,6 +7,7 @@ struct WGConfig: Decodable {
let initPacketJunkSize, responsePacketJunkSize: String?
let dns1: String
let dns2: String
let mtu: String
let hostName: String
let port: Int
let clientIP: String
@ -25,6 +26,7 @@ struct WGConfig: Decodable {
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case dns1
case dns2
case mtu
case hostName
case port
case clientIP = "client_ip"
@ -58,6 +60,7 @@ struct WGConfig: Decodable {
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = \(clientPrivateKey)
\(settings)
[Peer]
@ -74,6 +77,7 @@ struct WGConfig: Decodable {
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = ***
\(settings)
[Peer]
@ -88,10 +92,11 @@ struct WGConfig: Decodable {
struct OpenVPNConfig: Decodable {
let config: String
let mtu: String
let splitTunnelType: Int
let splitTunnelSites: [String]
var str: String {
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) mtu: \(mtu) config: \(config)"
}
}

View file

@ -358,6 +358,13 @@ bool IosController::setupOpenVPN()
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
@ -410,7 +417,12 @@ bool IosController::setupCloak()
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
@ -433,6 +445,13 @@ bool IosController::setupWireGuard()
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::wireguard::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
@ -449,21 +468,18 @@ bool IosController::setupWireGuard()
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips)) {
QJsonArray allowed_ips;
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
for(int index = 0; index < allowed_ips_list.length(); index++) {
allowed_ips.append(allowed_ips_list[index]);
}
wgConfig.insert(config_key::allowed_ips, allowed_ips);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
wgConfig.insert("persistent_keep_alive", "25");
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@ -478,6 +494,13 @@ bool IosController::setupAwg()
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::awg::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
@ -494,21 +517,19 @@ bool IosController::setupAwg()
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips)) {
QJsonArray allowed_ips;
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
for(int index = 0; index < allowed_ips_list.length(); index++) {
allowed_ips.append(allowed_ips_list[index]);
}
wgConfig.insert(config_key::allowed_ips, allowed_ips);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
wgConfig.insert("persistent_keep_alive", "25");
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);

View file

@ -21,7 +21,7 @@
}
- (void) vpnConfigurationDidChange:(NSNotification *)notification {
cppController->vpnStatusDidChange(notification);
// cppController->vpnStatusDidChange(notification);
}

View file

@ -16,9 +16,6 @@
#include "leakdetector.h"
#include "logger.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsLinux");
}
@ -38,8 +35,6 @@ bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
}
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config);
// Create socket file descriptor to perform the ioctl operations on
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) {
@ -56,10 +51,10 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
// FIXME: We need to know how many layers deep this particular
// interface is into a tunnel to work effectively. Otherwise
// we will run into fragmentation issues.
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU -- Return code: " << ret;
logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false;
}

View file

@ -20,9 +20,6 @@
#include "logger.h"
#include "macosdaemon.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsMacos");
}
@ -56,10 +53,10 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
// MTU
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU:" << strerror(errno);
logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false;
}

View file

@ -58,7 +58,6 @@ void WindowsTunnelService::stop() {
if (m_logworker) {
m_logthread.quit();
m_logthread.wait();
delete m_logworker;
m_logworker = nullptr;
}
}
@ -104,6 +103,7 @@ bool WindowsTunnelService::start(const QString& configData) {
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
m_logworker->moveToThread(&m_logthread);
connect(&m_logthread, &QThread::finished, m_logworker, &QObject::deleteLater);
m_logthread.start();
SC_HANDLE scm = (SC_HANDLE)m_scm;

View file

@ -20,6 +20,7 @@ namespace amnezia
constexpr char dns1[] = "dns1";
constexpr char dns2[] = "dns2";
constexpr char serverIndex[] = "serverIndex";
constexpr char description[] = "description";
constexpr char name[] = "name";
constexpr char cert[] = "cert";
@ -44,7 +45,9 @@ namespace amnezia
constexpr char server_priv_key[] = "server_priv_key";
constexpr char server_pub_key[] = "server_pub_key";
constexpr char psk_key[] = "psk_key";
constexpr char mtu[] = "mtu";
constexpr char allowed_ips[] = "allowed_ips";
constexpr char persistent_keep_alive[] = "persistent_keep_alive";
constexpr char client_ip[] = "client_ip"; // internal ip address
@ -102,6 +105,7 @@ namespace amnezia
constexpr char defaultSubnetAddress[] = "10.8.0.0";
constexpr char defaultSubnetMask[] = "255.255.255.0";
constexpr char defaultSubnetCidr[] = "24";
constexpr char defaultMtu[] = "1500";
constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf";
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
@ -148,6 +152,7 @@ namespace amnezia
constexpr char defaultSubnetCidr[] = "24";
constexpr char defaultPort[] = "51820";
constexpr char defaultMtu[] = "1420";
constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key";
constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
@ -166,6 +171,7 @@ namespace amnezia
namespace awg
{
constexpr char defaultPort[] = "55424";
constexpr char defaultMtu[] = "1420";
constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";

View file

@ -224,6 +224,7 @@
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>images/controls/close.svg</file>
<file>images/controls/search.svg</file>
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>images/controls/split-tunneling.svg</file>
<file>ui/qml/Controls2/DrawerType2.qml</file>

View file

@ -68,6 +68,7 @@ void Settings::removeServer(int index)
servers.removeAt(index);
setServersArray(servers);
emit serverRemoved(index);
}
bool Settings::editServer(int index, const QJsonObject &server)
@ -338,6 +339,7 @@ QString Settings::secondaryDns() const
void Settings::clearSettings()
{
m_settings.clearSettings();
emit settingsCleared();
}
ServerCredentials Settings::defaultServerCredentials() const

View file

@ -185,12 +185,16 @@ public:
void setScreenshotsEnabled(bool enabled)
{
setValue("Conf/screenshotsEnabled", enabled);
emit screenshotsEnabledChanged(enabled);
}
void clearSettings();
signals:
void saveLogsChanged(bool enabled);
void screenshotsEnabledChanged(bool enabled);
void serverRemoved(int serverIndex);
void settingsCleared();
private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;

View file

@ -5,45 +5,45 @@
<name>ConnectionController</name>
<message>
<location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source>
<translation>اتصل</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed.
Please install VPN container at first</source>
<translation>لم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source>
<translation>اتصال...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source>
<translation>تم الاتصال</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source>
<translation>إعادة الاتصال...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source>
<translation>إنهاء الاتصال...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source>
<translation>تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source>
<translation>تم تحديث الاعدادات بنجاح</translation>
</message>
@ -324,17 +324,17 @@ Already installed containers were found on the server. All installed containers
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source>
<translation>بروتوكول VPN</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source>
<translation>الخوادم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source>
<translation>لا يمكن تغير الخادم بينما هناك اتصال مفعل</translation>
</message>
@ -1423,7 +1423,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source>
<source>Do you want to clear server Amnezia-installed services?</source>
<translation>هل تريد حذف الخادم من Amnezia?</translation>
</message>
<message>
@ -1471,7 +1471,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source>
<source>Remove this server from the app</source>
<translation>احذف خادم من التطبيق</translation>
</message>
<message>
@ -1496,7 +1496,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source>
<source>Clear server Amnezia-installed services</source>
<translation>احذف خادم من Amnezia</translation>
</message>
<message>
@ -1703,41 +1703,45 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation>اتصال الخادم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك
<translation type="vanished">لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك
لا بأس طالما انه من شخص تثق به.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source>
<translation>ماذا لديك؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source>
<translation>ملف إعدادات اتصال او نسخ احتياطي</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source>
<translation>ملف إعدادات اتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source>
<translation>افتح ملف تكوين</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source>
<translation>رمز QR</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source>
<translation>مفتاح كنص</translation>
</message>
@ -1823,7 +1827,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>واصل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source>
<translation>إعداد في وقت لاحق</translation>
</message>
@ -1924,32 +1928,32 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<context>
<name>PageSetupWizardStart</name>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source>
<translation>تم استرداد الإعدادات من ملف نسخة احتياطية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source>
<translation>خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation> يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source>
<translation>لدي البيانات المطلوبة للأتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source>
<translation>ليس لدي اي شئ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation></translation>
</message>
@ -1991,9 +1995,13 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>اتصال جديد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation>
<translation type="vanished">لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@ -2085,7 +2093,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source>
<translation>المستخدمين</translation>
</message>
@ -2095,52 +2103,52 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>شارك اتصال VPN بدون القدرة علي إدارة الخادم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source>
<translation>ابحث</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source>
<translation>تاريخ الإنشاء: </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source>
<translation>إعادة التسمية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source>
<translation>اسم العميل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source>
<translation>احفظ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source>
<translation>سحب وإبطال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source>
<translation>سحب وإبطال للمستخدم - %1?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source>
<translation>المستخدم لن يكون قادر علي الاتصال بعد الان.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source>
<translation>واصل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source>
<translation>إلغاء</translation>
</message>
@ -2173,8 +2181,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation type="obsolete">البروتوكولات</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source>
<translation>بروتوكول</translation>
</message>
@ -2194,14 +2202,14 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>اسم المستخدم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source>
<translation>تنسيق الاتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source>
<translation>شارك</translation>
</message>
@ -2583,147 +2591,161 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>انتهت مدة الاتصال بالخادم</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Sftp error: End-of-file encountered</source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source>
<translation>خطأ Sftp: الملف غير موجود</translation>
<translation type="vanished">خطأ Sftp: الملف غير موجود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source>
<translation>خطأ Sftp: تم حظر الصلحيات</translation>
<translation type="vanished">خطأ Sftp: تم حظر الصلحيات</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source>
<translation>خطأ Sftp: فشل عام</translation>
<translation type="vanished">خطأ Sftp: فشل عام</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source>
<translation>خطأ Sftp: تم استلام نفايات من الخادم</translation>
<translation type="vanished">خطأ Sftp: تم استلام نفايات من الخادم</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source>
<translation>خطأ Sftp: لم يتم إعداد اتصال</translation>
<translation type="vanished">خطأ Sftp: لم يتم إعداد اتصال</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source>
<translation>خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation>
<translation type="vanished">خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source>
<translation>خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation>
<translation type="vanished">خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source>
<translation>خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation>
<translation type="vanished">خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation>
<translation type="vanished">خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source>
<translation>خطأ Sftp: نظام كتابة الملفات محمي</translation>
<translation type="vanished">خطأ Sftp: نظام كتابة الملفات محمي</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source>
<translation>خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation>
<translation type="vanished">خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="68"/>
<location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source>
<translation>خطأ عند استرداد التكوين من API</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source>
<translation>هذا التكوين بالفعل تمت إضافتة للبرنامج</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="76"/>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="47"/>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source>
<translation>OpenVpn تكوين مفقود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="48"/>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source>
<translation>OpenVpn خطأ في إدارة الخادم</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="51"/>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source>
<translation>OpenVpn executeable مفقود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="52"/>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable مفقود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="53"/>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable مفقود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="54"/>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source>
<translation>خطأ في خدمة مٌساعد Amnezia</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="55"/>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source>
<translation>فشل OpenSSL</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="58"/>
<location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source>
<translation>لا يمكن الاتصال: هناك اتصال VPN اخر بالفعل يعمل</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="59"/>
<location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>لا يمك نتثبيت محول شبكة OpenVPN TAP</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="60"/>
<location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source>
<translation>VPN pool error: لا يوجد عنواين مٌتاحة</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source>
<translation>التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="73"/>
<location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source>
<translation>خطأ داخلي</translation>
</message>
@ -3023,8 +3045,8 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation>خادم #1</translation>
</message>
<message>
<location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="213"/>
<location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="214"/>
<source>Server</source>
<translation>خادم</translation>
</message>
@ -3032,17 +3054,17 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>SettingsController</name>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source>
<translation>ملف النسخه الاحتياطيه تالف</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source>
<translation>تم استرجاع جميع الإعدادات للإعدادات الافتراضية</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source>
<translation>تم حذف الملفات الشخصية المٌخزنة مؤقتاُ</translation>
</message>
@ -3056,28 +3078,28 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation>احفظ تكوين AmneziaVPN</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source>
<translation>شارك</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source>
<translation>انسخ</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source>
<translation>تم النسخ</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source>
<translation>انسخ نص التكوين</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source>
<translation>اظهر إعدادات الاتصال</translation>
</message>
@ -3086,7 +3108,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation type="obsolete"></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار &quot;إضافة خادم&quot; - &quot;لدي بيانات الاتصال&quot; - &quot;رمز Qr, او مفتاح تعريف او ملف إعدادات&quot;</translation>
</message>
@ -3178,7 +3200,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>VpnConnection</name>
<message>
<location filename="../vpnconnection.cpp" line="457"/>
<location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source>
<translation></translation>
</message>
@ -3262,12 +3284,12 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>main2</name>
<message>
<location filename="../ui/qml/main2.qml" line="174"/>
<location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source>
<translation>عبارة المرور الخاصة بالمفتاح</translation>
</message>
<message>
<location filename="../ui/qml/main2.qml" line="197"/>
<location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source>
<translation>احفظ</translation>
</message>

View file

@ -34,48 +34,48 @@
<context>
<name>ConnectionController</name>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed.
Please install VPN container at first</source>
<translation>پروتکل ویپیان نصب نشده است
لطفا کانتینر ویپیان را نصب کنید</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source>
<translation>در حال ارتباط...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source>
<translation>متصل</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source>
<translation>تنظیمات به روز رسانی شد
در حال اتصال دوباره...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source>
<translation>تنظیمات با موفقیت بهروزرسانی شدند</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source>
<translation>اتصال دوباره...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source>
<translation>اتصال</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source>
<translation>قطع ارتباط...</translation>
</message>
@ -335,17 +335,17 @@ Already installed containers were found on the server. All installed containers
<translation>تونل تقسیمشده غیرفعال شده</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source>
<translation>پروتکل ویپیان</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source>
<translation>سرورها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source>
<translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation>
</message>
@ -1479,7 +1479,7 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source>
<source>Do you want to clear server Amnezia-installed services?</source>
<translation>آیا میخواهید سرور را از نرمافزار Amnezia پاک کنید؟</translation>
</message>
<message>
@ -1494,11 +1494,11 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source>
<source>Remove this server from the app</source>
<translation>حذف کردن سرور از نرمافزار</translation>
</message>
<message>
<source>Remove server?</source>
<source>Remove server from application?</source>
<translation type="vanished">حذف سرور؟</translation>
</message>
<message>
@ -1508,11 +1508,11 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source>
<source>Clear server Amnezia-installed services</source>
<translation>پاک کردن سرور از نرمافزار Amnezia</translation>
</message>
<message>
<source>Clear server from Amnezia software?</source>
<source>Clear server Amnezia-installed services?</source>
<translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation>
</message>
<message>
@ -1715,41 +1715,45 @@ Already installed containers were found on the server. All installed containers
<translation>ارتباط سرور</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند.
<translation type="vanished">از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند.
ایرادی ندارد که از طرف کسی باشد که به او اعتماد دارید.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source>
<translation>چی داری؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source>
<translation>فایل شامل تنظیمات اتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source>
<translation>فایل شامل تنظیمات اتصال یا بکآپ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source>
<translation>باز کردن فایل تنظیمات</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source>
<translation>QR-Code</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source>
<translation>متن شامل کلید</translation>
</message>
@ -1852,7 +1856,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>ادامه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source>
<translation>بعدا تنظیم شود</translation>
</message>
@ -1953,32 +1957,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context>
<name>PageSetupWizardStart</name>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source>
<translation>تنظیمات از فایل بکآپ بازیابی شدند</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source>
<translation>سرویس رایگان برای ایجاد ویپیان شخصی بر روی سرور خودتان.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation>به شما کمک میکند که بدون فاش کردن حریم شخصی خودتان حتی برای ارائه دهنده ویپیان به محتوای مسدود شده دسترسی پیدا کنید.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source>
<translation>من داده برای اتصال دارم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source>
<translation>من هیچی ندارم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
@ -2019,9 +2023,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>ارتباط جدید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
<translation type="vanished">از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@ -2159,7 +2167,7 @@ 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="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source>
<translation>کاربران</translation>
</message>
@ -2169,37 +2177,37 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>نام کاربری</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source>
<translation>جستجو</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source>
<translation>تاریخ ایجاد:</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source>
<translation>تغییر نام</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source>
<translation>نام کلاینت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source>
<translation>ذخیره</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source>
<translation>ابطال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source>
<translation> لغو پیکربندی برای یک کاربر %1؟</translation>
</message>
@ -2208,17 +2216,17 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">ابطال تنظیمات برای کاربر </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source>
<translation>کاربر دیگر نمیتواند به سرور وصل شود.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source>
<translation>ادامه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source>
<translation>کنسل</translation>
</message>
@ -2236,20 +2244,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">به اشتراک گذاری دسترسی به مدیریت سرور. کاربری که دسترسی کامل سرور با او به اشتراک گذاشته میشود میتواند پروتکلها و سرویسها را در سرور حذف یا اضافه کند و یا تنظیمات سرور را تغییر دهد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source>
<translation>پروتکل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source>
<translation>فرمت ارتباط</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source>
<translation>اشتراکگذاری</translation>
</message>
@ -2627,91 +2635,113 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source>
<translation>Sftp error: End-of-file encountered</translation>
<translation type="vanished">Sftp error: End-of-file encountered</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source>
<translation>Sftp error: File does not exist</translation>
<translation type="vanished">Sftp error: File does not exist</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source>
<translation>Sftp error: Permission denied</translation>
<translation type="vanished">Sftp error: Permission denied</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source>
<translation>Sftp error: Generic failure</translation>
<translation type="vanished">Sftp error: Generic failure</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source>
<translation>Sftp error: Garbage received from server</translation>
<translation type="vanished">Sftp error: Garbage received from server</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source>
<translation>Sftp error: No connection has been set up</translation>
<translation type="vanished">Sftp error: No connection has been set up</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp error: There was a connection, but we lost it</translation>
<translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: Operation not supported by libssh yet</translation>
<translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source>
<translation>Sftp error: Invalid file handle</translation>
<translation type="vanished">Sftp error: Invalid file handle</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source>
<translation>Sftp error: No such file or directory path exists</translation>
<translation type="vanished">Sftp error: No such file or directory path exists</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation>
<translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source>
<translation>Sftp error: Write-protected filesystem</translation>
<translation type="vanished">Sftp error: Write-protected filesystem</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source>
<translation>Sftp error: No media was in remote drive</translation>
<translation type="vanished">Sftp error: No media was in remote drive</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source>
<translation>تنظیمات شامل هیچ کانتینر یا اعتبارنامهای برای اتصال به سرور نیست</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="68"/>
<location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="76"/>
<location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source>
<translation type="unfinished"></translation>
</message>
@ -2720,52 +2750,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Failed to save config to disk</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="47"/>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source>
<translation>OpenVPN config missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="48"/>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source>
<translation>OpenVPN management server error</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="51"/>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source>
<translation>OpenVPN executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="52"/>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="53"/>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="54"/>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source>
<translation>Amnezia helper service error</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="55"/>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source>
<translation>OpenSSL failed</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="58"/>
<location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source>
<translation>Can&apos;t connect: another VPN connection is active</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="59"/>
<location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>Can&apos;t setup OpenVPN TAP network adapter</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="60"/>
<location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source>
<translation>VPN pool error: no available addresses</translation>
</message>
@ -2774,7 +2804,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">The config does not contain any containers and credentiaks for connecting to the server</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="73"/>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source>
<translation>Internal error</translation>
</message>
@ -3153,8 +3183,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Server #1</translation>
</message>
<message>
<location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="213"/>
<location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="214"/>
<source>Server</source>
<translation>Server</translation>
</message>
@ -3162,17 +3192,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>SettingsController</name>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source>
<translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source>
<translation>پروفایل ذخیره شده پاک شد</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source>
<translation>فایل بکآپ خراب شده است</translation>
</message>
@ -3186,33 +3216,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>ذخیره تنظیمات AmneziaVPN</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source>
<translation>اشتراکگذاری</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source>
<translation>کپی</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source>
<translation>کپی شد</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source>
<translation>کپیکردن متن تنظیمات</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source>
<translation>نمایش تنظیمات ارتباط</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>برای خواندن QR Code در نرمافزار AmneziaVPN &quot;اضافه کردن سرور&quot; -&gt; &quot;من داده برای اتصال دارم&quot; -&gt; &quot;QR Code، کلید یا فایل تنظیمات&quot;</translation>
</message>
@ -3304,7 +3334,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>VpnConnection</name>
<message>
<location filename="../vpnconnection.cpp" line="457"/>
<location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source>
<translation>Mbps</translation>
</message>
@ -3408,12 +3438,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>main2</name>
<message>
<location filename="../ui/qml/main2.qml" line="174"/>
<location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source>
<translation>عبارت کلید خصوصی</translation>
</message>
<message>
<location filename="../ui/qml/main2.qml" line="197"/>
<location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source>
<translation>ذخیره</translation>
</message>

File diff suppressed because it is too large Load diff

View file

@ -27,47 +27,47 @@
<context>
<name>ConnectionController</name>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed.
Please install VPN container at first</source>
<translation>VPN протоколы не установлены.
Пожалуйста, установите протокол</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source>
<translation>Подключение...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source>
<translation>Подключено</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source>
<translation>Настройки успешно обновлены, Подключение...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source>
<translation type="unfinished">Настройки успешно обновлены.</translation>
<translation>Настройки успешно обновлены.</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source>
<translation>Переподключение...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source>
<translation>Подключиться</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source>
<translation>Отключение...</translation>
</message>
@ -143,7 +143,7 @@
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="32"/>
<source>Split tunneling</source>
<translation type="unfinished">Раздельное VPN-туннелирование</translation>
<translation>Раздельное-туннелирование</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/>
@ -153,33 +153,34 @@
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/>
<source>Split tunneling on the server</source>
<translation type="unfinished"></translation>
<translation>Раздельное-туннелирование на сервере</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="43"/>
<source>Enabled
Can&apos;t be disabled for current server</source>
<translation type="unfinished"></translation>
<translation>Включено.
Не может быть отключено для текущего сервера.</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="62"/>
<source>Site-based split tunneling</source>
<translation type="unfinished">Раздельное туннелирование сайтов</translation>
<translation>Раздельное туннелирование по сайтам</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
<source>Enabled</source>
<translation type="unfinished">Включено</translation>
<translation>Включено</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
<source>Disabled</source>
<translation type="unfinished">Отключено</translation>
<translation>Отключено</translation>
</message>
<message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="79"/>
<source>App-based split tunneling</source>
<translation type="unfinished">Раздельное VPN-туннелирование приложений</translation>
<translation>Раздельное VPN-туннелирование по приложениям</translation>
</message>
</context>
<context>
@ -208,7 +209,7 @@ Can&apos;t be disabled for current server</source>
<location filename="../ui/controllers/installController.cpp" line="149"/>
<source>
Added containers that were already installed on the server</source>
<translation type="unfinished">Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
<translation>Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="213"/>
@ -225,7 +226,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
<translation>Сервер &apos;%1&apos; перезагружен</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
@ -326,17 +327,17 @@ Already installed containers were found on the server. All installed containers
<translation type="unfinished">Раздельное туннелирование выключено</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source>
<translation>VPN протокол</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source>
<translation>Серверы</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source>
<translation>Невозможно изменить сервер при активном соединении</translation>
</message>
@ -931,7 +932,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="56"/>
<source>Support Amnezia</source>
<translation type="unfinished">Поддержите Amnezia</translation>
<translation>Поддержите Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="71"/>
@ -1458,7 +1459,7 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source>
<source>Do you want to clear server Amnezia-installed services?</source>
<translation type="unfinished">Вы хотите очистить сервер от всех сервисов Amnezia?</translation>
</message>
<message>
@ -1473,12 +1474,12 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source>
<source>Remove this server from the app</source>
<translation>Удалить сервер из приложения</translation>
</message>
<message>
<source>Remove server?</source>
<translation type="vanished">Удалить сервер?</translation>
<source>Remove server from application?</source>
<translation type="vanished">Удалить сервер из приложения?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="174"/>
@ -1487,11 +1488,11 @@ Already installed containers were found on the server. All installed containers
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source>
<source>Clear server Amnezia-installed services</source>
<translation>Очистить сервер от протоколов и сервисов Amnezia</translation>
</message>
<message>
<source>Clear server from Amnezia software?</source>
<source>Clear server Amnezia-installed services?</source>
<translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation>
</message>
<message>
@ -1690,41 +1691,45 @@ Already installed containers were found on the server. All installed containers
<translation>Подключение к серверу</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.
<translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.
Всё в порядке, если кодом поделился пользователь, которому вы доверяете.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source>
<translation>Выберите что у вас есть</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source>
<translation>Файл с настройками подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source>
<translation>Файл с настройками подключения или бэкап</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source>
<translation>Открыть файл с конфигурацией</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source>
<translation>QR-код</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source>
<translation>Ключ в виде текста</translation>
</message>
@ -1771,7 +1776,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="46"/>
<source>Configure your server</source>
<translation>Настроить ваш сервер</translation>
<translation>Настроить свой сервер</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="54"/>
@ -1827,7 +1832,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source>
<translation>Настроить позднее</translation>
</message>
@ -1936,32 +1941,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context>
<name>PageSetupWizardStart</name>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source>
<translation>Восстановление настроек из бэкап файла</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source>
<translation>Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation> Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source>
<translation>У меня есть данные для подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source>
<translation>У меня ничего нет</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
@ -2002,9 +2007,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Новое соединение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
<translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@ -2138,7 +2147,7 @@ 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="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source>
<translation type="unfinished">Пользователи</translation>
</message>
@ -2148,52 +2157,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished">Имя пользователя</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source>
<translation type="unfinished">Дата создания</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source>
<translation type="unfinished">Переименовать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source>
<translation type="unfinished">Сохранить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source>
<translation type="unfinished">Отозвать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source>
<translation type="unfinished">Отозвать доступ для пользователя - %1?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished">Пользователь больше не сможет подключаться к вашему серверу</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source>
<translation type="unfinished">Продолжить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source>
<translation type="unfinished">Отменить</translation>
</message>
@ -2207,20 +2216,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Поделиться доступом к VPN, без возможности управления сервером</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source>
<translation>Протокол</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source>
<translation>Формат подключения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source>
<translation>Поделиться</translation>
</message>
@ -2597,86 +2606,78 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source>
<translation>Sftp error: End-of-file encountered</translation>
<translation type="vanished">Sftp error: End-of-file encountered</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source>
<translation>Sftp error: File does not exist</translation>
<translation type="vanished">Sftp error: File does not exist</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source>
<translation>Sftp error: Permission denied</translation>
<translation type="vanished">Sftp error: Permission denied</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source>
<translation>Sftp error: Generic failure</translation>
<translation type="vanished">Sftp error: Generic failure</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source>
<translation>Sftp error: Garbage received from server</translation>
<translation type="vanished">Sftp error: Garbage received from server</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source>
<translation>Sftp error: No connection has been set up</translation>
<translation type="vanished">Sftp error: No connection has been set up</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp error: There was a connection, but we lost it</translation>
<translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: Operation not supported by libssh yet</translation>
<translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source>
<translation>Sftp error: Invalid file handle</translation>
<translation type="vanished">Sftp error: Invalid file handle</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source>
<translation>Sftp error: No such file or directory path exists</translation>
<translation type="vanished">Sftp error: No such file or directory path exists</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation>
<translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source>
<translation>Sftp error: Write-protected filesystem</translation>
<translation type="vanished">Sftp error: Write-protected filesystem</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source>
<translation>Sftp error: No media was in remote drive</translation>
<translation type="vanished">Sftp error: No media was in remote drive</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source>
<translation type="unfinished">Конфиг не содержит контейнеров и учетных данных для подключения к серверу</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="68"/>
<location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source>
<translation type="unfinished"></translation>
<translation>Этот конфиг уже был добавлен в приложение</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="76"/>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source>
<translation type="unfinished"></translation>
</message>
@ -2685,62 +2686,92 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Failed to save config to disk</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="47"/>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source>
<translation>OpenVPN config missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="48"/>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source>
<translation>OpenVPN management server error</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="51"/>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source>
<translation>OpenVPN executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="52"/>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="53"/>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable missing</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="54"/>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source>
<translation>Amnezia helper service error</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="55"/>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source>
<translation>OpenSSL failed</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="58"/>
<location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source>
<translation>Can&apos;t connect: another VPN connection is active</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="59"/>
<location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>Can&apos;t setup OpenVPN TAP network adapter</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="60"/>
<location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source>
<translation>VPN pool error: no available addresses</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="73"/>
<location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source>
<translation>Internal error</translation>
</message>
@ -3077,8 +3108,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Server #1</translation>
</message>
<message>
<location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="213"/>
<location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="214"/>
<source>Server</source>
<translation>Server</translation>
</message>
@ -3086,17 +3117,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>SettingsController</name>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source>
<translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source>
<translation>Кэш профиля очищен</translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source>
<translation>Backup файл поврежден</translation>
</message>
@ -3110,33 +3141,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Сохранить config AmneziaVPN</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source>
<translation>Поделиться</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source>
<translation>Скопировать</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source>
<translation>Скопировано</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source>
<translation type="unfinished">Показать настройки подключения</translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>Для считывания QR-кода в приложении Amnezia выберите &quot;Добавить сервер&quot; &quot;У меня есть данные для подключения&quot; &quot;QR-код, ключ или файл настроек&quot;</translation>
</message>
@ -3228,7 +3259,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>VpnConnection</name>
<message>
<location filename="../vpnconnection.cpp" line="457"/>
<location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source>
<translation>Mbps</translation>
</message>
@ -3332,12 +3363,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context>
<name>main2</name>
<message>
<location filename="../ui/qml/main2.qml" line="174"/>
<location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source>
<translation>Кодовая фраза для закрытого ключа</translation>
</message>
<message>
<location filename="../ui/qml/main2.qml" line="197"/>
<location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source>
<translation>Сохранить</translation>
</message>

View file

@ -20,45 +20,45 @@
<name>ConnectionController</name>
<message>
<location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed.
Please install VPN container at first</source>
<translation>VPN协议</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source>
<translation type="unfinished"></translation>
</message>
@ -353,17 +353,17 @@ Already installed containers were found on the server. All installed containers
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source>
<translation>VPN协议</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source>
<translation></translation>
</message>
@ -1493,7 +1493,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source>
<source>Do you want to clear server Amnezia-installed services?</source>
<translation>Amnezia软件吗</translation>
</message>
<message>
@ -1541,7 +1541,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source>
<source>Remove this server from the app</source>
<translation></translation>
</message>
<message>
@ -1560,7 +1560,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove server?</source>
<source>Remove server from application?</source>
<translation type="vanished">?</translation>
</message>
<message>
@ -1570,11 +1570,11 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source>
<source>Clear server Amnezia-installed services</source>
<translation>Amnezia中服务器信息</translation>
</message>
<message>
<source>Clear server from Amnezia software?</source>
<source>Clear server Amnezia-installed services?</source>
<translation type="vanished">Amnezia中服务器信息</translation>
</message>
<message>
@ -1797,40 +1797,44 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>使
<translation type="vanished">使
</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source>
<translation></translation>
</message>
@ -1930,7 +1934,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source>
<translation></translation>
</message>
@ -2039,32 +2043,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context>
<name>PageSetupWizardStart</name>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source>
<translation>VPN服务</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation>访使VPN提供商也无法获取</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
@ -2105,9 +2109,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>使</translation>
<translation type="vanished">使</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@ -2199,7 +2207,7 @@ 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="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source>
<translation></translation>
</message>
@ -2209,52 +2217,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation> VPN 访</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source>
<translation>- %1?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
@ -2319,8 +2327,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="obsolete"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source>
<translation></translation>
</message>
@ -2340,14 +2348,14 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source>
<translation></translation>
</message>
@ -2729,86 +2737,108 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source>
<translation>Sftp错误: End-of-file encountered</translation>
<translation type="vanished">Sftp错误: End-of-file encountered</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source>
<translation>Sftp错误: 文件不存在</translation>
<translation type="vanished">Sftp错误: 文件不存在</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source>
<translation>Sftp错误: 权限不足</translation>
<translation type="vanished">Sftp错误: 权限不足</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source>
<translation>Sftp错误: 一般失败</translation>
<translation type="vanished">Sftp错误: 一般失败</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source>
<translation>Sftp错误: 从服务器收到垃圾信息</translation>
<translation type="vanished">Sftp错误: 从服务器收到垃圾信息</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source>
<translation>Sftp 错误: 未建立连接</translation>
<translation type="vanished">Sftp 错误: 未建立连接</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp 错误: 已有连接丢失</translation>
<translation type="vanished">Sftp 错误: 已有连接丢失</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: libssh不支持该操作</translation>
<translation type="vanished">Sftp error: libssh不支持该操作</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source>
<translation>Sftp error: 无效的文件句柄</translation>
<translation type="vanished">Sftp error: 无效的文件句柄</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source>
<translation>Sftp 错误: 文件夹或文件不存在</translation>
<translation type="vanished">Sftp 错误: 文件夹或文件不存在</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp 错误: 文件或目录已存在</translation>
<translation type="vanished">Sftp 错误: 文件或目录已存在</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source>
<translation>Sftp 错误: 文件系统写保护</translation>
<translation type="vanished">Sftp 错误: 文件系统写保护</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source>
<translation>Sftp 错误: 远程驱动器中没有媒介</translation>
<translation type="vanished">Sftp 错误: 远程驱动器中没有媒介</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="68"/>
<location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="76"/>
<location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source>
<translation type="unfinished"></translation>
</message>
@ -2817,57 +2847,57 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="47"/>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source>
<translation>OpenVPN配置丢失</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="48"/>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source>
<translation>OpenVPN </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="51"/>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source>
<translation>OpenVPN </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="52"/>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="53"/>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="54"/>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source>
<translation>Amnezia </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="55"/>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source>
<translation>OpenSSL错误</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="58"/>
<location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source>
<translation>VPN连接处于活跃状态</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="59"/>
<location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation> OpenVPN TAP </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="60"/>
<location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source>
<translation>VPN </translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source>
<translation></translation>
</message>
@ -2876,7 +2906,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="73"/>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source>
<translation></translation>
</message>
@ -3245,8 +3275,8 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation></translation>
</message>
<message>
<location filename="../settings.cpp" line="206"/>
<location filename="../settings.cpp" line="213"/>
<location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="214"/>
<source>Server</source>
<translation></translation>
</message>
@ -3254,17 +3284,17 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>SettingsController</name>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source>
<translation></translation>
</message>
<message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source>
<translation></translation>
</message>
@ -3278,28 +3308,28 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source>
<translation></translation>
</message>
@ -3308,7 +3338,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation type="obsolete"></translation>
</message>
<message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation> Amnezia+</translation>
</message>
@ -3400,7 +3430,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>VpnConnection</name>
<message>
<location filename="../vpnconnection.cpp" line="457"/>
<location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source>
<translation></translation>
</message>
@ -3504,12 +3534,12 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context>
<name>main2</name>
<message>
<location filename="../ui/qml/main2.qml" line="174"/>
<location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/main2.qml" line="197"/>
<location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source>
<translation></translation>
</message>

View file

@ -90,7 +90,7 @@ void ApiController::updateServerConfigFromApi()
request.setRawHeader("Authorization",
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
request.setUrl(endpoint.replace("https", "http")); // todo remove
request.setUrl(endpoint);
QString protocol = serverConfig.value(configKey::protocol).toString();
@ -138,7 +138,10 @@ void ApiController::updateServerConfigFromApi()
serverConfig.insert(config_key::defaultContainer, defaultContainer);
m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex());
} else {
QString err = reply->errorString();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
emit errorOccurred(errorString(ApiConfigDownloadError));
m_isConfigUpdateStarted = false;

View file

@ -18,7 +18,9 @@ namespace
enum class ConfigTypes {
Amnezia,
OpenVpn,
WireGuard
WireGuard,
Backup,
Invalid
};
ConfigTypes checkConfigFormat(const QString &config)
@ -32,15 +34,23 @@ namespace
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
const QString amneziaConfigPattern = "containers";
const QString amneziaFreeConfigPattern = "api_key";
const QString backupPattern = "Servers/serversList";
if (config.contains(backupPattern)) {
return ConfigTypes::Backup;
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)) {
return ConfigTypes::Amnezia;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface)
&& config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard;
}
return ConfigTypes::Amnezia;
return ConfigTypes::Invalid;
}
#if defined Q_OS_ANDROID
@ -58,34 +68,65 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
#endif
}
void ImportController::extractConfigFromFile(const QString &fileName)
bool ImportController::extractConfigFromFile(const QString &fileName)
{
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
QString data = file.readAll();
extractConfigFromData(data);
m_configFileName = QFileInfo(file.fileName()).fileName();
return extractConfigFromData(data);
}
emit importErrorOccurred(tr("Unable to open file"));
return false;
}
void ImportController::extractConfigFromData(QString data)
bool ImportController::extractConfigFromData(QString data)
{
auto configFormat = checkConfigFormat(data);
if (configFormat == ConfigTypes::OpenVpn) {
m_config = extractOpenVpnConfig(data);
} else if (configFormat == ConfigTypes::WireGuard) {
m_config = extractWireGuardConfig(data);
} else {
m_config = extractAmneziaConfig(data);
}
}
QString config = data;
auto configFormat = checkConfigFormat(config);
if (configFormat == ConfigTypes::Invalid) {
data.replace("vpn://", "");
QByteArray ba =
QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray ba_uncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) {
ba = ba_uncompressed;
}
void ImportController::extractConfigFromCode(QString code)
{
m_config = extractAmneziaConfig(code);
m_configFileName = "";
config = ba;
configFormat = checkConfigFormat(config);
}
switch (configFormat) {
case ConfigTypes::OpenVpn: {
m_config = extractOpenVpnConfig(config);
return m_config.empty() ? false : true;
}
case ConfigTypes::WireGuard: {
m_config = extractWireGuardConfig(config);
return m_config.empty() ? false : true;
}
case ConfigTypes::Amnezia: {
m_config = QJsonDocument::fromJson(config.toUtf8()).object();
return m_config.empty() ? false : true;
}
case ConfigTypes::Backup: {
if (!m_serversModel->getServersCount()) {
emit restoreAppConfig(config.toUtf8());
} else {
emit importErrorOccurred(tr("Invalid configuration file"));
}
break;
}
case ConfigTypes::Invalid: {
emit importErrorOccurred(tr("Invalid configuration file"));
break;
}
}
return false;
}
bool ImportController::extractConfigFromQr(const QByteArray &data)
@ -139,28 +180,13 @@ void ImportController::importConfig()
} else {
qDebug() << "Failed to import profile";
qDebug().noquote() << QJsonDocument(m_config).toJson();
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
}
m_config = {};
m_configFileName.clear();
}
QJsonObject ImportController::extractAmneziaConfig(QString &data)
{
data.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray ba_uncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) {
ba = ba_uncompressed;
}
QJsonObject config = QJsonDocument::fromJson(ba).object();
return config;
}
QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
{
QJsonObject openVpnConfig;
@ -229,8 +255,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
if (hostNameAndPortMatch.hasCaptured(1)) {
hostName = hostNameAndPortMatch.captured(1);
} else {
qDebug() << "Failed to import profile";
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
qDebug() << "Key parameter 'Endpoint' is missing";
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
}
if (hostNameAndPortMatch.hasCaptured(2)) {
@ -242,10 +268,11 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
lastConfig[config_key::hostName] = hostName;
lastConfig[config_key::port] = port.toInt();
// if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty()
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) {
if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty()
&& !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address");
if (!configMap.value("PresharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) {
@ -253,11 +280,15 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
}
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else {
// qDebug() << "Failed to import profile";
// emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
// return QJsonObject();
// }
} else {
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
return QJsonObject();
}
if (!configMap.value("MTU").isEmpty()) {
lastConfig[config_key::mtu] = configMap.value("MTU");
}
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));

View file

@ -18,9 +18,8 @@ public:
public slots:
void importConfig();
void extractConfigFromFile(const QString &fileName);
void extractConfigFromData(QString data);
void extractConfigFromCode(QString code);
bool extractConfigFromFile(const QString &fileName);
bool extractConfigFromData(QString data);
bool extractConfigFromQr(const QByteArray &data);
QString getConfig();
QString getConfigFileName();
@ -39,12 +38,14 @@ public slots:
signals:
void importFinished();
void importErrorOccurred(const QString &errorMessage, bool goToPageHome = false);
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
void importErrorOccurred(const QString &errorMessage);
void qrDecodingFinished();
void restoreAppConfig(const QByteArray &data);
private:
QJsonObject extractAmneziaConfig(QString &data);
QJsonObject extractOpenVpnConfig(const QString &data);
QJsonObject extractWireGuardConfig(const QString &data);

View file

@ -10,6 +10,8 @@
#include "core/errorstrings.h"
#include "core/controllers/serverController.h"
#include "utilities.h"
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
namespace
{
@ -273,12 +275,16 @@ void InstallController::updateContainer(QJsonObject config)
const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString());
QJsonObject oldContainerConfig = m_containersModel->getContainerConfig(container);
ErrorCode errorCode = ErrorCode::NoError;
ServerController serverController(m_settings);
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
if (isUpdateDockerContainerRequired(container, oldContainerConfig, config)) {
ServerController serverController(m_settings);
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config);
}
auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config);
if (errorCode == ErrorCode::NoError) {
m_serversModel->updateContainerConfig(container, config);
m_protocolModel->updateModel(config);
@ -514,3 +520,29 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully"));
}
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::Awg) {
const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
return true;
}
} else if (container == DockerContainer::WireGuard) {
const WgConfig oldConfig(oldProtoConfig);
const WgConfig newConfig(newProtoConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
return true;
}
}
return false;
}

View file

@ -76,6 +76,8 @@ private:
void installContainer(DockerContainer container, QJsonObject &config);
bool isServerAlreadyExists();
bool isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ProtocolsModel> m_protocolModel;

View file

@ -7,9 +7,7 @@
#include "ui/qautostart.h"
#include "version.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h"
#include "platforms/android/android_controller.h"
#include <QJniObject>
#endif
#ifdef Q_OS_IOS
@ -29,20 +27,6 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
m_settings(settings)
{
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
#ifdef Q_OS_ANDROID
if (!m_settings->isScreenshotsEnabled()) {
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_SECURE = 8192;
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
}
});
}
#endif
}
void SettingsController::toggleAmneziaDns(bool enable)
@ -129,6 +113,11 @@ void SettingsController::restoreAppConfig(const QString &fileName)
QByteArray data = file.readAll();
restoreAppConfigFromData(data);
}
void SettingsController::restoreAppConfigFromData(const QByteArray &data)
{
bool ok = m_settings->restoreAppConfig(data);
if (ok) {
m_serversModel->resetModel();
@ -152,7 +141,12 @@ void SettingsController::clearSettings()
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
emit changeSettingsFinished(tr("All settings have been reset to default values"));
#ifdef Q_OS_IOS
AmneziaVPN::clearSettings();
#endif
}
void SettingsController::clearCachedProfiles()
@ -199,19 +193,6 @@ bool SettingsController::isScreenshotsEnabled()
void SettingsController::toggleScreenshotsEnabled(bool enable)
{
m_settings->setScreenshotsEnabled(enable);
#ifdef Q_OS_ANDROID
std::string command = enable ? "clearFlags" : "addFlags";
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([&command]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_SECURE = 8192;
window.callMethod<void>(command.c_str(), "(I)V", FLAG_SECURE);
}
});
#endif
}
bool SettingsController::isCameraPresent()

View file

@ -41,6 +41,7 @@ public slots:
void backupAppConfig(const QString &fileName);
void restoreAppConfig(const QString &fileName);
void restoreAppConfigFromData(const QByteArray &data);
QString getAppVersion();

View file

@ -46,6 +46,7 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan
case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break;
case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break;
case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break;
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
default:
break;
}
@ -61,6 +62,7 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum
case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break;
case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break;
case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break;
case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break;
default: emit updateTranslations(QLocale::English); break;
}
}
@ -74,6 +76,7 @@ int LanguageModel::getCurrentLanguageIndex()
case QLocale::Chinese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::China_cn); break;
case QLocale::Persian: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Persian); break;
case QLocale::Arabic: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Arabic); break;
case QLocale::Burmese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Burmese); break;
default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break;
}
}

View file

@ -14,7 +14,8 @@ namespace LanguageSettings
Russian,
China_cn,
Persian,
Arabic
Arabic,
Burmese
};
Q_ENUM_NS(AvailableLanguageEnum)

View file

@ -22,6 +22,7 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
switch (role) {
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
@ -57,6 +58,7 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
switch (role) {
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount);
case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize);
case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize);
@ -80,25 +82,21 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
QJsonObject protocolConfig = config.value(config_key::awg).toObject();
m_protocolConfig[config_key::port] =
protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
m_protocolConfig[config_key::port] = protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
m_protocolConfig[config_key::mtu] = protocolConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
m_protocolConfig[config_key::junkPacketCount] =
protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
m_protocolConfig[config_key::junkPacketMinSize] =
protocolConfig.value(config_key::junkPacketMinSize)
.toString(protocols::awg::defaultJunkPacketMinSize);
protocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
m_protocolConfig[config_key::junkPacketMaxSize] =
protocolConfig.value(config_key::junkPacketMaxSize)
.toString(protocols::awg::defaultJunkPacketMaxSize);
protocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
m_protocolConfig[config_key::initPacketJunkSize] =
protocolConfig.value(config_key::initPacketJunkSize)
.toString(protocols::awg::defaultInitPacketJunkSize);
protocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
m_protocolConfig[config_key::responsePacketJunkSize] =
protocolConfig.value(config_key::responsePacketJunkSize)
.toString(protocols::awg::defaultResponsePacketJunkSize);
protocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
m_protocolConfig[config_key::initPacketMagicHeader] =
protocolConfig.value(config_key::initPacketMagicHeader)
.toString(protocols::awg::defaultInitPacketMagicHeader);
protocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
m_protocolConfig[config_key::responsePacketMagicHeader] =
protocolConfig.value(config_key::responsePacketMagicHeader)
.toString(protocols::awg::defaultResponsePacketMagicHeader);
@ -114,6 +112,19 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
QJsonObject AwgConfigModel::getConfig()
{
const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
const AwgConfig newConfig(m_protocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
m_protocolConfig.remove(config_key::last_config);
} else {
auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = newConfig.mtu;
m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
m_fullConfig.insert(config_key::awg, m_protocolConfig);
return m_fullConfig;
}
@ -123,6 +134,7 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
QHash<int, QByteArray> roles;
roles[PortRole] = "port";
roles[MtuRole] = "mtu";
roles[JunkPacketCountRole] = "junkPacketCount";
roles[JunkPacketMinSizeRole] = "junkPacketMinSize";
roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize";
@ -135,3 +147,47 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
return roles;
}
AwgConfig::AwgConfig(const QJsonObject &jsonConfig)
{
port = jsonConfig.value(config_key::port).toString(protocols::awg::defaultPort);
mtu = jsonConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
junkPacketCount = jsonConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
junkPacketMinSize =
jsonConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
junkPacketMaxSize =
jsonConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
initPacketJunkSize =
jsonConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
responsePacketJunkSize =
jsonConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
initPacketMagicHeader =
jsonConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
responsePacketMagicHeader = jsonConfig.value(config_key::responsePacketMagicHeader)
.toString(protocols::awg::defaultResponsePacketMagicHeader);
underloadPacketMagicHeader = jsonConfig.value(config_key::underloadPacketMagicHeader)
.toString(protocols::awg::defaultUnderloadPacketMagicHeader);
transportPacketMagicHeader = jsonConfig.value(config_key::transportPacketMagicHeader)
.toString(protocols::awg::defaultTransportPacketMagicHeader);
}
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
{
if (port != other.port || junkPacketCount != other.junkPacketCount || junkPacketMinSize != other.junkPacketMinSize
|| junkPacketMaxSize != other.junkPacketMaxSize || initPacketJunkSize != other.initPacketJunkSize
|| responsePacketJunkSize != other.responsePacketJunkSize || initPacketMagicHeader != other.initPacketMagicHeader
|| responsePacketMagicHeader != other.responsePacketMagicHeader
|| underloadPacketMagicHeader != other.underloadPacketMagicHeader
|| transportPacketMagicHeader != other.transportPacketMagicHeader) {
return false;
}
return true;
}
bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const
{
if (mtu != other.mtu) {
return false;
}
return true;
}

View file

@ -6,6 +6,27 @@
#include "containers/containers_defs.h"
struct AwgConfig
{
AwgConfig(const QJsonObject &jsonConfig);
QString port;
QString mtu;
QString junkPacketCount;
QString junkPacketMinSize;
QString junkPacketMaxSize;
QString initPacketJunkSize;
QString responsePacketJunkSize;
QString initPacketMagicHeader;
QString responsePacketMagicHeader;
QString underloadPacketMagicHeader;
QString transportPacketMagicHeader;
bool hasEqualServerSettings(const AwgConfig &other) const;
bool hasEqualClientSettings(const AwgConfig &other) const;
};
class AwgConfigModel : public QAbstractListModel
{
Q_OBJECT
@ -13,6 +34,7 @@ class AwgConfigModel : public QAbstractListModel
public:
enum Roles {
PortRole = Qt::UserRole + 1,
MtuRole,
JunkPacketCountRole,
JunkPacketMinSizeRole,
JunkPacketMaxSizeRole,

View file

@ -1,5 +1,7 @@
#include "wireguardConfigModel.h"
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent)
@ -19,8 +21,8 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
}
switch (role) {
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break;
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
}
emit dataChanged(index, index, QList { role });
@ -34,9 +36,8 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
}
switch (role) {
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort);
case Roles::CipherRole:
return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher);
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
}
return QVariant();
@ -50,11 +51,31 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
m_fullConfig = config;
QJsonObject protocolConfig = config.value(config_key::wireguard).toObject();
m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
m_protocolConfig[config_key::port] =
protocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
m_protocolConfig[config_key::mtu] =
protocolConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
endResetModel();
}
QJsonObject WireGuardConfigModel::getConfig()
{
const WgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
const WgConfig newConfig(m_protocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
m_protocolConfig.remove(config_key::last_config);
} else {
auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = newConfig.mtu;
m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
m_fullConfig.insert(config_key::wireguard, m_protocolConfig);
return m_fullConfig;
}
@ -64,7 +85,29 @@ QHash<int, QByteArray> WireGuardConfigModel::roleNames() const
QHash<int, QByteArray> roles;
roles[PortRole] = "port";
roles[CipherRole] = "cipher";
roles[MtuRole] = "mtu";
return roles;
}
WgConfig::WgConfig(const QJsonObject &jsonConfig)
{
port = jsonConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
mtu = jsonConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
}
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
{
if (port != other.port) {
return false;
}
return true;
}
bool WgConfig::hasEqualClientSettings(const WgConfig &other) const
{
if (mtu != other.mtu) {
return false;
}
return true;
}

View file

@ -6,6 +6,18 @@
#include "containers/containers_defs.h"
struct WgConfig
{
WgConfig(const QJsonObject &jsonConfig);
QString port;
QString mtu;
bool hasEqualServerSettings(const WgConfig &other) const;
bool hasEqualClientSettings(const WgConfig &other) const;
};
class WireGuardConfigModel : public QAbstractListModel
{
Q_OBJECT
@ -13,7 +25,7 @@ class WireGuardConfigModel : public QAbstractListModel
public:
enum Roles {
PortRole = Qt::UserRole + 1,
CipherRole
MtuRole
};
explicit WireGuardConfigModel(QObject *parent = nullptr);

View file

@ -58,10 +58,8 @@ ListView {
break
}
case ContainerEnum.WireGuard: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
// WireGuardConfigModel.updateModel(config)
// goToPage(PageEnum.PageProtocolWireGuardSettings)
WireGuardConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolWireGuardSettings)
break
}
case ContainerEnum.Awg: {
@ -72,8 +70,6 @@ ListView {
case ContainerEnum.Ipsec: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
// Ikev2ConfigModel.updateModel(config)
// goToPage(PageEnum.PageProtocolIKev2Settings)
break
}
case ContainerEnum.Sftp: {

View file

@ -40,7 +40,7 @@ DrawerType2 {
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
header.forceActiveFocus()
}

View file

@ -2,6 +2,8 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Config"
Item {
id: root
@ -32,6 +34,6 @@ Item {
}
}
repeat: false // Stop the timer after one trigger
running: true // Start the timer
running: !GC.isMobile() // Start the timer
}
}

View file

@ -137,6 +137,7 @@ Item {
// textColor: "#D7D8DB"
// borderWidth: 0
focusPolicy: Qt.NoFocus
text: root.buttonText
imageSource: root.buttonImageSource

View file

@ -160,12 +160,11 @@ PageType {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: ServersModel.defaultServerDescriptionCollapsed
}
}
expandedContent: Item {
id: serverMenuContainer
implicitHeight: root.height * 0.9
implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
Component.onCompleted: {
drawer.expandedHeight = serverMenuContainer.implicitHeight

View file

@ -106,6 +106,26 @@ PageType {
KeyNavigation.tab: junkPacketCountTextField.textField
}
TextFieldWithHeaderType {
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: junkPacketCountTextField
Layout.fillWidth: true
@ -337,7 +357,7 @@ PageType {
junkPacketCountTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save and Restart Amnezia")
text: qsTr("Save")
clickedFunc: function() {
forceActiveFocus()

View file

@ -174,7 +174,7 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
text: qsTr("Save and Restart Amnezia")
text: qsTr("Save")
clickedFunc: function() {
forceActiveFocus()

View file

@ -404,7 +404,7 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
text: qsTr("Save and Restart Amnezia")
text: qsTr("Save")
clickedFunc: function() {
forceActiveFocus()

View file

@ -148,7 +148,7 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
text: qsTr("Save and Restart Amnezia")
text: qsTr("Save")
clickedFunc: function() {
forceActiveFocus()

View file

@ -0,0 +1,172 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
ColumnLayout {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
BackButtonType {
}
}
FlickableType {
id: fl
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
Column {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess()
ListView {
id: listview
width: parent.width
height: listview.contentItem.height
clip: true
interactive: false
model: WireGuardConfigModel
delegate: Item {
implicitWidth: listview.width
implicitHeight: col.implicitHeight
ColumnLayout {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 0
HeaderType {
Layout.fillWidth: true
headerText: qsTr("WG settings")
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textFieldText !== port) {
port = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
BasicButtonType {
Layout.topMargin: 24
Layout.leftMargin: -8
implicitHeight: 32
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
textColor: "#EB5757"
text: qsTr("Remove WG")
onClicked: {
questionDrawer.headerText = qsTr("Remove WG from server?")
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.visible = false
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeCurrentlyProcessedContainer()
}
questionDrawer.noButtonFunction = function() {
questionDrawer.visible = false
}
questionDrawer.visible = true
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: mtuTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
onClicked: {
forceActiveFocus()
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(WireGuardConfigModel.getConfig())
}
}
}
}
}
}
QuestionDrawer {
id: questionDrawer
}
}
}

View file

@ -88,7 +88,7 @@ PageType {
Layout.topMargin: 32
text: qsTr("Host")
descriptionText: ServersModel.getProcessedServerData("HostName")
descriptionText: ServersModel.getProcessedServerData("hostName")
descriptionOnTop: true

View file

@ -166,7 +166,7 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Remove server from application")
text: qsTr("Remove this server from the app")
textColor: "#EB5757"
clickedFunction: function() {
@ -196,11 +196,11 @@ PageType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: qsTr("Clear server from Amnezia software")
text: qsTr("Clear server Amnezia-installed services")
textColor: "#EB5757"
clickedFunction: function() {
var headerText = qsTr("Do you want to clear server from Amnezia software?")
var headerText = qsTr("Do you want to clear server Amnezia-installed services?")
var descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")

View file

@ -93,6 +93,7 @@ PageType {
Connections {
target: serverNameEditDrawer
enabled: !GC.isMobile()
function onOpened() {
serverName.textField.forceActiveFocus()
}
@ -130,7 +131,7 @@ PageType {
}
Component.onCompleted: {
if (header.itemAt(0)) {
if (header.itemAt(0) && !GC.isMobile()) {
defaultActiveFocusItem = serverName.textField
}
}

View file

@ -48,8 +48,7 @@ PageType {
Layout.leftMargin: 16
headerText: qsTr("Server connection")
descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n
It's okay as long as it's from someone you trust.")
descriptionText: qsTr("Do not use connection codes from untrusted sources, as they may be created to intercept your data.")
}
Header2TextType {
@ -74,12 +73,7 @@ It's okay as long as it's from someone you trust.")
"Config files (*.vpn *.ovpn *.conf)"
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
if (fileName !== "") {
if (fileName.indexOf(".backup") !== -1 && !ServersModel.getServersCount()) {
PageController.showBusyIndicator(true)
SettingsController.restoreAppConfig(fileName)
PageController.showBusyIndicator(false)
} else {
ImportController.extractConfigFromFile(fileName)
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}

View file

@ -56,8 +56,8 @@ PageType {
regularExpression: InstallController.ipAddressPortRegExp()
}
onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
KeyNavigation.tab: username.textField
@ -70,6 +70,10 @@ PageType {
headerText: qsTr("Login to connect via SSH")
textFieldPlaceholderText: "root"
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
KeyNavigation.tab: secretData.textField
}
@ -88,8 +92,8 @@ PageType {
hidePassword = !hidePassword
}
onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
KeyNavigation.tab: continueButton

View file

@ -22,10 +22,6 @@ PageType {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
function onShowBusyIndicator(visible) {
busyIndicator.visible = visible
}
function onClosePage() {
if (stackView.depth <= 1) {
return
@ -53,7 +49,7 @@ PageType {
}
function onEscapePressed() {
if (isControlsDisabled || busyIndicator.visible) {
if (isControlsDisabled) {
return
}
@ -85,6 +81,20 @@ PageType {
}
}
Connections {
target: ImportController
function onRestoreAppConfig(data) {
PageController.showBusyIndicator(true)
SettingsController.restoreAppConfigFromData(data)
PageController.showBusyIndicator(false)
}
function onImportErrorOccurred(errorMessage) {
PageController.showErrorMessage(errorMessage)
}
}
FlickableType {
id: fl
anchors.top: parent.top
@ -159,10 +169,4 @@ PageType {
ConnectionTypeSelectionDrawer {
id: connectionTypeSelection
}
BusyIndicatorType {
id: busyIndicator
anchors.centerIn: parent
z: 1
}
}

View file

@ -77,8 +77,9 @@ PageType {
text: qsTr("Continue")
clickedFunc: function() {
ImportController.extractConfigFromCode(textKey.textFieldText)
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
if (ImportController.extractConfigFromData(textKey.textFieldText)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
}
}

View file

@ -92,7 +92,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Do not use connection code from public sources. It could be created to intercept your data.")
text: qsTr("Do not use connection codes from untrusted sources, as they may be created to intercept your data.")
color: "#878B91"
}

View file

@ -648,6 +648,7 @@ PageType {
Connections {
target: clientNameEditDrawer
enabled: !GC.isMobile()
function onOpened() {
clientNameEditor.textField.forceActiveFocus()
}

View file

@ -14,6 +14,8 @@ import "../Components"
PageType {
id: root
property bool isControlsDisabled: false
Connections {
target: PageController
@ -32,20 +34,11 @@ PageType {
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition)
}
function onShowBusyIndicator(visible) {
busyIndicator.visible = visible
tabBarStackView.enabled = !visible
tabBar.enabled = !visible
}
function onDisableControls(disabled) {
tabBar.enabled = !disabled
isControlsDisabled = disabled
}
function onClosePage() {
tabBar.isServerInfoShow = tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsServerInfo)
&& tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsSplitTunneling)
if (tabBarStackView.depth <= 1) {
return
}
@ -60,8 +53,6 @@ PageType {
} else {
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate)
}
tabBar.isServerInfoShow = (page === PageEnum.PageSettingsServerInfo) || (page === PageEnum.PageSettingsSplitTunneling) || tabBar.isServerInfoShow
}
function onGoToStartPage() {
@ -72,7 +63,7 @@ PageType {
}
function onEscapePressed() {
if (!tabBar.enabled || busyIndicator.visible) {
if (root.isControlsDisabled) {
return
}
@ -130,6 +121,14 @@ PageType {
}
}
Connections {
target: ImportController
function onImportErrorOccurred(errorMessage) {
PageController.showErrorMessage(errorMessage)
}
}
StackViewType {
id: tabBarStackView
@ -141,13 +140,14 @@ PageType {
width: parent.width
height: root.height - tabBar.implicitHeight
enabled: !root.isControlsDisabled
function goToTabBarPage(page) {
connectionTypeSelection.close()
var pagePath = PageController.getPagePath(page)
tabBarStackView.clear(StackView.Immediate)
tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate)
tabBar.isServerInfoShow = false
}
Component.onCompleted: {
@ -161,7 +161,6 @@ PageType {
id: tabBar
property int previousIndex: 0
property bool isServerInfoShow: false
anchors.right: parent.right
anchors.left: parent.left
@ -172,6 +171,8 @@ PageType {
leftPadding: 96
rightPadding: 96
enabled: !root.isControlsDisabled
background: Shape {
width: parent.width
height: parent.height
@ -192,7 +193,7 @@ PageType {
}
TabImageButtonType {
isSelected: tabBar.isServerInfoShow ? false : tabBar.currentIndex === 0
isSelected: tabBar.currentIndex === 0
image: "qrc:/images/controls/home.svg"
onClicked: {
tabBarStackView.goToTabBarPage(PageEnum.PageHome)
@ -226,7 +227,7 @@ PageType {
}
TabImageButtonType {
isSelected: tabBar.isServerInfoShow ? true : tabBar.currentIndex === 2
isSelected: tabBar.currentIndex === 2
image: "qrc:/images/controls/settings-2.svg"
onClicked: {
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
@ -243,12 +244,6 @@ PageType {
}
}
BusyIndicatorType {
id: busyIndicator
anchors.centerIn: parent
z: 1
}
ConnectionTypeSelectionDrawer {
id: connectionTypeSelection

View file

@ -86,6 +86,11 @@ Window {
function onGoToPageSettingsBackup() {
PageController.goToPage(PageEnum.PageSettingsBackup)
}
function onShowBusyIndicator(visible) {
busyIndicator.visible = visible
PageController.disableControls(visible)
}
}
Connections {
@ -215,6 +220,16 @@ Window {
}
}
Item {
anchors.fill: parent
BusyIndicatorType {
id: busyIndicator
anchors.centerIn: parent
z: 1
}
}
function showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) {
questionDrawer.headerText = headerText
questionDrawer.descriptionText = descriptionText

View file

@ -270,6 +270,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerC
ErrorCode *errorCode)
{
QJsonObject vpnConfiguration;
vpnConfiguration[config_key::serverIndex] = serverIndex;
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
auto s = m_settings->server(serverIndex);
@ -471,10 +472,15 @@ void VpnConnection::disconnectFromVpn()
#ifdef Q_OS_ANDROID
if (m_vpnProtocol && m_vpnProtocol.data()) {
connect(AndroidController::instance(), &AndroidController::vpnDisconnected, this,
[this]() {
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
}, Qt::SingleShotConnection);
auto *const connection = new QMetaObject::Connection;
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
[this, connection](AndroidController::ConnectionState state) {
if (state == AndroidController::ConnectionState::DISCONNECTED) {
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
disconnect(*connection);
delete connection;
}
});
m_vpnProtocol.data()->stop();
}
#endif