Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ee61a040b | |||
| 741b5cc0f9 | |||
| aaf0e070dc | |||
| e0e126eda8 | |||
| 236daf6b3b | |||
| f1481b1b1f | |||
| f6e7d3ccf1 | |||
| a754a11913 | |||
| 4d25e3b6f6 | |||
| 1fac280497 | |||
| c886c5e6a7 | |||
| cd7f78b9ca | |||
| a587d3230f | |||
| 93e7b45136 | |||
| e024f71ce1 | |||
| 50d1be7b4a | |||
| 3ec6d8973b | |||
| 3ea47d31a9 | |||
| 30c8cc4548 | |||
| 98586d2dd9 | |||
| c66d8ecca0 | |||
| db535f7e7d | |||
| 89f30d8c31 | |||
| 8bce432824 | |||
| f3539b2632 | |||
| 7a96c212f3 | |||
| 2d5dc54e0f | |||
| cef4c262e9 | |||
| 34309261a8 | |||
| 657eeb40c7 | |||
| b4938c2cc9 | |||
| 524fefc5cb | |||
| 73f13404bb | |||
| 5fc68cca83 | |||
| fcb7b8fa8d | |||
| a81e32ff95 | |||
| c897052107 | |||
| 4d0efc7ea5 | |||
| a77842c9e3 | |||
| 0ded9db780 | |||
| 58d480fcb5 | |||
| 7154428d26 | |||
| 02a52d0169 | |||
| ec60764072 | |||
| 17d2fa5532 | |||
| 3ca8b534e8 | |||
| e88f7c5e46 | |||
| 3ac5d7bd1f | |||
| 19cad00a00 | |||
| 1ea716a163 | |||
| 4551659c2a | |||
| c568bf8c24 | |||
| a412d91105 | |||
| ad01f23bbe | |||
| 656070b132 | |||
| c907f5ca36 | |||
| 94a13b2b54 | |||
| 169f11d9c7 | |||
| 816dc3af95 | |||
| b802863de5 | |||
| 8dc2a4b76c | |||
| beb1c6dbf2 | |||
| 3eb06916c7 | |||
| 30d0f84a4f | |||
| 251f2aa5db | |||
| 16d92ddb7c | |||
| e9d4fd8482 | |||
| 9fdcf5ab13 | |||
| a6e6de33c8 | |||
| 53c7fd4d81 | |||
| 2608ea4367 | |||
| d20ed4ad01 | |||
| eae2936449 | |||
| da8ad1f6ba | |||
| 5472347969 | |||
| a43f7a6926 | |||
| 47f917de0b | |||
| dbeb7edd7a | |||
| 6cede712f5 | |||
| d328739192 | |||
| d15c0bd962 | |||
| d53c794936 | |||
| e5dcb25a4a | |||
| f9002b4f43 | |||
| 0531508a75 | |||
| 174e85a20a | |||
| e9abb6f1e2 | |||
| 5be44f9596 | |||
| 90efaaff92 | |||
| 99b554e7c3 | |||
| ac0ce8a6f6 | |||
| 9f9da885b7 | |||
| f51fd2bf3e | |||
| c8378fd32d | |||
| d767214f10 |
@@ -358,7 +358,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.4.0'
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -402,14 +402,75 @@ jobs:
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.8.3
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos_ne.sh
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.3
|
||||
ANDROID_BUILD_PLATFORM: android-36
|
||||
QT_VERSION: 6.9.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
|
||||
@@ -9,6 +9,7 @@ deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
|
||||
# Qt-es
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.9.0)
|
||||
set(AMNEZIAVPN_VERSION 4.8.11.1)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
@@ -12,7 +12,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 2090)
|
||||
set(APP_ANDROID_VERSION_CODE 2096)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -32,13 +32,19 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
elseif(MACOS_NE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||
else()
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
||||
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
|
||||
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
set(PROJECT AmneziaVPN)
|
||||
project(${PROJECT})
|
||||
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
||||
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
||||
@@ -53,6 +52,9 @@ endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||
target_include_directories(${PROJECT} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
@@ -110,6 +112,15 @@ include_directories(
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
add_definitions(-DQ_OS_MAC)
|
||||
add_definitions(-DMACOS_NE)
|
||||
message("Add macros for MacOS Network Extension")
|
||||
else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
|
||||
include_directories(mozilla)
|
||||
include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
@@ -139,7 +150,7 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
cmake_policy(SET CMP0099 OLD)
|
||||
cmake_policy(SET CMP0099 NEW)
|
||||
cmake_policy(SET CMP0114 NEW)
|
||||
|
||||
if(NOT BUILD_OSX_APP_IDENTIFIER)
|
||||
@@ -158,7 +169,6 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -166,8 +176,7 @@ if(LINUX AND NOT ANDROID)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
endif()
|
||||
|
||||
@@ -178,7 +187,9 @@ endif()
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
elseif(APPLE AND MACOS_NE)
|
||||
include(cmake/macos_ne.cmake)
|
||||
elseif(APPLE)
|
||||
include(cmake/osxtools.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
@@ -199,7 +210,7 @@ elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
@@ -214,7 +225,6 @@ if(NOT IOS AND NOT ANDROID)
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QEvent>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
@@ -21,8 +22,12 @@
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
|
||||
#include "protocols/qml_register_protocols.h"
|
||||
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||
#include <QWindow> // for qobject_cast<QWindow*>
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
@@ -49,7 +54,6 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
m_vpnConnectionThread.quit();
|
||||
m_vpnConnectionThread.wait(3000);
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
@@ -63,15 +67,28 @@ void AmneziaApplication::init()
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[this, url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl) {
|
||||
QCoreApplication::exit(-1);
|
||||
return;
|
||||
}
|
||||
// install filter on main window
|
||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||
win->installEventFilter(this);
|
||||
win->show();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||
|
||||
#ifdef MACOS_NE
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
|
||||
#else
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||
#endif
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(m_settings));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
@@ -94,7 +111,7 @@ void AmneziaApplication::init()
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN //TODO
|
||||
if (m_parser.isSet("a"))
|
||||
if (m_parser.isSet(m_optAutostart))
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
else
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
@@ -162,15 +179,12 @@ bool AmneziaApplication::parseCommands()
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
||||
m_parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
||||
m_parser.addOption(c_cleanup);
|
||||
|
||||
m_parser.addOption(m_optAutostart);
|
||||
m_parser.addOption(m_optCleanup);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(c_cleanup)) {
|
||||
if (m_parser.isSet(m_optCleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
@@ -179,9 +193,8 @@ bool AmneziaApplication::parseCommands()
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
@@ -198,6 +211,22 @@ void AmneziaApplication::startLocalServer()
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Close) {
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
quit();
|
||||
#else
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
m_coreController->pageController()->hideMainWindow();
|
||||
}
|
||||
#endif
|
||||
return true; // eat the close
|
||||
}
|
||||
// call base QObject::eventFilter
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <QClipboard>
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
void loadFonts();
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
@@ -56,10 +56,15 @@ private:
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QCommandLineOption m_optAutostart;
|
||||
QCommandLineOption m_optCleanup;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:enforceNavigationBarContrast">false</item>
|
||||
<item name="android:enforceStatusBarContrast">false</item>
|
||||
</style>
|
||||
<style name="Translucent" parent="NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
@@ -35,6 +35,7 @@ import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -170,10 +171,9 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
// Configure window for edge-to-edge display
|
||||
configureWindowForEdgeToEdge()
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
@@ -265,6 +265,49 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.decorView.apply {
|
||||
invalidate()
|
||||
|
||||
postDelayed({
|
||||
sendTouch(1f, 1f)
|
||||
}, 100)
|
||||
|
||||
postDelayed({
|
||||
sendTouch(2f, 2f)
|
||||
}, 200)
|
||||
|
||||
postDelayed({
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureWindowForEdgeToEdge() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
} else {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy Amnezia activity")
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
@@ -666,6 +709,43 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
@Suppress("unused")
|
||||
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
|
||||
@Suppress("unused")
|
||||
fun getStatusBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getNavigationBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
|
||||
@@ -38,15 +38,15 @@ object AppListProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
|
||||
val name: String?
|
||||
val packageName: String = pi.packageName
|
||||
val icon: Boolean = ai.icon != 0
|
||||
val icon: Boolean = (ai?.icon ?: 0) != 0
|
||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||
|
||||
init {
|
||||
val name = ai.loadLabel(pm).toString()
|
||||
this.name = if (name != packageName) name else null
|
||||
val name = ai?.loadLabel(pm)?.toString()
|
||||
this.name = name?.takeIf { it != packageName }
|
||||
}
|
||||
|
||||
override fun compareTo(other: App): Int {
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileLock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.amnezia.vpn.util.Log.Priority.D
|
||||
import org.amnezia.vpn.util.Log.Priority.E
|
||||
@@ -135,8 +137,8 @@ object Log {
|
||||
}
|
||||
|
||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
|
||||
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
"$tag: $msg\n"
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,15 @@ if(WIN32)
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
endif()
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||
|
||||
set(APP_ANDROID_MIN_SDK 26)
|
||||
set(APP_ANDROID_MIN_SDK 28)
|
||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||
"The minimum API level supported by the application or library" FORCE)
|
||||
|
||||
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
|
||||
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||
QT_ANDROID_TARGET_SDK_VERSION 36
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||
)
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -14,11 +14,15 @@ set(LIBS ${LIBS}
|
||||
${FW_SECURITY}
|
||||
${FW_COREWLAN}
|
||||
${FW_NETWORK}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_USER_NOTIFICATIONS}
|
||||
${FW_NETWORK_EXTENSION}
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
@@ -31,6 +35,8 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
@@ -49,4 +55,3 @@ execute_process(
|
||||
)
|
||||
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||
# Link Qt Widgets for QWidget, QMenu, QAction etc.
|
||||
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
|
||||
|
||||
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||
find_library(FW_AVFOUNDATION AVFoundation)
|
||||
find_library(FW_FOUNDATION Foundation)
|
||||
find_library(FW_STOREKIT StoreKit)
|
||||
find_library(FW_SERVICEMGMT ServiceManagement)
|
||||
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||
find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${FW_AUTHENTICATIONSERVICES}
|
||||
${FW_AVFOUNDATION}
|
||||
${FW_FOUNDATION}
|
||||
${FW_STOREKIT}
|
||||
${FW_SERVICEMGMT}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_NETWORKEXTENSION}
|
||||
)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE
|
||||
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
||||
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||
)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${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
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(macos/networkextension)
|
||||
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
|
||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Signing OpenVPNAdapter framework"
|
||||
)
|
||||
@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
@@ -89,12 +89,26 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Include native macOS platform helpers (dock/status-item)
|
||||
if(APPLE AND NOT IOS)
|
||||
list(APPEND HEADERS
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
|
||||
${CLIENT_ROOT_DIR}/ui/macos_util.h
|
||||
)
|
||||
list(APPEND SOURCES
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
|
||||
${CLIENT_ROOT_DIR}/ui/macos_util.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||
|
||||
@@ -83,12 +83,30 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
||||
return "";
|
||||
}
|
||||
|
||||
auto sanitizeStaticKey = [](const QString &key) {
|
||||
QStringList lines = key.split('\n');
|
||||
QStringList filtered;
|
||||
filtered.reserve(lines.size());
|
||||
for (const QString &line : lines) {
|
||||
const QString trimmed = line.trimmed();
|
||||
if (trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
filtered.append(line);
|
||||
}
|
||||
QString result = filtered.join('\n');
|
||||
if (!result.endsWith('\n')) {
|
||||
result.append('\n');
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||
|
||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
||||
} else {
|
||||
config.replace("<tls-auth>", "");
|
||||
config.replace("</tls-auth>", "");
|
||||
@@ -117,7 +135,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
if (!isApiConfig) {
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||
@@ -131,7 +149,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
// Prevent ipv6 leak
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QTemporaryFile>
|
||||
#include <QThread>
|
||||
#include <qtimer.h>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
|
||||
|
||||
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
#endif
|
||||
}
|
||||
|
||||
// DEAD CODE.
|
||||
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QProcess *p = new QProcess();
|
||||
p->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#elif defined(Q_OS_MACX)
|
||||
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
return true;
|
||||
|
||||
#elif defined(Q_OS_IOS)
|
||||
// Standard iOS build (without Network Extension limitations)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
@@ -269,7 +270,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
default: return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(MACOS_NE)
|
||||
// macOS build using Network Extension – hide OpenVPN-based containers
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
case DockerContainer::OpenVpn:
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
switch (c) {
|
||||
|
||||
@@ -47,12 +47,14 @@ namespace apiDefs
|
||||
constexpr QLatin1String serverCountryName("server_country_name");
|
||||
|
||||
constexpr QLatin1String osVersion("os_version");
|
||||
constexpr QLatin1String appLanguage("app_language");
|
||||
|
||||
constexpr QLatin1String availableCountries("available_countries");
|
||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
||||
|
||||
constexpr QLatin1String supportInfo("support_info");
|
||||
constexpr QLatin1String email("email");
|
||||
@@ -64,6 +66,16 @@ namespace apiDefs
|
||||
constexpr QLatin1String id("id");
|
||||
constexpr QLatin1String orderId("order_id");
|
||||
constexpr QLatin1String migrationCode("migration_code");
|
||||
|
||||
constexpr QLatin1String transactionId("transaction_id");
|
||||
|
||||
constexpr QLatin1String userCountryCode("user_country_code");
|
||||
|
||||
constexpr QLatin1String serviceInfo("service_info");
|
||||
constexpr QLatin1String isAdVisible("is_ad_visible");
|
||||
constexpr QLatin1String adHeader("ad_header");
|
||||
constexpr QLatin1String adDescription("ad_description");
|
||||
constexpr QLatin1String adEndpoint("ad_endpoint");
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace
|
||||
|
||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||
return endDate < now;
|
||||
}
|
||||
@@ -82,7 +82,9 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
|
||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||
}
|
||||
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody)
|
||||
{
|
||||
const int httpStatusCodeConflict = 409;
|
||||
const int httpStatusCodeNotFound = 404;
|
||||
@@ -90,21 +92,19 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return amnezia::ErrorCode::ApiConfigSslError;
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
} else if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << reply->error();
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << replyError;
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
qDebug() << reply->error();
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
qDebug() << replyError;
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << QString::fromUtf8(responseBody);
|
||||
qDebug() << replyError;
|
||||
qDebug() << replyErrorString;
|
||||
qDebug() << httpStatusCode;
|
||||
if (httpStatusCode == httpStatusCodeConflict) {
|
||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||
@@ -162,3 +162,51 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
|
||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||
}
|
||||
|
||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString vpnKeyText = "";
|
||||
|
||||
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
|
||||
|
||||
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
|
||||
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
|
||||
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
|
||||
|
||||
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
||||
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
|
||||
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
|
||||
|
||||
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
|
||||
|
||||
QString vpnKeyStr = "{";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
|
||||
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
|
||||
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
|
||||
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
|
||||
vpnKeyStr += "}, ";
|
||||
|
||||
vpnKeyStr += "\"auth_data\": {";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
|
||||
vpnKeyStr += "}";
|
||||
|
||||
vpnKeyStr += "}";
|
||||
|
||||
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||
|
||||
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||
|
||||
return vpnKeyText;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ namespace apiUtils
|
||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody);
|
||||
|
||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||
}
|
||||
|
||||
#endif // APIUTILS_H
|
||||
|
||||
@@ -26,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
updateTranslator(m_settings->getAppLanguage());
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
@@ -100,6 +99,9 @@ void CoreController::initModels()
|
||||
|
||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||
|
||||
m_newsModel.reset(new NewsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
||||
}
|
||||
|
||||
void CoreController::initControllers()
|
||||
@@ -120,6 +122,9 @@ void CoreController::initControllers()
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||
|
||||
connect(m_installController.get(), &InstallController::profileCleared,
|
||||
m_protocolsModel.get(), &ProtocolsModel::updateModel);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
@@ -151,6 +156,9 @@ void CoreController::initControllers()
|
||||
|
||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||
|
||||
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
@@ -230,7 +238,7 @@ void CoreController::initSignalHandlers()
|
||||
|
||||
void CoreController::initNotificationHandler()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
@@ -242,7 +250,10 @@ void CoreController::initNotificationHandler()
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
|
||||
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
@@ -279,6 +290,7 @@ void CoreController::updateTranslator(const QLocale &locale)
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
|
||||
}
|
||||
|
||||
void CoreController::initErrorMessagesHandler()
|
||||
@@ -310,6 +322,11 @@ void CoreController::initContainerModelUpdateHandler()
|
||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||
&ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
||||
if (m_serversModel->hasServersFromGatewayApi()) {
|
||||
m_apiNewsController->fetchNews();
|
||||
}
|
||||
});
|
||||
m_serversModel->resetModel();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/systemtray_notificationhandler.h"
|
||||
#endif
|
||||
|
||||
#include "ui/controllers/api/apiConfigsController.h"
|
||||
#include "ui/controllers/api/apiSettingsController.h"
|
||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||
#include "ui/controllers/api/apiNewsController.h"
|
||||
#include "ui/controllers/appSplitTunnelingController.h"
|
||||
#include "ui/controllers/allowedDnsController.h"
|
||||
#include "ui/controllers/connectionController.h"
|
||||
@@ -43,8 +48,9 @@
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
|
||||
@@ -61,6 +67,7 @@ public:
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
@@ -92,7 +99,7 @@ private:
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
@@ -113,6 +120,7 @@ private:
|
||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
@@ -120,6 +128,7 @@ private:
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<NewsModel> m_newsModel;
|
||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
@@ -50,85 +51,25 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
||||
{
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||
{
|
||||
EncryptedRequestData encRequestData;
|
||||
encRequestData.errorCode = ErrorCode::NoError;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->get(request);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||
const QList<QSslError> &nestedSslErrors) {
|
||||
responseBody = nestedReply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
reply = nestedReply;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString host = QUrl(encRequestData.request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
@@ -137,14 +78,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
#endif
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
@@ -159,24 +100,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
@@ -186,35 +140,44 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
wait.exec();
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
reply->deleteLater();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||
encRequestData.request.setUrl(url);
|
||||
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = nestedReply->readAll();
|
||||
reply = nestedReply;
|
||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||
this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = reply->readAll();
|
||||
replyErrorString = reply->errorString();
|
||||
replyError = reply->error();
|
||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
try {
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
@@ -223,7 +186,58 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
}
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls()
|
||||
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||
{
|
||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||
promise->start();
|
||||
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) {
|
||||
*sslErrors = errors;
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, reply, [=]() {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
try {
|
||||
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
|
||||
promise->finish();
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||
promise->finish();
|
||||
}
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
@@ -233,15 +247,26 @@ QStringList GatewayController::getProxyUrls()
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
QStringList baseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
@@ -286,7 +311,10 @@ QStringList GatewayController::getProxyUrls()
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << replyError;
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
|
||||
reply->deleteLater();
|
||||
@@ -295,33 +323,33 @@ QStringList GatewayController::getProxyUrls()
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||
const QByteArray &iv, const QByteArray &salt)
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
|
||||
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "timeout occurred";
|
||||
qDebug() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << reply->error();
|
||||
} else if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
@@ -335,29 +363,74 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
|
||||
return false;
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||
{
|
||||
QStringList proxyUrls = getProxyUrls();
|
||||
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QByteArray responseBody;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (replyProcessingFunction(reply, sslErrors)) {
|
||||
auto result = replyProcessingFunction(reply, sslErrors);
|
||||
reply->deleteLater();
|
||||
return result;
|
||||
};
|
||||
|
||||
if (m_proxyUrl.isEmpty()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
request.setUrl(proxyUrl + "lmbd-health");
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
|
||||
m_proxyUrl = proxyUrl;
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
m_proxyUrl = proxyUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#ifndef GATEWAYCONTROLLER_H
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/defs.h"
|
||||
|
||||
@@ -18,20 +20,34 @@ public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
|
||||
private:
|
||||
QStringList getProxyUrls();
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
struct EncryptedRequestData {
|
||||
QNetworkRequest request;
|
||||
QByteArray requestBody;
|
||||
QByteArray key;
|
||||
QByteArray iv;
|
||||
QByteArray salt;
|
||||
amnezia::ErrorCode errorCode;
|
||||
};
|
||||
|
||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||
|
||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
||||
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
||||
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
int m_requestTimeoutMsecs;
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
#endif // GATEWAYCONTROLLER_H
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace amnezia
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
ApiSubscriptionExpiredError = 1112,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -77,6 +77,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -390,7 +390,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
close(sock);
|
||||
return gateway_address;
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QString gateway;
|
||||
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||
int afinet_type[] = {AF_INET, AF_INET6};
|
||||
|
||||
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_primaryDnsServer.isNull()) {
|
||||
if (!m_primaryDnsServer.isEmpty()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isNull()) {
|
||||
if (!m_secondaryDnsServer.isEmpty()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4_34)">
|
||||
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4_34">
|
||||
<rect width="74" height="74" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Основа газеты -->
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
<!-- Линии текста -->
|
||||
<line x1="7" y1="8" x2="17" y2="8"/>
|
||||
<line x1="7" y1="12" x2="17" y2="12"/>
|
||||
<line x1="7" y1="16" x2="13" y2="16"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
@@ -32,17 +32,41 @@
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>QIOSWindowSceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 682 B |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,6 +1,68 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
"images": [
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "1x",
|
||||
"filename": "16.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "2x",
|
||||
"filename": "16@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "1x",
|
||||
"filename": "32.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "2x",
|
||||
"filename": "32@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "1x",
|
||||
"filename": "128.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "2x",
|
||||
"filename": "128@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "1x",
|
||||
"filename": "256.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "2x",
|
||||
"filename": "256@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "1x",
|
||||
"filename": "512.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "2x",
|
||||
"filename": "512@2x.png"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>LSMultipleInstancesProhibited</key>
|
||||
<true/>
|
||||
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,172 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict/>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Amnezia VPN config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>vpn</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>WireGuard config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>conf</string>
|
||||
<string>cfg</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>OpenVPN config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>ovpn</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AmneziaVPN backup file</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>backup</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amnezia VPN config</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSExtensions</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,34 +2,40 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.developer.networking.custom-protocol</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>app-proxy-provider</string>
|
||||
<string>packet-tunnel-provider</string>
|
||||
<string>dns-settings</string>
|
||||
<string>relay</string>
|
||||
<string>content-filter-provider</string>
|
||||
<string>dns-proxy</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<array>
|
||||
<string>allow-vpn</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,41 +2,30 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.developer.networking.custom-protocol</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>dns-settings</string>
|
||||
<string>relay</string>
|
||||
<string>packet-tunnel-provider</string>
|
||||
<string>content-filter-provider</string>
|
||||
<string>dns-proxy</string>
|
||||
<string>app-proxy-provider</string>
|
||||
</array>
|
||||
|
||||
<key>keychain-access-groups</key>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
<string>allow-vpn</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.private.network.socket-delegate</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
enable_language(Swift)
|
||||
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||
|
||||
add_executable(AmneziaVPNNetworkExtension)
|
||||
|
||||
message("executable_path is: @executable_path/../../Frameworks")
|
||||
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
|
||||
# MACOSX_BUNDLE YES
|
||||
BUNDLE_EXTENSION appex
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
|
||||
|
||||
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||
|
||||
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
message("DEPLOY is ON")
|
||||
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
|
||||
)
|
||||
else()
|
||||
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
)
|
||||
|
||||
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
|
||||
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
|
||||
find_library(FW_MOBILE_CORE MobileCoreServices)
|
||||
find_library(FW_UI_KIT UIKit)
|
||||
find_library(FW_LIBRESOLV libresolv.9.tbd)
|
||||
|
||||
|
||||
# Set the root directory
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
|
||||
|
||||
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
|
||||
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
|
||||
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
|
||||
|
||||
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
|
||||
)
|
||||
|
||||
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
## Build wireguard-go-version.h
|
||||
execute_process(
|
||||
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
|
||||
OUTPUT_VARIABLE WG_VERSION_FULL
|
||||
)
|
||||
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
|
||||
|
||||
message(${CLIENT_ROOT_DIR})
|
||||
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
|
||||
@@ -3,27 +3,32 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<string>org.amnezia.AmneziaVPN.network-extension</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<string>${APPLE_PROJECT_VERSION}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
|
||||
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
@@ -31,5 +36,11 @@
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
|
||||
<key>com.wireguard.macos.app_group_id</key>
|
||||
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,10 +1,10 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macos/gobridge/wireguard.h"
|
||||
|
||||
#include "wireguard-go-version.h"
|
||||
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
|
||||
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
|
||||
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
|
||||
|
||||
void write_msg_to_log(const char* tag, const char* msg);
|
||||
|
||||
// init function definition in C
|
||||
void hev_socks5_tunnel_quit(void);
|
||||
// Updated function definition in C
|
||||
int hev_socks5_tunnel_main(const char* configFile, int fd);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#ifndef WIREGUARD_GO_VERSION
|
||||
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
|
||||
#endif // WIREGUARD_GO_VERSION
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
bool isAnotherInstanceRunning()
|
||||
{
|
||||
QLocalSocket socket;
|
||||
@@ -45,7 +45,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
AmneziaApplication app(argc, argv);
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||
return app.exec();
|
||||
|
||||
@@ -264,13 +264,13 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||
/* && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||
@@ -278,27 +278,27 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()*/) {
|
||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
// json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
// json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
// json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
// json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
// json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
// json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
// json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
// json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
}
|
||||
|
||||
write(json);
|
||||
|
||||
@@ -202,6 +202,21 @@ bool AndroidController::isOnTv()
|
||||
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
||||
}
|
||||
|
||||
bool AndroidController::isEdgeToEdgeEnabled()
|
||||
{
|
||||
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
|
||||
}
|
||||
|
||||
int AndroidController::getStatusBarHeight()
|
||||
{
|
||||
return callActivityMethod<jint>("getStatusBarHeight", "()I");
|
||||
}
|
||||
|
||||
int AndroidController::getNavigationBarHeight()
|
||||
{
|
||||
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
|
||||
}
|
||||
|
||||
void AndroidController::startQrReaderActivity()
|
||||
{
|
||||
callActivityMethod("startQrCodeReader", "()V");
|
||||
|
||||
@@ -39,6 +39,9 @@ public:
|
||||
QString getFileName(const QString &uri);
|
||||
bool isCameraPresent();
|
||||
bool isOnTv();
|
||||
bool isEdgeToEdgeEnabled();
|
||||
int getStatusBarHeight();
|
||||
int getNavigationBarHeight();
|
||||
void startQrReaderActivity();
|
||||
void setSaveLogs(bool enabled);
|
||||
void exportLogsFile(const QString &fileName);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
#include "ios_controller.h"
|
||||
|
||||
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
|
||||
|
||||
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
|
||||
|
||||
static void amnezia_handleURL(NSURL *url)
|
||||
{
|
||||
if (!url || !url.isFileURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath(url.path.UTF8String);
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (filePath.contains("backup")) {
|
||||
IosController::Instance()->importBackupFromOutside(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = file.readAll();
|
||||
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
|
||||
});
|
||||
}
|
||||
|
||||
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
|
||||
{
|
||||
if (g_originalSceneOpenURLContexts) {
|
||||
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
|
||||
}
|
||||
|
||||
if (!contexts || contexts.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
for (UIOpenURLContext *context in contexts) {
|
||||
amnezia_handleURL(context.URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@interface AmneziaSceneDelegateHooks : NSObject
|
||||
@end
|
||||
|
||||
@implementation AmneziaSceneDelegateHooks
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
Class cls = objc_getClass("QIOSWindowSceneDelegate");
|
||||
if (!cls) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL selector = @selector(scene:openURLContexts:);
|
||||
Method method = class_getInstanceMethod(cls, selector);
|
||||
if (method) {
|
||||
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
|
||||
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
|
||||
} else {
|
||||
const char *types = "v@:@@";
|
||||
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -2,7 +2,8 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
struct Log {
|
||||
static let osLog = Logger()
|
||||
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
||||
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
|
||||
|
||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||
static var isLoggingEnabled: Bool {
|
||||
@@ -77,9 +78,40 @@ struct Log {
|
||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||
NSLog("\(title) \(message)")
|
||||
|
||||
guard isLoggingEnabled else { return }
|
||||
switch type {
|
||||
case .debug:
|
||||
if title.isEmpty {
|
||||
osLog.debug("\(message, privacy: .public)")
|
||||
} else {
|
||||
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .info:
|
||||
if title.isEmpty {
|
||||
osLog.info("\(message, privacy: .public)")
|
||||
} else {
|
||||
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .error:
|
||||
if title.isEmpty {
|
||||
osLog.error("\(message, privacy: .public)")
|
||||
} else {
|
||||
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .fault:
|
||||
if title.isEmpty {
|
||||
osLog.fault("\(message, privacy: .public)")
|
||||
} else {
|
||||
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
default:
|
||||
if title.isEmpty {
|
||||
osLog.log("\(message, privacy: .public)")
|
||||
} else {
|
||||
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
osLog.log(level: type, "\(title) \(message)")
|
||||
guard isLoggingEnabled else { return }
|
||||
|
||||
let date = Date()
|
||||
let level = Record.Level(from: type)
|
||||
|
||||
@@ -1,22 +1,76 @@
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
||||
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
|
||||
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
|
||||
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
|
||||
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
|
||||
|
||||
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
|
||||
let combinedTitle: String
|
||||
if title.isEmpty {
|
||||
combinedTitle = prefix
|
||||
} else {
|
||||
combinedTitle = "\(prefix): \(title)"
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .debug:
|
||||
if combinedTitle.isEmpty {
|
||||
logger.debug("\(message, privacy: .public)")
|
||||
} else {
|
||||
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .info:
|
||||
if combinedTitle.isEmpty {
|
||||
logger.info("\(message, privacy: .public)")
|
||||
} else {
|
||||
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .error:
|
||||
if combinedTitle.isEmpty {
|
||||
logger.error("\(message, privacy: .public)")
|
||||
} else {
|
||||
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
case .fault:
|
||||
if combinedTitle.isEmpty {
|
||||
logger.fault("\(message, privacy: .public)")
|
||||
} else {
|
||||
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
default:
|
||||
if combinedTitle.isEmpty {
|
||||
logger.log("\(message, privacy: .public)")
|
||||
} else {
|
||||
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||
let stringMessage = String(describing: staticMessage)
|
||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
|
||||
neLog(type, title: "WG: \(title)", message: stringMessage)
|
||||
}
|
||||
|
||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
|
||||
neLog(type, title: "WG: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
|
||||
neLog(type, title: "OVPN: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
|
||||
neLog(type, title: "XRAY: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
|
||||
Log.log(type, title: "NE: \(title)", message: message)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import OpenVPNAdapter
|
||||
import CryptoKit
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
@@ -27,26 +28,83 @@ extension PacketTunnelProvider {
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func logOpenVPNError(_ error: NSError) {
|
||||
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
|
||||
var lines: [String] = []
|
||||
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
|
||||
|
||||
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
|
||||
lines.append("message=\(adapterMessage)")
|
||||
}
|
||||
|
||||
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
|
||||
if !userInfoKeys.isEmpty {
|
||||
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
|
||||
}
|
||||
|
||||
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
|
||||
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
|
||||
lines.append("underlyingMessage=\(underlyingMessage)")
|
||||
} else if !underlying.localizedDescription.isEmpty {
|
||||
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
|
||||
}
|
||||
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
|
||||
lines.append("underlyingRaw=\(underlying)")
|
||||
}
|
||||
|
||||
let formatted = lines.joined(separator: "\n ")
|
||||
ovpnLog(.error, title: "Error", message: formatted)
|
||||
}
|
||||
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||
withShadowSocks viaSS: Bool = false,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
ovpnLog(.info, message: "Setup and launch")
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
let digest = SHA256.hash(data: ovpnConfiguration)
|
||||
let digestString = digest.map { String(format: "%02x", $0) }.joined()
|
||||
ovpnLog(.info, title: "ConfigDigest", message: digestString)
|
||||
|
||||
let hasTlsAuthOpen = configString.contains("<tls-auth>")
|
||||
let hasTlsAuthClose = configString.contains("</tls-auth>")
|
||||
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
|
||||
|
||||
let lines = configString.split(separator: "\n")
|
||||
let head = lines.prefix(10).joined(separator: "\n")
|
||||
let tail = lines.suffix(10).joined(separator: "\n")
|
||||
ovpnLog(.debug, title: "ConfigHead", message: head)
|
||||
ovpnLog(.debug, title: "ConfigTail", message: tail)
|
||||
|
||||
if let start = configString.range(of: "<tls-auth>"),
|
||||
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
|
||||
let keyBody = String(configString[start.upperBound..<end.lowerBound])
|
||||
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
|
||||
let sanitizedLines = keyBody
|
||||
.split(whereSeparator: { $0.isNewline })
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
.filter { !$0.hasPrefix("#") }
|
||||
|
||||
let sanitizedKey = sanitizedLines.joined(separator: "\n")
|
||||
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
|
||||
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
|
||||
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
|
||||
}
|
||||
|
||||
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
|
||||
let sanitizedData = Data(normalizedConfig.utf8)
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.fileContent = sanitizedData
|
||||
if configString.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
@@ -57,6 +115,8 @@ extension PacketTunnelProvider {
|
||||
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||
|
||||
} catch {
|
||||
let nsError = error as NSError
|
||||
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
@@ -73,7 +133,7 @@ extension PacketTunnelProvider {
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter?.connect(using: packetFlow)
|
||||
}
|
||||
|
||||
|
||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
|
||||
@@ -208,8 +268,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
|
||||
// Handle errors thrown by the OpenVPN library
|
||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
||||
let nsError = error as NSError
|
||||
logOpenVPNError(nsError)
|
||||
|
||||
// Handle only fatal errors
|
||||
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||
fatal == true else { return }
|
||||
|
||||
if vpnReachability.isTracking {
|
||||
|
||||
@@ -112,9 +112,19 @@ extension PacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
|
||||
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
|
||||
let lastHandshake: Int64
|
||||
|
||||
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
|
||||
lastHandshake = handshakeValue
|
||||
} else {
|
||||
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
|
||||
"last_handshake_time_sec": lastHandshake
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if !MACOS_NE
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@@ -108,3 +109,19 @@ void QRCodeReader::startReading() {
|
||||
void QRCodeReader::stopReading() {
|
||||
[m_qrCodeReader stopReading];
|
||||
}
|
||||
#else
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
QRCodeReader::QRCodeReader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QRect QRCodeReader::cameraSize() {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {}
|
||||
void QRCodeReader::stopReading() {}
|
||||
void QRCodeReader::setCameraSize(QRect) {}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#if !MACOS_NE
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#endif
|
||||
@interface QIOSApplicationDelegate
|
||||
@end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
|
||||
#if !MACOS_NE
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||
@@ -57,5 +57,5 @@
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#endif
|
||||
@end
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
#if MACOS_NE
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
|
||||
}
|
||||
|
||||
class ScreenProtection {
|
||||
|
||||
|
||||
}
|
||||
#else
|
||||
import UIKit
|
||||
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
@@ -90,3 +100,4 @@ struct ProtectionPair {
|
||||
textField.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
void disconnectVpn();
|
||||
|
||||
void vpnStatusDidChange(void *pNotification);
|
||||
|
||||
void vpnConfigurationDidChange(void *pNotification);
|
||||
|
||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
||||
|
||||
@@ -27,15 +27,51 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
#if !MACOS_NE
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.isKeyWindow) {
|
||||
UIApplication *application = [UIApplication sharedApplication];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
for (UIScene *scene in application.connectedScenes) {
|
||||
if (scene.activationState != UISceneActivationStateForegroundActive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (window.isKeyWindow && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (!window.isHidden && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in application.windows) {
|
||||
if (window.isKeyWindow && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in application.windows) {
|
||||
if (window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||
switch (status) {
|
||||
@@ -249,6 +285,21 @@ void IosController::checkStatus()
|
||||
sendVpnExtensionMessage(message, [&](NSDictionary* response){
|
||||
uint64_t txBytes = [response[@"tx_bytes"] intValue];
|
||||
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
|
||||
|
||||
uint64_t last_handshake_time_sec = 0;
|
||||
#if !MACOS_NE
|
||||
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
|
||||
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
|
||||
} else {
|
||||
qDebug() << "Key last_handshake_time_sec is missing or null";
|
||||
}
|
||||
|
||||
if (last_handshake_time_sec < 0) {
|
||||
disconnectVpn();
|
||||
qDebug() << "Invalid handshake time, disconnecting VPN.";
|
||||
}
|
||||
#endif
|
||||
|
||||
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
|
||||
m_rxBytes = rxBytes;
|
||||
m_txBytes = txBytes;
|
||||
@@ -803,14 +854,14 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||
[sharingItems addObject:logFileUrl];
|
||||
}
|
||||
|
||||
#if !MACOS_NE
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
|
||||
#endif
|
||||
__block bool isAccepted = false;
|
||||
|
||||
#if !MACOS_NE
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
@@ -823,6 +874,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
|
||||
#endif
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
@@ -831,6 +883,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
}
|
||||
|
||||
QString IosController::openFile() {
|
||||
#if !MACOS_NE
|
||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
|
||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
@@ -841,8 +894,9 @@ QString IosController::openFile() {
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
|
||||
#endif
|
||||
__block QString filePath;
|
||||
|
||||
#if !MACOS_NE
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
if (path) {
|
||||
filePath = QString::fromUtf8(path.UTF8String);
|
||||
@@ -851,7 +905,7 @@ QString IosController::openFile() {
|
||||
}
|
||||
emit finished();
|
||||
};
|
||||
|
||||
#endif
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#import <NetworkExtension/NetworkExtension.h>
|
||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !MACOS_NE
|
||||
#include <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#include <Security/Security.h>
|
||||
|
||||
class IosController;
|
||||
@@ -17,9 +21,10 @@ class IosController;
|
||||
@end
|
||||
|
||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||
|
||||
#if !MACOS_NE
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
|
||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation DocumentPickerDelegate
|
||||
#if !MACOS_NE
|
||||
@implementation DocumentPickerDelegate
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
@@ -42,4 +43,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !MACOS_NE
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface IOSNotificationDelegate
|
||||
@@ -87,3 +89,86 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
||||
}
|
||||
}];
|
||||
}
|
||||
#else
|
||||
|
||||
// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS
|
||||
@interface IOSNotificationDelegate
|
||||
: NSObject <UNUserNotificationCenterDelegate> {
|
||||
IOSNotificationHandler* m_iosNotificationHandler;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation IOSNotificationDelegate
|
||||
|
||||
- (id)initWithObject:(IOSNotificationHandler*)notification {
|
||||
self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific
|
||||
if (self) {
|
||||
m_iosNotificationHandler = notification;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
willPresentNotification:(UNNotification*)notification
|
||||
withCompletionHandler:
|
||||
(void (^)(UNNotificationPresentationOptions options))completionHandler {
|
||||
Q_UNUSED(center)
|
||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse*)response
|
||||
withCompletionHandler:(void (^)())completionHandler {
|
||||
Q_UNUSED(center)
|
||||
Q_UNUSED(response)
|
||||
completionHandler();
|
||||
}
|
||||
@end
|
||||
|
||||
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge)
|
||||
completionHandler:^(BOOL granted, NSError* _Nullable error) {
|
||||
Q_UNUSED(granted);
|
||||
if (!error) {
|
||||
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
IOSNotificationHandler::~IOSNotificationHandler() { }
|
||||
|
||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||
const QString& message, int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
|
||||
if (!m_delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = title.toNSString();
|
||||
content.body = message.toNSString();
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
int timerSec = timerMsec / 1000;
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
center.delegate = (id<UNUserNotificationCenterDelegate>)m_delegate;
|
||||
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError* _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"Local Notification failed");
|
||||
}
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,6 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent)
|
||||
Ikev2Protocol::~Ikev2Protocol()
|
||||
{
|
||||
qDebug() << "IpsecProtocol::~IpsecProtocol()";
|
||||
disconnect_vpn();
|
||||
Ikev2Protocol::stop();
|
||||
}
|
||||
|
||||
@@ -38,7 +37,7 @@ void Ikev2Protocol::stop()
|
||||
{
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
{
|
||||
if (! disconnect_vpn() ){
|
||||
if (!disconnect_vpn()){
|
||||
qDebug()<<"We don't disconnect";
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
}
|
||||
@@ -311,7 +310,9 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::disconnect_vpn(){
|
||||
if ( hRasConn != nullptr ){
|
||||
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
|
||||
auto ret = RasHangUp(hRasConn);
|
||||
qDebug() << "RasHangUp " << ret;
|
||||
if (ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
QThread::msleep(3000);
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace amnezia
|
||||
|
||||
constexpr char defaultPort[] = "51820";
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
constexpr char defaultMtu[] = "1280";
|
||||
#else
|
||||
constexpr char defaultMtu[] = "1376";
|
||||
@@ -212,7 +212,7 @@ namespace amnezia
|
||||
namespace awg
|
||||
{
|
||||
constexpr char defaultPort[] = "55424";
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
constexpr char defaultMtu[] = "1280";
|
||||
#else
|
||||
constexpr char defaultMtu[] = "1376";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "core/errorstrings.h"
|
||||
#include "vpnprotocol.h"
|
||||
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#include "openvpnovercloakprotocol.h"
|
||||
#include "openvpnprotocol.h"
|
||||
#include "shadowsocksvpnprotocol.h"
|
||||
@@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
|
||||
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
|
||||
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
|
||||
|
||||
@@ -169,6 +169,7 @@ void XrayProtocol::stop()
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::Interface()->disableKillSwitch();
|
||||
IpcClient::Interface()->StartRoutingIpv6();
|
||||
IpcClient::Interface()->restoreResolvers();
|
||||
#endif
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
m_xrayProcess.disconnect();
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<file>images/controls/mail.svg</file>
|
||||
<file>images/controls/map-pin.svg</file>
|
||||
<file>images/controls/more-vertical.svg</file>
|
||||
<file>images/controls/news.svg</file>
|
||||
<file>images/controls/news-unread.svg</file>
|
||||
<file>images/controls/unread-dot.svg</file>
|
||||
<file>images/controls/plus.svg</file>
|
||||
<file>images/controls/qr-code.svg</file>
|
||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||
@@ -49,6 +52,7 @@
|
||||
<file>images/controls/server.svg</file>
|
||||
<file>images/controls/settings-2.svg</file>
|
||||
<file>images/controls/settings.svg</file>
|
||||
<file>images/controls/settings-news.svg</file>
|
||||
<file>images/controls/share-2.svg</file>
|
||||
<file>images/controls/split-tunneling.svg</file>
|
||||
<file>images/controls/tag.svg</file>
|
||||
@@ -127,7 +131,6 @@
|
||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||
<file>ui/qml/Components/ServersListView.qml</file>
|
||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
|
||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||
<file>ui/qml/Components/AddSitePanel.qml</file>
|
||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||
@@ -212,6 +215,8 @@
|
||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsNewsNotifications.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||
@@ -228,6 +233,7 @@
|
||||
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
|
||||
<file>ui/qml/Pages2/PageShare.qml</file>
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>ui/qml/Pages2/PageShareConnection.qml</file>
|
||||
<file>ui/qml/Pages2/PageStart.qml</file>
|
||||
<file>ui/qml/Components/RenameServerDrawer.qml</file>
|
||||
<file>ui/qml/Controls2/ListViewType.qml</file>
|
||||
@@ -240,6 +246,7 @@
|
||||
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
|
||||
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||
<file>ui/qml/Components/AwgTextField.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/countriesFlags">
|
||||
<file>images/flagKit/ZW.svg</file>
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
#!/bin/sh
|
||||
|
||||
LOG_DATE=$(date -u +'%Y%m%d-%H%M%S')
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
LOG_FILE="${SCRIPT_DIR}/server-diagnostics-${LOG_DATE}.log"
|
||||
|
||||
# Logging function (sh compatible)
|
||||
log_and_display() {
|
||||
if [ "$1" = "-n" ]; then
|
||||
shift
|
||||
printf "%s" "$*" | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "$1" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Redirect stderr to stdout for logging
|
||||
exec 2>&1
|
||||
|
||||
header() {
|
||||
log_and_display ""
|
||||
log_and_display "=== $1 ==="
|
||||
}
|
||||
|
||||
# Pause for cancellation
|
||||
log_and_display ""
|
||||
log_and_display "VPN Server Diagnostics will start in 9s. Press Ctrl+C to cancel."
|
||||
sleep 9
|
||||
|
||||
log_and_display ""
|
||||
header "STARTING VPN SERVER DIAGNOSTICS"
|
||||
log_and_display ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 1. Basic system information
|
||||
# ------------------------------------------------------------------------------
|
||||
header "System Information"
|
||||
|
||||
# Uptime
|
||||
UPTIME_STR=$(awk '{printf "%d:%02d:%02d", int($1/3600), int(($1%3600)/60), int($1%60)}' /proc/uptime 2>/dev/null || echo "unknown")
|
||||
log_and_display "Uptime (H:M:S): $UPTIME_STR"
|
||||
|
||||
# Date/time UTC
|
||||
DATE_UTC=$(date -u +'%d %b %Y|%T' 2>/dev/null || echo "unknown")
|
||||
log_and_display "Date|Time (UTC): $DATE_UTC"
|
||||
|
||||
# Init system (PID 1)
|
||||
INIT_NAME=$(cat /proc/1/status 2>/dev/null | head -1 | awk '{print $2}' 2>/dev/null || echo "unknown")
|
||||
log_and_display "Init system (PID 1): $INIT_NAME"
|
||||
|
||||
# Locale
|
||||
if echo "$LANG" | grep -E '^(en_US.UTF-8|C.UTF-8|C)$' >/dev/null 2>&1; then
|
||||
log_and_display "Locale: $LANG"
|
||||
else
|
||||
log_and_display "Locale: $LANG (not en_US.UTF-8, C.UTF-8 or C)"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 2. Package manager detection
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Package Manager Information"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: APT"
|
||||
PM="apt-get"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker.io"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: DNF"
|
||||
PM="dnf"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: YUM"
|
||||
PM="yum"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v zypper >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: ZYPPER"
|
||||
PM="zypper"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: PACMAN"
|
||||
PM="pacman"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: OPKG - Not supported on this platform"
|
||||
PM="opkg"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
else
|
||||
log_and_display "Package Manager: Unknown"
|
||||
# fallback
|
||||
PM="uname"
|
||||
PM_VER_OPT="-a"
|
||||
DOCKER_PKG="docker"
|
||||
fi
|
||||
|
||||
# Check package versions
|
||||
log_and_display ""
|
||||
log_and_display "Package versions:"
|
||||
|
||||
# Check sudo
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
sudo_version=$(dpkg -s "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
sudo_version=$(rpm -q "sudo" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
sudo_version=$(pacman -Q "sudo" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
sudo_version=$(opkg info "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
sudo_version="unknown"
|
||||
fi
|
||||
log_and_display " sudo: $sudo_version"
|
||||
|
||||
# Check Docker package
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
docker_pkg_version=$(dpkg -s "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
docker_pkg_version=$(rpm -q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
docker_pkg_version=$(pacman -Q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
docker_pkg_version=$(opkg info "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
docker_pkg_version="unknown"
|
||||
fi
|
||||
log_and_display " $DOCKER_PKG: $docker_pkg_version"
|
||||
|
||||
# Check lsof
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
lsof_version=$(dpkg -s "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
lsof_version=$(rpm -q "lsof" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
lsof_version=$(pacman -Q "lsof" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
lsof_version=$(opkg info "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
lsof_version="unknown"
|
||||
fi
|
||||
log_and_display " lsof: $lsof_version"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 3. Additional system information (hostnamectl / /proc/version)
|
||||
# ------------------------------------------------------------------------------
|
||||
header "OS / Kernel Information"
|
||||
|
||||
if command -v hostnamectl >/dev/null 2>&1; then
|
||||
hostnamectl 2>/dev/null | grep -E 'Operating System:|Virtualization:|Kernel:|Architecture:' | sed 's/^[ \t]*//;s/:/: /' | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display "Operating System: $(cat /proc/version 2>/dev/null || echo 'unknown')"
|
||||
fi
|
||||
|
||||
# CPU threads
|
||||
CPU_THREADS=$(nproc 2>/dev/null || grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo "unknown")
|
||||
log_and_display " CPU threads: $CPU_THREADS"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 4. Memory (RAM) check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Memory Information"
|
||||
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
# Remove extra spaces in header
|
||||
free -h 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
elif command -v vmstat >/dev/null 2>&1; then
|
||||
vmstat -S M -s 2>/dev/null | grep -iE 'total memory|total swap' | sed 's/ *//' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
else
|
||||
grep -iE 'MemTotal|SwapTotal' /proc/meminfo 2>/dev/null | sed 's/ \+/ /' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
fi
|
||||
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
log_and_display ""
|
||||
log_and_display "Detailed Memory Info:"
|
||||
free -h 2>/dev/null | awk 'NR==2{printf " Used: %s / %s (%.1f%%)\n", $3, $2, $3/$2*100}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating memory usage"
|
||||
free -h 2>/dev/null | awk 'NR==3{printf " Swap: %s / %s (%.1f%%)\n", $3, $2, $2>0 ? $3/$2*100 : 0}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating swap usage"
|
||||
fi
|
||||
|
||||
# Disk usage
|
||||
header "Disk Usage"
|
||||
df -h 2>/dev/null | awk '
|
||||
BEGIN {print " Filesystem Size Used Avail Use% Mounted"}
|
||||
NR>1 {printf " %-10s %5s %5s %5s %4s %s\n", $1, $2, $3, $4, $5, $6}' | tee -a "$LOG_FILE" || log_and_display " Error getting disk usage"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 5. Current user and sudo check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "User Check"
|
||||
|
||||
CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///')
|
||||
USER_GROUP=$(groups "$CUR_USER" 2>/dev/null || echo "")
|
||||
USER_GOOD=0
|
||||
|
||||
log_and_display -n "Current user: $CUR_USER => "
|
||||
|
||||
if [ "$CUR_USER" = "root" ]; then
|
||||
log_and_display "passed.. (is root)"
|
||||
USER_GOOD="r" # root
|
||||
else
|
||||
if echo "$USER_GROUP" | grep -qE '(^|[[:space:]])sudo($|[[:space:]])'; then
|
||||
log_and_display "passed.. (in sudo group)"
|
||||
USER_GOOD=1
|
||||
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])wheel($|[[:space:]])'; then
|
||||
log_and_display "passed.. (in wheel group)"
|
||||
USER_GOOD=1
|
||||
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])docker($|[[:space:]])'; then
|
||||
log_and_display "failed.. (only in docker group)"
|
||||
USER_GOOD="d"
|
||||
else
|
||||
log_and_display "failed.. (not a member of the sudo or wheel groups)"
|
||||
USER_GOOD=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if password is required for sudo
|
||||
if [ "$USER_GOOD" = "0" ] || [ "$USER_GOOD" = "d" ]; then
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "check skipped (not sudoer)"
|
||||
else
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
# Try sudo without password - more thorough check
|
||||
PASSWD_REQUEST=$(sudo -K 2>&1 && sudo -nu $CUR_USER $PM $PM_VER_OPT 2>&1 >/dev/null && sudo -n $PM $PM_VER_OPT 2>&1 >/dev/null)
|
||||
if [ -n "$PASSWD_REQUEST" ]; then
|
||||
USER_GOOD=0
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "failed.. ($PASSWD_REQUEST)" \
|
||||
| sed "s/$CUR_USER/User/g;s/$(hostname 2>/dev/null || echo 'Server')/Server/g;s/ user / /g"
|
||||
else
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "passed.. (not required)"
|
||||
fi
|
||||
else
|
||||
if [ "$USER_GOOD" = "r" ]; then
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "check skipped (sudo not installed, but root user)"
|
||||
else
|
||||
log_and_display "Warning! The sudo package must be pre-installed!"
|
||||
USER_GOOD=0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Home directory check
|
||||
log_and_display -n "Home dir: "
|
||||
if cd ~ 2>/dev/null; then
|
||||
log_and_display "passed.. (accessible)"
|
||||
else
|
||||
log_and_display "failed.. (not accessible)"
|
||||
fi
|
||||
log_and_display "Default shell: $SHELL"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 6. Important components check (sudo, lsof, fuser, apparmor)
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Component Checks"
|
||||
|
||||
log_and_display -n " sudo: "
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n " lsof: "
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n " fuser: "
|
||||
if command -v fuser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "psmisc not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n "apparmor: "
|
||||
AA_ENABLED=$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null || echo "N")
|
||||
if [ "$AA_ENABLED" = "Y" ]; then
|
||||
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (used)"
|
||||
else
|
||||
log_and_display "failed.. (installation required)"
|
||||
fi
|
||||
else
|
||||
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (not used)"
|
||||
else
|
||||
log_and_display "passed.. (not required)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 7. SELinux check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "SELinux Check"
|
||||
|
||||
if command -v getenforce >/dev/null 2>&1; then
|
||||
SELINUX_STATUS=$(getenforce 2>/dev/null || echo "unknown")
|
||||
if [ "$SELINUX_STATUS" = "Enforcing" ]; then
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (strict mode)"
|
||||
elif [ "$SELINUX_STATUS" = "Permissive" ]; then
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (permissive mode)"
|
||||
else
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (disabled)"
|
||||
fi
|
||||
else
|
||||
log_and_display "SELinux: not found (or not applicable)"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 8. Docker + Docker/Podman service check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Docker / Podman Status"
|
||||
CHECK_CONTAINERS=0
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
log_and_display "Docker: $DOCKER_PKG not installed"
|
||||
else
|
||||
# If user is in sudoers, use sudo without password
|
||||
if [ "$USER_GOOD" = "1" ]; then
|
||||
SUD="sudo -n"
|
||||
elif [ "$USER_GOOD" = "r" ]; then
|
||||
SUD="" # root
|
||||
else
|
||||
SUD=""
|
||||
fi
|
||||
|
||||
DOCKER_VERSION=$($SUD docker -v 2>/dev/null || echo 'docker -v error')
|
||||
log_and_display "Installed: $DOCKER_VERSION"
|
||||
|
||||
# Check for podman
|
||||
if echo "$DOCKER_VERSION" | grep -qi "podman"; then
|
||||
log_and_display " WARNING: Podman detected - not supported at the moment!"
|
||||
log_and_display " Podman (podman-docker) is not supported and is installed by mistake"
|
||||
docker_service="podman.socket"
|
||||
else
|
||||
docker_service="docker.service"
|
||||
fi
|
||||
log_and_display " service: $docker_service"
|
||||
|
||||
# Check status
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
docker_status=$(systemctl is-active "$docker_service" 2>/dev/null || echo "unknown")
|
||||
docker_loading=$(systemctl is-enabled "$docker_service" 2>/dev/null || echo "unknown")
|
||||
else
|
||||
docker_status="unknown (systemctl not found)"
|
||||
docker_loading="unknown"
|
||||
fi
|
||||
|
||||
if [ "$docker_status" = "active" ]; then
|
||||
log_and_display " status: passed.. ($docker_status)"
|
||||
CHECK_CONTAINERS=1
|
||||
else
|
||||
log_and_display " status: incorrect.. ($docker_status)"
|
||||
CHECK_CONTAINERS=0
|
||||
fi
|
||||
|
||||
if [ "$docker_loading" = "enabled" ]; then
|
||||
log_and_display " loading: good (startup $docker_loading)"
|
||||
else
|
||||
log_and_display " loading: bad (startup $docker_loading)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 9. Docker pull test + container check with improved Docker Hub verification
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Docker Hub: pull hello-world test"
|
||||
|
||||
if [ "$CHECK_CONTAINERS" = "1" ] && [ "$USER_GOOD" != "0" ]; then
|
||||
# First check Docker Hub availability
|
||||
log_and_display "Checking Docker Hub connectivity..."
|
||||
|
||||
# Try to execute docker pull with timeout
|
||||
if timeout 30 $SUD docker pull docker.io/library/hello-world >/dev/null 2>&1; then
|
||||
log_and_display "Docker Hub: available"
|
||||
|
||||
# Start container for testing
|
||||
if $SUD docker run --rm docker.io/library/hello-world >/dev/null 2>&1; then
|
||||
log_and_display "Hello-world container: successfully started and completed"
|
||||
else
|
||||
log_and_display "Hello-world container: startup error"
|
||||
fi
|
||||
else
|
||||
log_and_display "Docker Hub: unavailable or blocked (possibly exceeded download limit)"
|
||||
log_and_display "Docker Hub has download limits, try again later"
|
||||
fi
|
||||
|
||||
log_and_display ""
|
||||
total_cont=$($SUD docker ps -aq 2>/dev/null | wc -l || echo "0")
|
||||
active_cont=$($SUD docker ps -q 2>/dev/null | wc -l || echo "0")
|
||||
amnezia_cont=$($SUD docker ps -a 2>/dev/null | grep -c amnezia || echo "0")
|
||||
|
||||
log_and_display "Containers check: Total $total_cont / Active $active_cont / Amnezia $amnezia_cont"
|
||||
$SUD docker ps -a --format "{{.Names}} ({{.Image}}) ({{.Status}}) ({{.Ports}})" 2>/dev/null | grep amnezia || true
|
||||
|
||||
# Peers check
|
||||
if $SUD docker ps 2>/dev/null | grep -qE '\<(amnezia-awg|amnezia-wireguard)\>'; then
|
||||
log_and_display ""
|
||||
log_and_display "Peers check (beta):"
|
||||
if $SUD docker ps 2>/dev/null | grep -q amnezia-awg; then
|
||||
AMNEZIA_WG_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-awg | awk '{print $1}' | head -1)
|
||||
if [ -n "$AMNEZIA_WG_CONTAINER" ]; then
|
||||
WG_PEERS=$($SUD docker exec -it "$AMNEZIA_WG_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||
log_and_display "AmneziaWG peers: $WG_PEERS"
|
||||
fi
|
||||
fi
|
||||
if $SUD docker ps 2>/dev/null | grep -q amnezia-wireguard; then
|
||||
WIREGUARD_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-wireguard | awk '{print $1}' | head -1)
|
||||
if [ -n "$WIREGUARD_CONTAINER" ]; then
|
||||
WG_PEERS=$($SUD docker exec -it "$WIREGUARD_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||
log_and_display "WireGuard peers: $WG_PEERS"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_and_display "skipped.."
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10. Additional improvements
|
||||
# ------------------------------------------------------------------------------
|
||||
#
|
||||
# 10.1. CPU and memory load check (Load average, top processes)
|
||||
#
|
||||
header "CPU & Memory usage (top)"
|
||||
|
||||
# Load average (last 1,5,15 minutes)
|
||||
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average:' '{print $2}' || echo "unknown")
|
||||
log_and_display "Load average: $LOAD_AVG"
|
||||
|
||||
log_and_display ""
|
||||
log_and_display "Top 5 processes by CPU:"
|
||||
ps aux 2>/dev/null | sort -k3 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting CPU processes"
|
||||
|
||||
log_and_display ""
|
||||
log_and_display "Top 5 processes by MEM:"
|
||||
ps aux 2>/dev/null | sort -k4 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting MEM processes"
|
||||
|
||||
# 10.2. System logs check (latest critical messages)
|
||||
header "Last 10 critical/error messages (journalctl)"
|
||||
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
journalctl -p 3 -n 10 --no-pager 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting system logs"
|
||||
else
|
||||
log_and_display "journalctl not found (non-systemd system?)"
|
||||
fi
|
||||
|
||||
# 10.3. System package versions check (examples)
|
||||
|
||||
# Open ports check
|
||||
header "Network Ports Check"
|
||||
if command -v netstat >/dev/null 2>&1; then
|
||||
log_and_display "Listening ports:"
|
||||
netstat -tlnp 2>/dev/null | grep LISTEN | head -10 | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
elif command -v ss >/dev/null 2>&1; then
|
||||
log_and_display "Listening ports:"
|
||||
ss -tlnp 2>/dev/null | head -10 | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display "netstat/ss not found"
|
||||
fi
|
||||
|
||||
# SSH check
|
||||
header "SSH Service Check"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
ssh_status=$(systemctl is-active ssh 2>/dev/null || systemctl is-active sshd 2>/dev/null || echo "not found")
|
||||
if [ "$ssh_status" = "active" ]; then
|
||||
log_and_display "SSH service: $ssh_status"
|
||||
else
|
||||
log_and_display "SSH service: $ssh_status"
|
||||
fi
|
||||
else
|
||||
log_and_display "systemctl not found"
|
||||
fi
|
||||
|
||||
# Time check
|
||||
header "Time Synchronization"
|
||||
if command -v timedatectl >/dev/null 2>&1; then
|
||||
timedatectl status 2>/dev/null | grep -E "System clock|NTP service" | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display " System time: $(date 2>/dev/null || echo 'unknown')"
|
||||
fi
|
||||
|
||||
# Kernel check
|
||||
header "Kernel Information"
|
||||
log_and_display "Kernel version: $(uname -r 2>/dev/null || echo 'unknown')"
|
||||
log_and_display "Kernel architecture: $(uname -m 2>/dev/null || echo 'unknown')"
|
||||
if [ -f /proc/cmdline ]; then
|
||||
log_and_display "Kernel parameters:"
|
||||
cat /proc/cmdline 2>/dev/null | tr ' ' '\n' | head -5 | while read param; do
|
||||
log_and_display " $param"
|
||||
done
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Completion
|
||||
# ------------------------------------------------------------------------------
|
||||
log_and_display ""
|
||||
header "FINISH"
|
||||
log_and_display ""
|
||||
log_and_display "Diagnostics completed. Log saved to: $LOG_FILE"
|
||||
log_and_display ""
|
||||
|
||||
# Variable cleanup
|
||||
pm="" && opt="" && docker_pkg="" && CUR_USER="" && USER_GOOD="" && USER_GROUP="" && PASSWD_REQUEST="" && CHECK_CONTAINERS="" && SUD="" && docker_service="" && docker_status="" && docker_loading=""
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM alpine:3.15
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG XRAY_RELEASE="v1.8.6"
|
||||
ARG XRAY_RELEASE="v25.8.3"
|
||||
|
||||
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
|
||||
RUN apk --update upgrade --no-cache
|
||||
|
||||
@@ -541,12 +541,12 @@ QString Settings::getGatewayEndpoint()
|
||||
|
||||
bool Settings::isDevGatewayEnv()
|
||||
{
|
||||
return m_isDevGatewayEnv;
|
||||
return value("Conf/devGatewayEnv", false).toBool();
|
||||
}
|
||||
|
||||
void Settings::toggleDevGatewayEnv(bool enabled)
|
||||
{
|
||||
m_isDevGatewayEnv = enabled;
|
||||
setValue("Conf/devGatewayEnv", enabled);
|
||||
}
|
||||
|
||||
bool Settings::isHomeAdLabelVisible()
|
||||
@@ -578,3 +578,13 @@ void Settings::setAllowedDnsServers(const QStringList &servers)
|
||||
{
|
||||
setValue("Conf/allowedDnsServers", servers);
|
||||
}
|
||||
|
||||
QStringList Settings::readNewsIds() const
|
||||
{
|
||||
return value("News/readIds").toStringList();
|
||||
}
|
||||
|
||||
void Settings::setReadNewsIds(const QStringList &ids)
|
||||
{
|
||||
setValue("News/readIds", ids);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
|
||||
QLocale getAppLanguage()
|
||||
{
|
||||
QString localeStr = m_settings.value("Conf/appLanguage").toString();
|
||||
QString localeStr = m_settings.value("Conf/appLanguage", QLocale::system().name()).toString();
|
||||
return QLocale(localeStr);
|
||||
};
|
||||
void setAppLanguage(QLocale locale)
|
||||
@@ -236,6 +236,9 @@ public:
|
||||
QStringList allowedDnsServers() const;
|
||||
void setAllowedDnsServers(const QStringList &servers);
|
||||
|
||||
QStringList readNewsIds() const;
|
||||
void setReadNewsIds(const QStringList &ids);
|
||||
|
||||
signals:
|
||||
void saveLogsChanged(bool enabled);
|
||||
void screenshotsEnabledChanged(bool enabled);
|
||||
@@ -251,7 +254,6 @@ private:
|
||||
mutable SecureQSettings m_settings;
|
||||
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevGatewayEnv = false;
|
||||
};
|
||||
|
||||
#endif // SETTINGS_H
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace
|
||||
constexpr char authData[] = "auth_data";
|
||||
|
||||
constexpr char config[] = "config";
|
||||
|
||||
constexpr char subscription[] = "subscription";
|
||||
constexpr char endDate[] = "end_date";
|
||||
|
||||
constexpr char isConnectEvent[] = "is_connect_event";
|
||||
}
|
||||
|
||||
struct ProtocolData
|
||||
@@ -59,6 +64,7 @@ namespace
|
||||
{
|
||||
QString osVersion;
|
||||
QString appVersion;
|
||||
QString appLanguage;
|
||||
|
||||
QString installationUuid;
|
||||
|
||||
@@ -78,6 +84,9 @@ namespace
|
||||
if (!appVersion.isEmpty()) {
|
||||
obj[configKey::appVersion] = appVersion;
|
||||
}
|
||||
if (!appLanguage.isEmpty()) {
|
||||
obj[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
if (!installationUuid.isEmpty()) {
|
||||
obj[configKey::uuid] = installationUuid;
|
||||
}
|
||||
@@ -163,7 +172,7 @@ namespace
|
||||
auto clientProtocolConfig =
|
||||
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
|
||||
//TODO looks like this block can be removed after v1 configs EOL
|
||||
// TODO looks like this block can be removed after v1 configs EOL
|
||||
|
||||
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
|
||||
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
|
||||
@@ -217,12 +226,28 @@ namespace
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols,
|
||||
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
|
||||
|
||||
apiConfig.insert(apiDefs::key::serviceInfo,
|
||||
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject());
|
||||
}
|
||||
|
||||
serverConfig[configKey::apiConfig] = apiConfig;
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
bool isSubscriptionExpired(const QJsonObject &apiConfig)
|
||||
{
|
||||
auto subscription = apiConfig.value(configKey::subscription).toObject();
|
||||
if (subscription.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
|
||||
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
||||
@@ -232,6 +257,23 @@ ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &s
|
||||
{
|
||||
}
|
||||
|
||||
bool ApiConfigsController::exportVpnKey(const QString &fileName)
|
||||
{
|
||||
if (fileName.isEmpty()) {
|
||||
emit errorOccurred(ErrorCode::PermissionsError);
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareVpnKeyExport();
|
||||
if (m_vpnKey.isEmpty()) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return false;
|
||||
}
|
||||
|
||||
SystemController::saveFile(fileName, m_vpnKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
||||
{
|
||||
if (fileName.isEmpty()) {
|
||||
@@ -242,8 +284,14 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
@@ -277,8 +325,14 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
@@ -303,6 +357,13 @@ void ApiConfigsController::prepareVpnKeyExport()
|
||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||
|
||||
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
||||
if (vpnKey.isEmpty()) {
|
||||
vpnKey = apiUtils::getPremiumV2VpnKey(serverConfigObject);
|
||||
apiConfigObject.insert(apiDefs::key::vpnKey, vpnKey);
|
||||
serverConfigObject.insert(configKey::apiConfig, apiConfigObject);
|
||||
m_serversModel->editServer(serverConfigObject, m_serversModel->getProcessedServerIndex());
|
||||
}
|
||||
|
||||
m_vpnKey = vpnKey;
|
||||
|
||||
vpnKey.replace("vpn://", "");
|
||||
@@ -322,6 +383,7 @@ bool ApiConfigsController::fillAvailableServices()
|
||||
{
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||
@@ -345,6 +407,7 @@ bool ApiConfigsController::importServiceFromGateway()
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
m_apiServicesModel->getCountryCode(),
|
||||
"",
|
||||
@@ -396,8 +459,14 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfig)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfig.value(configKey::userCountryCode).toString(),
|
||||
newCountryCode,
|
||||
@@ -410,6 +479,10 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
if (newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig) {
|
||||
apiPayload.insert(configKey::isConnectEvent, true);
|
||||
}
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
|
||||
@@ -429,6 +502,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
|
||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
||||
newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
|
||||
|
||||
if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) {
|
||||
newServerConfig.insert(config_key::name, serverConfig.value(config_key::name));
|
||||
@@ -492,7 +566,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||
}
|
||||
}
|
||||
|
||||
bool ApiConfigsController::deactivateDevice()
|
||||
bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
|
||||
{
|
||||
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
||||
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||
@@ -502,8 +576,18 @@ bool ApiConfigsController::deactivateDevice()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
if (isRemoveEvent) {
|
||||
return true;
|
||||
} else {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
apiConfigObject.value(configKey::serverCountryCode).toString(),
|
||||
@@ -536,8 +620,14 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
uuid,
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
public slots:
|
||||
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
||||
bool revokeNativeConfig(const QString &serverCountryCode);
|
||||
// bool exportVpnKey(const QString &fileName);
|
||||
bool exportVpnKey(const QString &fileName);
|
||||
void prepareVpnKeyExport();
|
||||
void copyVpnKeyToClipboard();
|
||||
|
||||
@@ -30,7 +30,7 @@ public slots:
|
||||
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||
bool reloadServiceConfig = false);
|
||||
bool updateServiceFromTelegram(const int serverIndex);
|
||||
bool deactivateDevice();
|
||||
bool deactivateDevice(const bool isRemoveEvent);
|
||||
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||
|
||||
bool isConfigValid();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "apiNewsController.h"
|
||||
|
||||
#include "core/api/apiUtils.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
{
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serviceType[] = "service_type";
|
||||
}
|
||||
}
|
||||
|
||||
ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<ServersModel> &serversModel, QObject *parent)
|
||||
: QObject(parent), m_newsModel(newsModel), m_settings(settings), m_serversModel(serversModel)
|
||||
{
|
||||
}
|
||||
|
||||
void ApiNewsController::fetchNews()
|
||||
{
|
||||
if (m_serversModel.isNull()) {
|
||||
qWarning() << "ServersModel is null, skip fetchNews";
|
||||
return;
|
||||
}
|
||||
const auto stacks = m_serversModel->gatewayStacks();
|
||||
if (stacks.isEmpty()) {
|
||||
qDebug() << "No Gateway stacks, skip fetchNews";
|
||||
return;
|
||||
}
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||
|
||||
const QJsonObject stacksJson = stacks.toJson();
|
||||
if (stacksJson.contains(configKey::userCountryCode)) {
|
||||
payload.insert(configKey::userCountryCode, stacksJson.value(configKey::userCountryCode));
|
||||
}
|
||||
if (stacksJson.contains(configKey::serviceType)) {
|
||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||
}
|
||||
|
||||
auto future = gatewayController.postAsync(QString("%1v1/news"), payload);
|
||||
future.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [errorCode, responseBody] = result;
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit errorOccurred(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||
QJsonArray newsArray;
|
||||
if (doc.isArray()) {
|
||||
newsArray = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.value("news").isArray()) {
|
||||
newsArray = obj.value("news").toArray();
|
||||
}
|
||||
}
|
||||
|
||||
m_newsModel->updateModel(newsArray);
|
||||
emit fetchNewsFinished();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef APINEWSCONTROLLER_H
|
||||
#define APINEWSCONTROLLER_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <memory>
|
||||
|
||||
#include "core/api/apiDefs.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "settings.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
|
||||
class ApiNewsController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<ServersModel> &serversModel, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void fetchNews();
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
void fetchNewsFinished();
|
||||
|
||||
private:
|
||||
QSharedPointer<NewsModel> m_newsModel;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
};
|
||||
|
||||
#endif // APINEWSCONTROLLER_H
|
||||
@@ -62,6 +62,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
||||
apiPayload[configKey::authData] = authData;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "connectionController.h"
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
@@ -32,8 +32,9 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||
|
||||
void ConnectionController::openConnection()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
|
||||
{
|
||||
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -297,10 +297,11 @@ void ExportController::revokeConfig(const int row, const DockerContainer contain
|
||||
{
|
||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||
ErrorCode errorCode =
|
||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit exportErrorOccurred(errorCode);
|
||||
}
|
||||
emit revokeConfigCompleted();
|
||||
}
|
||||
|
||||
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
|
||||
|
||||
@@ -42,6 +42,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
void generateConfig(int type);
|
||||
void revokeConfigCompleted();
|
||||
void exportErrorOccurred(const QString &errorMessage);
|
||||
void exportErrorOccurred(ErrorCode errorCode);
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("FocusController");
|
||||
}
|
||||
|
||||
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
|
||||
: QObject { parent },
|
||||
m_engine { engine },
|
||||
@@ -85,7 +91,7 @@ void FocusController::dropRootObject(QObject *object)
|
||||
dropListView();
|
||||
setFocusOnDefaultItem();
|
||||
} else {
|
||||
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
|
||||
logger.warning() << "TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +107,7 @@ void FocusController::reload(Direction direction)
|
||||
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
|
||||
|
||||
if (!rootObject) {
|
||||
qCritical() << "No ROOT OBJECT found!";
|
||||
logger.error() << "No ROOT OBJECT found!";
|
||||
resetRootObject();
|
||||
dropListView();
|
||||
return;
|
||||
@@ -113,7 +119,7 @@ void FocusController::reload(Direction direction)
|
||||
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore);
|
||||
|
||||
if (m_focusChain.empty()) {
|
||||
qWarning() << "Focus chain is empty!";
|
||||
logger.warning() << "Focus chain is empty!";
|
||||
resetRootObject();
|
||||
dropListView();
|
||||
return;
|
||||
@@ -131,7 +137,7 @@ void FocusController::nextItem(Direction direction)
|
||||
}
|
||||
|
||||
if (m_focusChain.empty()) {
|
||||
qWarning() << "There are no items to navigate";
|
||||
logger.warning() << "There are no items to navigate";
|
||||
setFocusOnDefaultItem();
|
||||
return;
|
||||
}
|
||||
@@ -149,7 +155,7 @@ void FocusController::nextItem(Direction direction)
|
||||
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
|
||||
|
||||
if (focusedItem == nullptr) {
|
||||
qWarning() << "Failed to get item to focus on. Setting focus on default";
|
||||
logger.warning() << "Failed to get item to focus on. Setting focus on default";
|
||||
setFocusOnDefaultItem();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
#ifdef Q_OS_IOS
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
@@ -274,7 +274,7 @@ void ImportController::processNativeWireGuardConfig()
|
||||
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
||||
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
||||
@@ -595,7 +595,7 @@ void ImportController::startDecodingQr()
|
||||
m_totalQrCodeChunksCount = 0;
|
||||
m_receivedQrCodeChunksCount = 0;
|
||||
|
||||
#if defined Q_OS_IOS
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
m_isQrCodeProcessed = true;
|
||||
#endif
|
||||
#if defined Q_OS_ANDROID
|
||||
|
||||
@@ -73,7 +73,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
||||
containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
|
||||
@@ -403,9 +403,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QJsonObject config;
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
const auto &protocols = ContainerProps::protocolsForContainer(container);
|
||||
|
||||
for (const auto &protocol : protocols) {
|
||||
QJsonObject containerConfig;
|
||||
if (protocol == mainProto) {
|
||||
|
||||
// for Multiprotocols (OpenVPN over SS, OpenVPN over Cloak)
|
||||
bool shouldProcessProtocol = false;
|
||||
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
|
||||
shouldProcessProtocol = true;
|
||||
} else {
|
||||
shouldProcessProtocol = (protocol == mainProto);
|
||||
}
|
||||
|
||||
if (shouldProcessProtocol) {
|
||||
containerConfig.insert(config_key::port, port);
|
||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
||||
|
||||
@@ -550,14 +560,97 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
qDebug() << siteName;
|
||||
|
||||
containerConfig.insert(config_key::site, siteName);
|
||||
} else if (protocol == Proto::OpenVpn) {
|
||||
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||
protocols::openvpn::serverConfigPath, errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
for (auto &line : serverConfigLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" ");
|
||||
if (parts.count() >= 2) {
|
||||
QString key = parts[0];
|
||||
QString value = parts.mid(1).join(" ");
|
||||
serverConfigMap.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString serverValue = serverConfigMap.value("server");
|
||||
|
||||
if (!serverValue.isEmpty()) {
|
||||
QStringList serverParts = serverValue.split(" ");
|
||||
if (serverParts.count() >= 1) {
|
||||
containerConfig[config_key::subnet_address] = serverParts[0];
|
||||
}
|
||||
}
|
||||
|
||||
bool ncpDisable = serverConfig.contains("ncp-disable");
|
||||
containerConfig[config_key::ncp_disable] = ncpDisable;
|
||||
|
||||
bool tlsAuth = serverConfig.contains("tls-auth");
|
||||
containerConfig[config_key::tls_auth] = tlsAuth;
|
||||
|
||||
bool blockOutsideDns = serverConfig.contains("block-outside-dns");
|
||||
|
||||
containerConfig[config_key::block_outside_dns] = blockOutsideDns;
|
||||
|
||||
QString cipher = serverConfigMap.value("cipher");
|
||||
if (!cipher.isEmpty()) {
|
||||
containerConfig[config_key::cipher] = cipher;
|
||||
}
|
||||
|
||||
QString hash = serverConfigMap.value("auth");
|
||||
if (!hash.isEmpty()) {
|
||||
containerConfig[config_key::hash] = hash;
|
||||
}
|
||||
} else if (protocol == Proto::Cloak) {
|
||||
QString cloakConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||
"/opt/amnezia/cloak/ck-config.json", errorCode);
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(cloakConfig.toUtf8());
|
||||
|
||||
if (!doc.isNull() && doc.isObject()) {
|
||||
QJsonObject cloakConfigObj = doc.object();
|
||||
|
||||
QString site = cloakConfigObj.value("RedirAddr").toString();
|
||||
if (!site.isEmpty()) {
|
||||
containerConfig[config_key::site] = site;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Failed to parse main loop Cloak JSON config";
|
||||
}
|
||||
|
||||
} else if (protocol == Proto::ShadowSocks) {
|
||||
QString shadowsocksConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||
"/opt/amnezia/shadowsocks/ss-config.json", errorCode);
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8());
|
||||
|
||||
if (!doc.isNull() && doc.isObject()) {
|
||||
QJsonObject ssConfigObj = doc.object();
|
||||
QString cipher = ssConfigObj.value("method").toString();
|
||||
if (!cipher.isEmpty()) {
|
||||
containerConfig[config_key::cipher] = cipher;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Failed to parse main loop Shadowsocks JSON config";
|
||||
}
|
||||
}
|
||||
|
||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
}
|
||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
||||
if (shouldProcessProtocol) {
|
||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
||||
}
|
||||
}
|
||||
installedContainers.insert(container, config);
|
||||
}
|
||||
|
||||
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
|
||||
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
|
||||
if (torOrDnsRegMatch.hasMatch()) {
|
||||
@@ -721,6 +814,8 @@ void InstallController::clearCachedProfile(QSharedPointer<ServerController> serv
|
||||
m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex, serverController);
|
||||
|
||||
emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container)));
|
||||
QJsonObject updatedConfig = m_settings->containerConfig(serverIndex, container);
|
||||
emit profileCleared(updatedConfig);
|
||||
}
|
||||
|
||||
QRegularExpression InstallController::ipAddressPortRegExp()
|
||||
|
||||