diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d9138516..64a4986d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,10 @@ jobs: QT_VERSION: 6.6.2 QIF_VERSION: 4.7 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 }} steps: - name: 'Install Qt' @@ -83,7 +86,10 @@ jobs: QIF_VERSION: 4.7 BUILD_ARCH: 64 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 }} steps: - name: 'Get sources' @@ -146,7 +152,10 @@ jobs: CC: cc CXX: c++ 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 }} steps: - name: 'Setup xcode' @@ -238,7 +247,10 @@ jobs: QT_VERSION: 6.4.3 QIF_VERSION: 4.6 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 }} steps: - name: 'Setup xcode' @@ -301,10 +313,13 @@ jobs: env: ANDROID_BUILD_PLATFORM: android-34 - QT_VERSION: 6.7.2 + QT_VERSION: 6.7.3 QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' 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 }} steps: - name: 'Install desktop Qt' diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml index dffb3ab1..2bcbd8c6 100644 --- a/.github/workflows/tag-deploy.yml +++ b/.github/workflows/tag-deploy.yml @@ -16,7 +16,10 @@ jobs: QT_VERSION: 6.4.1 QIF_VERSION: 4.5 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 }} steps: - name: 'Install desktop Qt' diff --git a/CMakeLists.txt b/CMakeLists.txt index 79b0c18c..ce5777e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.1.0 +project(${PROJECT} VERSION 4.8.2.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 62) +set(APP_ANDROID_VERSION_CODE 2067) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/3rd/OpenVPNAdapter b/client/3rd/OpenVPNAdapter index dea60409..7c821a8d 160000 --- a/client/3rd/OpenVPNAdapter +++ b/client/3rd/OpenVPNAdapter @@ -1 +1 @@ -Subproject commit dea6040996298e947d63fb172709e6abfec2ba93 +Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b diff --git a/client/3rd/SingleApplication/singleapplication.cmake b/client/3rd/SingleApplication/singleapplication.cmake deleted file mode 100644 index 78abfa8a..00000000 --- a/client/3rd/SingleApplication/singleapplication.cmake +++ /dev/null @@ -1,25 +0,0 @@ -include_directories(${CMAKE_CURRENT_LIST_DIR}) - -find_package(Qt6 REQUIRED COMPONENTS - Core Network -) -set(LIBS ${LIBS} Qt6::Core Qt6::Network) - - -set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/singleapplication.h - ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h -) - -set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp - ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp -) - -if(WIN32) - if(MSVC) - set(LIBS ${LIBS} Advapi32.lib) - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(LIBS ${LIBS} advapi32) - endif() -endif() diff --git a/client/3rd/SingleApplication/singleapplication.cpp b/client/3rd/SingleApplication/singleapplication.cpp deleted file mode 100644 index 7e153a00..00000000 --- a/client/3rd/SingleApplication/singleapplication.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include -#include -#include - -#include "singleapplication.h" -#include "singleapplication_p.h" - -/** - * @brief Constructor. Checks and fires up LocalServer or closes the program - * if another instance already exists - * @param argc - * @param argv - * @param allowSecondary Whether to enable secondary instance support - * @param options Optional flags to toggle specific behaviour - * @param timeout Maximum time blocking functions are allowed during app load - */ -SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData ) - : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) -{ - Q_D( SingleApplication ); - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - // On Android and iOS since the library is not supported fallback to - // standard QApplication behaviour by simply returning at this point. - qWarning() << "SingleApplication is not supported on Android and iOS systems."; - return; -#endif - - // Store the current mode of the program - d->options = options; - - // Add any unique user data - if ( ! userData.isEmpty() ) - d->addAppData( userData ); - - // Generating an application ID used for identifying the shared memory - // block and QLocalServer - d->genBlockServerName(); - - // To mitigate QSharedMemory issues with large amount of processes - // attempting to attach at the same time - SingleApplicationPrivate::randomSleep(); - -#ifdef Q_OS_UNIX - // By explicitly attaching it and then deleting it we make sure that the - // memory is deleted even after the process has crashed on Unix. - d->memory = new QSharedMemory( d->blockServerName ); - d->memory->attach(); - delete d->memory; -#endif - // Guarantee thread safe behaviour with a shared memory block. - d->memory = new QSharedMemory( d->blockServerName ); - - // Create a shared memory block - if( d->memory->create( sizeof( InstancesInfo ) )){ - // Initialize the shared memory block - if( ! d->memory->lock() ){ - qCritical() << "SingleApplication: Unable to lock memory block after create."; - abortSafely(); - } - d->initializeMemoryBlock(); - } else { - if( d->memory->error() == QSharedMemory::AlreadyExists ){ - // Attempt to attach to the memory segment - if( ! d->memory->attach() ){ - qCritical() << "SingleApplication: Unable to attach to shared memory block."; - abortSafely(); - } - if( ! d->memory->lock() ){ - qCritical() << "SingleApplication: Unable to lock memory block after attach."; - abortSafely(); - } - } else { - qCritical() << "SingleApplication: Unable to create block."; - abortSafely(); - } - } - - auto *inst = static_cast( d->memory->data() ); - QElapsedTimer time; - time.start(); - - // Make sure the shared memory block is initialised and in consistent state - while( true ){ - // If the shared memory block's checksum is valid continue - if( d->blockChecksum() == inst->checksum ) break; - - // If more than 5s have elapsed, assume the primary instance crashed and - // assume it's position - if( time.elapsed() > 5000 ){ - qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; - d->initializeMemoryBlock(); - } - - // Otherwise wait for a random period and try again. The random sleep here - // limits the probability of a collision between two racing apps and - // allows the app to initialise faster - if( ! d->memory->unlock() ){ - qDebug() << "SingleApplication: Unable to unlock memory for random wait."; - qDebug() << d->memory->errorString(); - } - SingleApplicationPrivate::randomSleep(); - if( ! d->memory->lock() ){ - qCritical() << "SingleApplication: Unable to lock memory after random wait."; - abortSafely(); - } - } - - if( inst->primary == false ){ - d->startPrimary(); - if( ! d->memory->unlock() ){ - qDebug() << "SingleApplication: Unable to unlock memory after primary start."; - qDebug() << d->memory->errorString(); - } - return; - } - - // Check if another instance can be started - if( allowSecondary ){ - d->startSecondary(); - if( d->options & Mode::SecondaryNotification ){ - d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance ); - } - if( ! d->memory->unlock() ){ - qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; - qDebug() << d->memory->errorString(); - } - return; - } - - if( ! d->memory->unlock() ){ - qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; - qDebug() << d->memory->errorString(); - } - - d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); - - delete d; - - ::exit( EXIT_SUCCESS ); -} - -SingleApplication::~SingleApplication() -{ - Q_D( SingleApplication ); - delete d; -} - -/** - * Checks if the current application instance is primary. - * @return Returns true if the instance is primary, false otherwise. - */ -bool SingleApplication::isPrimary() const -{ - Q_D( const SingleApplication ); - return d->server != nullptr; -} - -/** - * Checks if the current application instance is secondary. - * @return Returns true if the instance is secondary, false otherwise. - */ -bool SingleApplication::isSecondary() const -{ - Q_D( const SingleApplication ); - return d->server == nullptr; -} - -/** - * Allows you to identify an instance by returning unique consecutive instance - * ids. It is reset when the first (primary) instance of your app starts and - * only incremented afterwards. - * @return Returns a unique instance id. - */ -quint32 SingleApplication::instanceId() const -{ - Q_D( const SingleApplication ); - return d->instanceNumber; -} - -/** - * Returns the OS PID (Process Identifier) of the process running the primary - * instance. Especially useful when SingleApplication is coupled with OS. - * specific APIs. - * @return Returns the primary instance PID. - */ -qint64 SingleApplication::primaryPid() const -{ - Q_D( const SingleApplication ); - return d->primaryPid(); -} - -/** - * Returns the username the primary instance is running as. - * @return Returns the username the primary instance is running as. - */ -QString SingleApplication::primaryUser() const -{ - Q_D( const SingleApplication ); - return d->primaryUser(); -} - -/** - * Returns the username the current instance is running as. - * @return Returns the username the current instance is running as. - */ -QString SingleApplication::currentUser() const -{ - return SingleApplicationPrivate::getUsername(); -} - -/** - * Sends message to the Primary Instance. - * @param message The message to send. - * @param timeout the maximum timeout in milliseconds for blocking functions. - * @return true if the message was sent successfuly, false otherwise. - */ -bool SingleApplication::sendMessage( const QByteArray &message, int timeout ) -{ - Q_D( SingleApplication ); - - // Nobody to connect to - if( isPrimary() ) return false; - - // Make sure the socket is connected - if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) ) - return false; - - d->socket->write( message ); - bool dataWritten = d->socket->waitForBytesWritten( timeout ); - d->socket->flush(); - return dataWritten; -} - -/** - * Cleans up the shared memory block and exits with a failure. - * This function halts program execution. - */ -void SingleApplication::abortSafely() -{ - Q_D( SingleApplication ); - - qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString(); - delete d; - ::exit( EXIT_FAILURE ); -} - -QStringList SingleApplication::userData() const -{ - Q_D( const SingleApplication ); - return d->appData(); -} diff --git a/client/3rd/SingleApplication/singleapplication.h b/client/3rd/SingleApplication/singleapplication.h deleted file mode 100644 index 400c88ac..00000000 --- a/client/3rd/SingleApplication/singleapplication.h +++ /dev/null @@ -1,154 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2018 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#ifndef SINGLE_APPLICATION_H -#define SINGLE_APPLICATION_H - -#include -#include - -#ifndef QAPPLICATION_CLASS - #define QAPPLICATION_CLASS QApplication -#endif - -#include QT_STRINGIFY(QAPPLICATION_CLASS) - -class SingleApplicationPrivate; - -/** - * @brief The SingleApplication class handles multiple instances of the same - * Application - * @see QCoreApplication - */ -class SingleApplication : public QAPPLICATION_CLASS -{ - Q_OBJECT - - using app_t = QAPPLICATION_CLASS; - -public: - /** - * @brief Mode of operation of SingleApplication. - * Whether the block should be user-wide or system-wide and whether the - * primary instance should be notified when a secondary instance had been - * started. - * @note Operating system can restrict the shared memory blocks to the same - * user, in which case the User/System modes will have no effect and the - * block will be user wide. - * @enum - */ - enum Mode { - User = 1 << 0, - System = 1 << 1, - SecondaryNotification = 1 << 2, - ExcludeAppVersion = 1 << 3, - ExcludeAppPath = 1 << 4 - }; - Q_DECLARE_FLAGS(Options, Mode) - - /** - * @brief Intitializes a SingleApplication instance with argc command line - * arguments in argv - * @arg {int &} argc - Number of arguments in argv - * @arg {const char *[]} argv - Supplied command line arguments - * @arg {bool} allowSecondary - Whether to start the instance as secondary - * if there is already a primary instance. - * @arg {Mode} mode - Whether for the SingleApplication block to be applied - * User wide or System wide. - * @arg {int} timeout - Timeout to wait in milliseconds. - * @note argc and argv may be changed as Qt removes arguments that it - * recognizes - * @note Mode::SecondaryNotification only works if set on both the primary - * instance and the secondary instance. - * @note The timeout is just a hint for the maximum time of blocking - * operations. It does not guarantee that the SingleApplication - * initialisation will be completed in given time, though is a good hint. - * Usually 4*timeout would be the worst case (fail) scenario. - * @see See the corresponding QAPPLICATION_CLASS constructor for reference - */ - explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} ); - ~SingleApplication() override; - - /** - * @brief Returns if the instance is the primary instance - * @returns {bool} - */ - bool isPrimary() const; - - /** - * @brief Returns if the instance is a secondary instance - * @returns {bool} - */ - bool isSecondary() const; - - /** - * @brief Returns a unique identifier for the current instance - * @returns {qint32} - */ - quint32 instanceId() const; - - /** - * @brief Returns the process ID (PID) of the primary instance - * @returns {qint64} - */ - qint64 primaryPid() const; - - /** - * @brief Returns the username of the user running the primary instance - * @returns {QString} - */ - QString primaryUser() const; - - /** - * @brief Returns the username of the current user - * @returns {QString} - */ - QString currentUser() const; - - /** - * @brief Sends a message to the primary instance. Returns true on success. - * @param {int} timeout - Timeout for connecting - * @returns {bool} - * @note sendMessage() will return false if invoked from the primary - * instance. - */ - bool sendMessage( const QByteArray &message, int timeout = 100 ); - - /** - * @brief Get the set user data. - * @returns {QStringList} - */ - QStringList userData() const; - -Q_SIGNALS: - void instanceStarted(); - void receivedMessage( quint32 instanceId, QByteArray message ); - -private: - SingleApplicationPrivate *d_ptr; - Q_DECLARE_PRIVATE(SingleApplication) - void abortSafely(); -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) - -#endif // SINGLE_APPLICATION_H diff --git a/client/3rd/SingleApplication/singleapplication.pri b/client/3rd/SingleApplication/singleapplication.pri deleted file mode 100644 index 80283fc4..00000000 --- a/client/3rd/SingleApplication/singleapplication.pri +++ /dev/null @@ -1,15 +0,0 @@ -QT += core network -CONFIG += c++11 - -HEADERS += \ - $$PWD/singleapplication.h \ - $$PWD/singleapplication_p.h -SOURCES += $$PWD/singleapplication.cpp \ - $$PWD/singleapplication_p.cpp - -INCLUDEPATH += $$PWD - -win32 { - msvc:LIBS += Advapi32.lib - gcc:LIBS += -ladvapi32 -} diff --git a/client/3rd/SingleApplication/singleapplication_p.cpp b/client/3rd/SingleApplication/singleapplication_p.cpp deleted file mode 100644 index e65bd955..00000000 --- a/client/3rd/SingleApplication/singleapplication_p.cpp +++ /dev/null @@ -1,486 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This file is not part of the SingleApplication API. It is used purely as an -// implementation detail. This header file may change from version to -// version without notice, or may even be removed. -// - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) -#include -#else -#include -#endif - -#include "singleapplication.h" -#include "singleapplication_p.h" - -#ifdef Q_OS_UNIX - #include - #include - #include -#endif - -#ifdef Q_OS_WIN - #ifndef NOMINMAX - #define NOMINMAX 1 - #endif - #include - #include -#endif - -SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) - : q_ptr( q_ptr ) -{ - server = nullptr; - socket = nullptr; - memory = nullptr; - instanceNumber = 0; -} - -SingleApplicationPrivate::~SingleApplicationPrivate() -{ - if( socket != nullptr ){ - socket->close(); - delete socket; - } - - if( memory != nullptr ){ - memory->lock(); - auto *inst = static_cast(memory->data()); - if( server != nullptr ){ - server->close(); - delete server; - inst->primary = false; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); - } - memory->unlock(); - - delete memory; - } -} - -QString SingleApplicationPrivate::getUsername() -{ -#ifdef Q_OS_WIN - wchar_t username[UNLEN + 1]; - // Specifies size of the buffer on input - DWORD usernameLength = UNLEN + 1; - if( GetUserNameW( username, &usernameLength ) ) - return QString::fromWCharArray( username ); -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - return QString::fromLocal8Bit( qgetenv( "USERNAME" ) ); -#else - return qEnvironmentVariable( "USERNAME" ); -#endif -#endif -#ifdef Q_OS_UNIX - QString username; - uid_t uid = geteuid(); - struct passwd *pw = getpwuid( uid ); - if( pw ) - username = QString::fromLocal8Bit( pw->pw_name ); - if ( username.isEmpty() ){ -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - username = QString::fromLocal8Bit( qgetenv( "USER" ) ); -#else - username = qEnvironmentVariable( "USER" ); -#endif - } - return username; -#endif -} - -void SingleApplicationPrivate::genBlockServerName() -{ - QCryptographicHash appData( QCryptographicHash::Sha256 ); - appData.addData( "SingleApplication", 17 ); - appData.addData( SingleApplication::app_t::applicationName().toUtf8() ); - appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); - appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); - - if ( ! appDataList.isEmpty() ) - appData.addData( appDataList.join( "" ).toUtf8() ); - - if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){ - appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); - } - - if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){ -#ifdef Q_OS_WIN - appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() ); -#else - appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); -#endif - } - - // User level block requires a user specific data in the hash - if( options & SingleApplication::Mode::User ){ - appData.addData( getUsername().toUtf8() ); - } - - // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with - // server naming requirements. - blockServerName = appData.result().toBase64().replace("/", "_"); -} - -void SingleApplicationPrivate::initializeMemoryBlock() const -{ - auto *inst = static_cast( memory->data() ); - inst->primary = false; - inst->secondary = 0; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); -} - -void SingleApplicationPrivate::startPrimary() -{ - // Reset the number of connections - auto *inst = static_cast ( memory->data() ); - - inst->primary = true; - inst->primaryPid = QCoreApplication::applicationPid(); - qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) ); - inst->checksum = blockChecksum(); - instanceNumber = 0; - // Successful creation means that no main process exists - // So we start a QLocalServer to listen for connections - QLocalServer::removeServer( blockServerName ); - server = new QLocalServer(); - - // Restrict access to the socket according to the - // SingleApplication::Mode::User flag on User level or no restrictions - if( options & SingleApplication::Mode::User ){ - server->setSocketOptions( QLocalServer::UserAccessOption ); - } else { - server->setSocketOptions( QLocalServer::WorldAccessOption ); - } - - server->listen( blockServerName ); - QObject::connect( - server, - &QLocalServer::newConnection, - this, - &SingleApplicationPrivate::slotConnectionEstablished - ); -} - -void SingleApplicationPrivate::startSecondary() -{ - auto *inst = static_cast ( memory->data() ); - - inst->secondary += 1; - inst->checksum = blockChecksum(); - instanceNumber = inst->secondary; -} - -bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) -{ - QElapsedTimer time; - time.start(); - - // Connect to the Local Server of the Primary Instance if not already - // connected. - if( socket == nullptr ){ - socket = new QLocalSocket(); - } - - if( socket->state() == QLocalSocket::ConnectedState ) return true; - - if( socket->state() != QLocalSocket::ConnectedState ){ - - while( true ){ - randomSleep(); - - if( socket->state() != QLocalSocket::ConnectingState ) - socket->connectToServer( blockServerName ); - - if( socket->state() == QLocalSocket::ConnectingState ){ - socket->waitForConnected( static_cast(msecs - time.elapsed()) ); - } - - // If connected break out of the loop - if( socket->state() == QLocalSocket::ConnectedState ) break; - - // If elapsed time since start is longer than the method timeout return - if( time.elapsed() >= msecs ) return false; - } - } - - // Initialisation message according to the SingleApplication protocol - QByteArray initMsg; - QDataStream writeStream(&initMsg, QIODevice::WriteOnly); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - writeStream.setVersion(QDataStream::Qt_5_6); -#endif - - writeStream << blockServerName.toLatin1(); - writeStream << static_cast(connectionType); - writeStream << instanceNumber; -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); -#else - quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); -#endif - writeStream << checksum; - - // The header indicates the message length that follows - QByteArray header; - QDataStream headerStream(&header, QIODevice::WriteOnly); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - headerStream.setVersion(QDataStream::Qt_5_6); -#endif - headerStream << static_cast ( initMsg.length() ); - - socket->write( header ); - socket->write( initMsg ); - bool result = socket->waitForBytesWritten( static_cast(msecs - time.elapsed()) ); - socket->flush(); - return result; -} - -quint16 SingleApplicationPrivate::blockChecksum() const -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum))); -#else - quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)); -#endif - return checksum; -} - -qint64 SingleApplicationPrivate::primaryPid() const -{ - qint64 pid; - - memory->lock(); - auto *inst = static_cast( memory->data() ); - pid = inst->primaryPid; - memory->unlock(); - - return pid; -} - -QString SingleApplicationPrivate::primaryUser() const -{ - QByteArray username; - - memory->lock(); - auto *inst = static_cast( memory->data() ); - username = inst->primaryUser; - memory->unlock(); - - return QString::fromUtf8( username ); -} - -/** - * @brief Executed when a connection has been made to the LocalServer - */ -void SingleApplicationPrivate::slotConnectionEstablished() -{ - QLocalSocket *nextConnSocket = server->nextPendingConnection(); - connectionMap.insert(nextConnSocket, ConnectionInfo()); - - QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, - [nextConnSocket, this](){ - auto &info = connectionMap[nextConnSocket]; - Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); - } - ); - - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); - - QObject::connect(nextConnSocket, &QLocalSocket::destroyed, - [nextConnSocket, this](){ - connectionMap.remove(nextConnSocket); - } - ); - - QObject::connect(nextConnSocket, &QLocalSocket::readyRead, - [nextConnSocket, this](){ - auto &info = connectionMap[nextConnSocket]; - switch(info.stage){ - case StageHeader: - readInitMessageHeader(nextConnSocket); - break; - case StageBody: - readInitMessageBody(nextConnSocket); - break; - case StageConnected: - Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId ); - break; - default: - break; - }; - } - ); -} - -void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock ) -{ - if (!connectionMap.contains( sock )){ - return; - } - - if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){ - return; - } - - QDataStream headerStream( sock ); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - headerStream.setVersion( QDataStream::Qt_5_6 ); -#endif - - // Read the header to know the message length - quint64 msgLen = 0; - headerStream >> msgLen; - ConnectionInfo &info = connectionMap[sock]; - info.stage = StageBody; - info.msgLen = msgLen; - - if ( sock->bytesAvailable() >= (qint64) msgLen ){ - readInitMessageBody( sock ); - } -} - -void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) -{ - Q_Q(SingleApplication); - - if (!connectionMap.contains( sock )){ - return; - } - - ConnectionInfo &info = connectionMap[sock]; - if( sock->bytesAvailable() < ( qint64 )info.msgLen ){ - return; - } - - // Read the message body - QByteArray msgBytes = sock->read(info.msgLen); - QDataStream readStream(msgBytes); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - readStream.setVersion( QDataStream::Qt_5_6 ); -#endif - - // server name - QByteArray latin1Name; - readStream >> latin1Name; - - // connection type - ConnectionType connectionType = InvalidConnection; - quint8 connTypeVal = InvalidConnection; - readStream >> connTypeVal; - connectionType = static_cast ( connTypeVal ); - - // instance id - quint32 instanceId = 0; - readStream >> instanceId; - - // checksum - quint16 msgChecksum = 0; - readStream >> msgChecksum; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16)))); -#else - const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); -#endif - - bool isValid = readStream.status() == QDataStream::Ok && - QLatin1String(latin1Name) == blockServerName && - msgChecksum == actualChecksum; - - if( !isValid ){ - sock->close(); - return; - } - - info.instanceId = instanceId; - info.stage = StageConnected; - - if( connectionType == NewInstance || - ( connectionType == SecondaryInstance && - options & SingleApplication::Mode::SecondaryNotification ) ) - { - Q_EMIT q->instanceStarted(); - } - - if (sock->bytesAvailable() > 0){ - Q_EMIT this->slotDataAvailable( sock, instanceId ); - } -} - -void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId ) -{ - Q_Q(SingleApplication); - Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() ); -} - -void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId ) -{ - if( closedSocket->bytesAvailable() > 0 ) - Q_EMIT slotDataAvailable( closedSocket, instanceId ); -} - -void SingleApplicationPrivate::randomSleep() -{ -#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) - QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u )); -#else - qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max() ); - QThread::msleep( 8 + static_cast ( static_cast ( qrand() ) / RAND_MAX * 10 )); -#endif -} - -void SingleApplicationPrivate::addAppData(const QString &data) -{ - appDataList.push_back(data); -} - -QStringList SingleApplicationPrivate::appData() const -{ - return appDataList; -} diff --git a/client/3rd/SingleApplication/singleapplication_p.h b/client/3rd/SingleApplication/singleapplication_p.h deleted file mode 100644 index c49a46dd..00000000 --- a/client/3rd/SingleApplication/singleapplication_p.h +++ /dev/null @@ -1,104 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This file is not part of the SingleApplication API. It is used purely as an -// implementation detail. This header file may change from version to -// version without notice, or may even be removed. -// - -#ifndef SINGLEAPPLICATION_P_H -#define SINGLEAPPLICATION_P_H - -#include -#include -#include -#include "singleapplication.h" - -struct InstancesInfo { - bool primary; - quint32 secondary; - qint64 primaryPid; - char primaryUser[128]; - quint16 checksum; // Must be the last field -}; - -struct ConnectionInfo { - qint64 msgLen = 0; - quint32 instanceId = 0; - quint8 stage = 0; -}; - -class SingleApplicationPrivate : public QObject { -Q_OBJECT -public: - enum ConnectionType : quint8 { - InvalidConnection = 0, - NewInstance = 1, - SecondaryInstance = 2, - Reconnect = 3 - }; - enum ConnectionStage : quint8 { - StageHeader = 0, - StageBody = 1, - StageConnected = 2, - }; - Q_DECLARE_PUBLIC(SingleApplication) - - SingleApplicationPrivate( SingleApplication *q_ptr ); - ~SingleApplicationPrivate() override; - - static QString getUsername(); - void genBlockServerName(); - void initializeMemoryBlock() const; - void startPrimary(); - void startSecondary(); - bool connectToPrimary( int msecs, ConnectionType connectionType ); - quint16 blockChecksum() const; - qint64 primaryPid() const; - QString primaryUser() const; - void readInitMessageHeader(QLocalSocket *socket); - void readInitMessageBody(QLocalSocket *socket); - static void randomSleep(); - void addAppData(const QString &data); - QStringList appData() const; - - SingleApplication *q_ptr; - QSharedMemory *memory; - QLocalSocket *socket; - QLocalServer *server; - quint32 instanceNumber; - QString blockServerName; - SingleApplication::Options options; - QMap connectionMap; - QStringList appDataList; - -public Q_SLOTS: - void slotConnectionEstablished(); - void slotDataAvailable( QLocalSocket*, quint32 ); - void slotClientConnectionClosed( QLocalSocket*, quint32 ); -}; - -#endif // SINGLEAPPLICATION_P_H diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2de5db48..2ec4082c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -26,9 +26,11 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}") add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}") +add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}") add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") +add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}") if(IOS) set(PACKAGES ${PACKAGES} Multimedia) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 526b9fa9..4e25097d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "logger.h" #include "ui/models/installedAppsModel.h" @@ -28,13 +30,7 @@ #include #endif -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) -#else -AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout, - const QString &userData) - : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) -#endif { setQuitOnLastWindowClosed(false); @@ -115,10 +111,11 @@ void AmneziaApplication::init() qFatal("Android controller initialization failed"); } - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { - m_pageController->goToPageHome(); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); + data.clear(); + emit m_pageController->goToPageViewConfig(); }); m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); @@ -126,16 +123,16 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { - m_pageController->goToPageHome(); + connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); + emit m_pageController->goToPageViewConfig(); }); - connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { - m_pageController->goToPageHome(); + connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { + emit m_pageController->goToPageHome(); m_pageController->goToPageSettingsBackup(); - m_settingsController->importBackupFromOutside(filePath); + emit m_settingsController->importBackupFromOutside(filePath); }); QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); @@ -180,16 +177,6 @@ void AmneziaApplication::init() m_pageController->showOnStartup(); #endif - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { - qDebug() << "Secondary instance started, showing this window instead"; - emit m_pageController->raiseMainWindow(); - }); - } -#endif - // Android TextArea clipboard workaround // Text from TextArea always has "text/html" mime-type: // /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865 @@ -294,6 +281,24 @@ bool AmneziaApplication::parseCommands() return true; } +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +void AmneziaApplication::startLocalServer() { + const QString serverName("AmneziaVPNInstance"); + QLocalServer::removeServer(serverName); + + QLocalServer* server = new QLocalServer(this); + server->listen(serverName); + + QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() { + if (server) { + QLocalSocket* clientConnection = server->nextPendingConnection(); + clientConnection->deleteLater(); + } + emit m_pageController->raiseMainWindow(); + }); +} +#endif + QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 6fb61f44..64566216 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -53,22 +53,14 @@ #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #define AMNEZIA_BASE_CLASS QGuiApplication #else - #define AMNEZIA_BASE_CLASS SingleApplication - #define QAPPLICATION_CLASS QApplication - #include "singleapplication.h" + #define AMNEZIA_BASE_CLASS QApplication #endif class AmneziaApplication : public AMNEZIA_BASE_CLASS { Q_OBJECT public: -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication(int &argc, char *argv[]); -#else - AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, - SingleApplication::Options options = SingleApplication::User, int timeout = 1000, - const QString &userData = {}); -#endif virtual ~AmneziaApplication(); void init(); @@ -78,6 +70,10 @@ public: void updateTranslator(const QLocale &locale); bool parseCommands(); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + void startLocalServer(); +#endif + QQmlApplicationEngine *qmlEngine() const; QNetworkAccessManager *manager() { return m_nam; } diff --git a/client/android/awg/src/main/kotlin/Awg.kt b/client/android/awg/src/main/kotlin/Awg.kt index fbd1cce0..c147ae03 100644 --- a/client/android/awg/src/main/kotlin/Awg.kt +++ b/client/android/awg/src/main/kotlin/Awg.kt @@ -1,28 +1,21 @@ package org.amnezia.vpn.protocol.awg import org.amnezia.vpn.protocol.wireguard.Wireguard -import org.amnezia.vpn.util.optStringOrNull +import org.amnezia.vpn.protocol.wireguard.WireguardConfig import org.json.JSONObject class Awg : Wireguard() { override val ifName: String = "awg0" - override fun parseConfig(config: JSONObject): AwgConfig { + override fun parseConfig(config: JSONObject): WireguardConfig { val configData = config.getJSONObject("awg_config_data") - return AwgConfig.build { + return WireguardConfig.build { + setUseProtocolExtension(true) + configExtensionParameters(configData) configWireguard(config, configData) configSplitTunneling(config) configAppSplitTunneling(config) - configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) } - configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) } - configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) } - configData.optStringOrNull("S1")?.let { setS1(it.toInt()) } - configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } - configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } - configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } - configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } - configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } } } } diff --git a/client/android/awg/src/main/kotlin/AwgConfig.kt b/client/android/awg/src/main/kotlin/AwgConfig.kt deleted file mode 100644 index 014c6e0a..00000000 --- a/client/android/awg/src/main/kotlin/AwgConfig.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.amnezia.vpn.protocol.awg - -import org.amnezia.vpn.protocol.BadConfigException -import org.amnezia.vpn.protocol.wireguard.WireguardConfig - -class AwgConfig private constructor( - wireguardConfigBuilder: WireguardConfig.Builder, - val jc: Int, - val jmin: Int, - val jmax: Int, - val s1: Int, - val s2: Int, - val h1: Long, - val h2: Long, - val h3: Long, - val h4: Long -) : WireguardConfig(wireguardConfigBuilder) { - - private constructor(builder: Builder) : this( - builder, - builder.jc, - builder.jmin, - builder.jmax, - builder.s1, - builder.s2, - builder.h1, - builder.h2, - builder.h3, - builder.h4 - ) - - override fun appendDeviceLine(sb: StringBuilder) = with(sb) { - super.appendDeviceLine(this) - appendLine("jc=$jc") - appendLine("jmin=$jmin") - appendLine("jmax=$jmax") - appendLine("s1=$s1") - appendLine("s2=$s2") - appendLine("h1=$h1") - appendLine("h2=$h2") - appendLine("h3=$h3") - appendLine("h4=$h4") - } - - class Builder : WireguardConfig.Builder() { - - private var _jc: Int? = null - internal var jc: Int - get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined") - private set(value) { _jc = value } - - private var _jmin: Int? = null - internal var jmin: Int - get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined") - private set(value) { _jmin = value } - - private var _jmax: Int? = null - internal var jmax: Int - get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined") - private set(value) { _jmax = value } - - private var _s1: Int? = null - internal var s1: Int - get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined") - private set(value) { _s1 = value } - - private var _s2: Int? = null - internal var s2: Int - get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined") - private set(value) { _s2 = value } - - private var _h1: Long? = null - internal var h1: Long - get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined") - private set(value) { _h1 = value } - - private var _h2: Long? = null - internal var h2: Long - get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined") - private set(value) { _h2 = value } - - private var _h3: Long? = null - internal var h3: Long - get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined") - private set(value) { _h3 = value } - - private var _h4: Long? = null - internal var h4: Long - get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined") - private set(value) { _h4 = value } - - fun setJc(jc: Int) = apply { this.jc = jc } - fun setJmin(jmin: Int) = apply { this.jmin = jmin } - fun setJmax(jmax: Int) = apply { this.jmax = jmax } - fun setS1(s1: Int) = apply { this.s1 = s1 } - fun setS2(s2: Int) = apply { this.s2 = s2 } - fun setH1(h1: Long) = apply { this.h1 = h1 } - fun setH2(h2: Long) = apply { this.h2 = h2 } - fun setH3(h3: Long) = apply { this.h3 = h3 } - fun setH4(h4: Long) = apply { this.h4 = h4 } - - override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) } - } - - companion object { - inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build() - } -} diff --git a/client/android/gradle.properties b/client/android/gradle.properties index 5a27838c..ce651e1c 100644 --- a/client/android/gradle.properties +++ b/client/android/gradle.properties @@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false # For development copy and set local values for these parameters in local.properties #androidCompileSdkVersion=android-34 #androidBuildToolsVersion=34.0.0 -#qtMinSdkVersion=24 +#qtMinSdkVersion=26 #qtTargetSdkVersion=34 #androidNdkVersion=26.1.10909125 #qtTargetAbiList=x86_64 diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt index b5c382be..6e682aa4 100644 --- a/client/android/protocolApi/src/main/kotlin/Protocol.kt +++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt @@ -1,6 +1,5 @@ package org.amnezia.vpn.protocol -import android.annotation.SuppressLint import android.content.Context import android.net.IpPrefix import android.net.VpnService @@ -8,9 +7,6 @@ import android.net.VpnService.Builder import android.os.Build import android.system.OsConstants import androidx.annotation.RequiresApi -import java.io.File -import java.io.FileOutputStream -import java.util.zip.ZipFile import kotlinx.coroutines.flow.MutableStateFlow import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.net.InetNetwork diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index d5026425..b2c2ff71 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -21,6 +21,7 @@ import android.os.Looper import android.os.Message import android.os.Messenger import android.provider.Settings +import android.view.MotionEvent import android.view.WindowManager.LayoutParams import android.webkit.MimeTypeMap import android.widget.Toast @@ -158,7 +159,7 @@ class AmneziaActivity : QtActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "Create Amnezia activity: $intent") + Log.d(TAG, "Create Amnezia activity") loadLibs() window.apply { addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) @@ -200,7 +201,7 @@ class AmneziaActivity : QtActivity() { NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED ) ) { - Log.d( + Log.v( TAG, "Notification state changed: ${it?.action}, blocked = " + "${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}" ) @@ -214,7 +215,7 @@ class AmneziaActivity : QtActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.d(TAG, "onNewIntent: $intent") + Log.v(TAG, "onNewIntent: $intent") intent?.let(::processIntent) } @@ -403,7 +404,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun startVpn(vpnConfig: String) { getVpnProto(vpnConfig)?.let { proto -> - Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto") + Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto") if (isServiceConnected) { if (proto.serviceClass == vpnProto?.serviceClass) { vpnProto = proto @@ -516,7 +517,7 @@ class AmneziaActivity : QtActivity() { startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( onSuccess = { it?.data?.let { uri -> - Log.d(TAG, "Save file to $uri") + Log.v(TAG, "Save file to $uri") try { contentResolver.openOutputStream(uri)?.use { os -> os.bufferedWriter().use { it.write(data) } @@ -565,7 +566,7 @@ class AmneziaActivity : QtActivity() { startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( onAny = { val uri = it?.data?.toString() ?: "" - Log.d(TAG, "Open file: $uri") + Log.v(TAG, "Open file: $uri") mainScope.launch { qtInitialized.await() QtAndroidController.onFileOpened(uri) @@ -720,6 +721,66 @@ class AmneziaActivity : QtActivity() { } } + // workaround for a bug in Qt that causes the mouse click event not to be handled + // also disable right-click, as it causes the application to crash + private var lastButtonState = 0 + private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain( + downTime, + eventTime, + action, + pointerCount, + (0 until pointerCount).map { i -> + MotionEvent.PointerProperties().apply { + getPointerProperties(i, this) + } + }.toTypedArray(), + (0 until pointerCount).map { i -> + MotionEvent.PointerCoords().apply { + getPointerCoords(i, this) + } + }.toTypedArray(), + metaState, + MotionEvent.BUTTON_PRIMARY, + xPrecision, + yPrecision, + deviceId, + edgeFlags, + source, + flags + ) + + private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean { + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + lastButtonState = ev.buttonState + if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true + } + + MotionEvent.ACTION_UP -> { + when (lastButtonState) { + MotionEvent.BUTTON_SECONDARY -> return true + MotionEvent.BUTTON_PRIMARY -> { + val modEvent = ev.fixCopy() + return superDispatch(modEvent).apply { modEvent.recycle() } + } + } + } + } + return superDispatch(ev) + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { + return handleMouseEvent(ev) { super.dispatchTouchEvent(it) } + } + return super.dispatchTouchEvent(ev) + } + + override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean { + ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }} + return super.dispatchTrackballEvent(ev) + } + /** * Utils methods */ diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index 6a7da7c7..8d108bc3 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -22,6 +22,7 @@ import androidx.annotation.MainThread import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat import androidx.core.content.getSystemService +import java.net.UnknownHostException import java.util.concurrent.ConcurrentHashMap import kotlin.LazyThreadSafetyMode.NONE import kotlinx.coroutines.CoroutineExceptionHandler @@ -127,6 +128,8 @@ open class AmneziaVpnService : VpnService() { is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}") + is UnknownHostException -> onError("Unknown host") + else -> throw e } } @@ -297,7 +300,7 @@ open class AmneziaVpnService : VpnService() { arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED ) { it?.action?.let { action -> - Log.d(TAG, "Broadcast request received: $action") + Log.v(TAG, "Broadcast request received: $action") when (action) { ACTION_CONNECT -> connect() ACTION_DISCONNECT -> disconnect() @@ -314,7 +317,7 @@ open class AmneziaVpnService : VpnService() { ) ) { val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false) - Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state") + Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state") if (state == false) { enableNotification() } else { @@ -447,7 +450,7 @@ open class AmneziaVpnService : VpnService() { serviceNotification.isNotificationEnabled() && getSystemService()?.isInteractive != false ) { - Log.d(TAG, "Launch traffic stats update") + Log.v(TAG, "Launch traffic stats update") trafficStats.reset() startTrafficStatsUpdateJob() } diff --git a/client/android/src/org/amnezia/vpn/AuthActivity.kt b/client/android/src/org/amnezia/vpn/AuthActivity.kt index 2593315c..46401548 100644 --- a/client/android/src/org/amnezia/vpn/AuthActivity.kt +++ b/client/android/src/org/amnezia/vpn/AuthActivity.kt @@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() { object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) - Log.d(TAG, "Authentication succeeded") + Log.v(TAG, "Authentication succeeded") QtAndroidController.onAuthResult(true) finish() } diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt index 9faa30d0..49823a36 100644 --- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt +++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt @@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "Create Import Config Activity: $intent") + Log.v(TAG, "Create Import Config Activity: $intent") intent?.let(::readConfig) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - Log.d(TAG, "onNewIntent: $intent") + Log.v(TAG, "onNewIntent: $intent") intent.let(::readConfig) } private fun readConfig(intent: Intent) { when (intent.action) { ACTION_SEND -> { - Log.d(TAG, "Process SEND action, type: ${intent.type}") + Log.v(TAG, "Process SEND action, type: ${intent.type}") when (intent.type) { "application/octet-stream" -> { intent.getUriCompat()?.let { uri -> @@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() { } ACTION_VIEW -> { - Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") + Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") when (intent.scheme) { "file", "content" -> { intent.data?.let { uri -> diff --git a/client/android/src/org/amnezia/vpn/ServiceNotification.kt b/client/android/src/org/amnezia/vpn/ServiceNotification.kt index f4707731..47e8f263 100644 --- a/client/android/src/org/amnezia/vpn/ServiceNotification.kt +++ b/client/android/src/org/amnezia/vpn/ServiceNotification.kt @@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) { fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification { val speedString = if (state == CONNECTED) zeroSpeed else null - Log.d(TAG, "Build notification: $serverName, $state") + Log.v(TAG, "Build notification: $serverName, $state") return notificationBuilder .setSmallIcon(R.drawable.ic_amnezia_round) @@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) { fun isNotificationEnabled(): Boolean { if (!context.isNotificationPermissionGranted()) return false if (!notificationManager.areNotificationsEnabled()) return false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) - ?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true - } - return true + return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let { + it.importance != NotificationManager.IMPORTANCE_NONE + } ?: true } @SuppressLint("MissingPermission") fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) { if (context.isNotificationPermissionGranted()) { - Log.d(TAG, "Update notification: $serverName, $state") + Log.v(TAG, "Update notification: $serverName, $state") notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state)) } } diff --git a/client/android/utils/src/main/kotlin/LibraryLoader.kt b/client/android/utils/src/main/kotlin/LibraryLoader.kt index f1c6465e..8def18d0 100644 --- a/client/android/utils/src/main/kotlin/LibraryLoader.kt +++ b/client/android/utils/src/main/kotlin/LibraryLoader.kt @@ -46,7 +46,7 @@ object LibraryLoader { System.loadLibrary(libraryName) return } catch (_: UnsatisfiedLinkError) { - Log.d(TAG, "Failed to load library, try to extract it from apk") + Log.w(TAG, "Failed to load library, try to extract it from apk") } var tempFile: File? = null try { diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt index a656b9ea..da11c200 100644 --- a/client/android/utils/src/main/kotlin/Log.kt +++ b/client/android/utils/src/main/kotlin/Log.kt @@ -1,8 +1,6 @@ package org.amnezia.vpn.util import android.content.Context -import android.icu.text.DateFormat -import android.icu.text.SimpleDateFormat import android.os.Build import android.os.Process import java.io.File @@ -12,8 +10,6 @@ import java.nio.channels.FileChannel import java.nio.channels.FileLock import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import java.util.Date -import java.util.Locale import java.util.concurrent.locks.ReentrantLock import org.amnezia.vpn.util.Log.Priority.D import org.amnezia.vpn.util.Log.Priority.E @@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024 * | | | create a report and/or terminate the process | */ object Log { - private val dateTimeFormat: Any = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) - else object : ThreadLocal() { - override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US) - } + private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) private lateinit var logDir: File private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) } @@ -143,12 +135,7 @@ object Log { } private fun formatLogMsg(tag: String, msg: String, priority: Priority): String { - val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter) - } else { - @Suppress("UNCHECKED_CAST") - (dateTimeFormat as ThreadLocal).get()?.format(Date()) - } + val date = LocalDateTime.now().format(dateTimeFormat) return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " + "$tag: $msg\n" } diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt index b71bf393..1cab5535 100644 --- a/client/android/utils/src/main/kotlin/net/NetworkState.kt +++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt @@ -42,18 +42,12 @@ class NetworkState( private val networkCallback: NetworkCallback by lazy(NONE) { object : NetworkCallback() { override fun onAvailable(network: Network) { - Log.d(TAG, "onAvailable: $network") + Log.v(TAG, "onAvailable: $network") } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - checkNetworkState(network, networkCapabilities) - } else { - handler.post { - checkNetworkState(network, networkCapabilities) - } - } + Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities") + checkNetworkState(network, networkCapabilities) } private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) { @@ -73,11 +67,11 @@ class NetworkState( } override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { - Log.d(TAG, "onBlockedStatusChanged: $network, $blocked") + Log.v(TAG, "onBlockedStatusChanged: $network, $blocked") } override fun onLost(network: Network) { - Log.d(TAG, "onLost: $network") + Log.v(TAG, "onLost: $network") } } } @@ -87,7 +81,7 @@ class NetworkState( Log.d(TAG, "Bind network listener") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + } else { val numberAttempts = 300 var attemptCount = 0 while(true) { @@ -108,8 +102,6 @@ class NetworkState( } } } - } else { - connectivityManager.requestNetwork(networkRequest, networkCallback) } isListenerBound = true } diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index 31e7f9be..e93834f4 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -66,7 +66,7 @@ open class Wireguard : Protocol() { try { delay(1000) var log = getLogcat(time) - Log.d(TAG, "First waiting log: $log") + Log.v(TAG, "First waiting log: $log") // check that there is a connection log, // to avoid infinite connection if (!log.contains("Attaching to interface")) { @@ -129,12 +129,29 @@ open class Wireguard : Protocol() { val port = configData.getInt("port") setEndpoint(InetEndpoint(host, port)) + if (configData.optBoolean("isObfuscationEnabled")) { + setUseProtocolExtension(true) + configExtensionParameters(configData) + } + configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) } configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) } configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) } configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) } } + protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) { + configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) } + configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) } + configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) } + configData.optStringOrNull("S1")?.let { setS1(it.toInt()) } + configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } + configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } + configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } + configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } + configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } + } + private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) { if (tunnelHandle != -1) { Log.w(TAG, "Tunnel already up") diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt index 09269f54..7ae3d43b 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt @@ -1,6 +1,7 @@ package org.amnezia.vpn.protocol.wireguard import android.util.Base64 +import org.amnezia.vpn.protocol.BadConfigException import org.amnezia.vpn.protocol.ProtocolConfig import org.amnezia.vpn.util.net.InetEndpoint @@ -12,7 +13,17 @@ open class WireguardConfig protected constructor( val persistentKeepalive: Int, val publicKeyHex: String, val preSharedKeyHex: String?, - val privateKeyHex: String + val privateKeyHex: String, + val useProtocolExtension: Boolean, + val jc: Int?, + val jmin: Int?, + val jmax: Int?, + val s1: Int?, + val s2: Int?, + val h1: Long?, + val h2: Long?, + val h3: Long?, + val h4: Long? ) : ProtocolConfig(protocolConfigBuilder) { protected constructor(builder: Builder) : this( @@ -21,7 +32,17 @@ open class WireguardConfig protected constructor( builder.persistentKeepalive, builder.publicKeyHex, builder.preSharedKeyHex, - builder.privateKeyHex + builder.privateKeyHex, + builder.useProtocolExtension, + builder.jc, + builder.jmin, + builder.jmax, + builder.s1, + builder.s2, + builder.h1, + builder.h2, + builder.h3, + builder.h4 ) fun toWgUserspaceString(): String = with(StringBuilder()) { @@ -33,6 +54,30 @@ open class WireguardConfig protected constructor( open fun appendDeviceLine(sb: StringBuilder) = with(sb) { appendLine("private_key=$privateKeyHex") + if (useProtocolExtension) { + validateProtocolExtensionParameters() + appendLine("jc=$jc") + appendLine("jmin=$jmin") + appendLine("jmax=$jmax") + appendLine("s1=$s1") + appendLine("s2=$s2") + appendLine("h1=$h1") + appendLine("h2=$h2") + appendLine("h3=$h3") + appendLine("h4=$h4") + } + } + + private fun validateProtocolExtensionParameters() { + if (jc == null) throw BadConfigException("Parameter jc is undefined") + if (jmin == null) throw BadConfigException("Parameter jmin is undefined") + if (jmax == null) throw BadConfigException("Parameter jmax is undefined") + if (s1 == null) throw BadConfigException("Parameter s1 is undefined") + if (s2 == null) throw BadConfigException("Parameter s2 is undefined") + if (h1 == null) throw BadConfigException("Parameter h1 is undefined") + if (h2 == null) throw BadConfigException("Parameter h2 is undefined") + if (h3 == null) throw BadConfigException("Parameter h3 is undefined") + if (h4 == null) throw BadConfigException("Parameter h4 is undefined") } open fun appendPeerLine(sb: StringBuilder) = with(sb) { @@ -65,6 +110,18 @@ open class WireguardConfig protected constructor( override var mtu: Int = WIREGUARD_DEFAULT_MTU + internal var useProtocolExtension: Boolean = false + + internal var jc: Int? = null + internal var jmin: Int? = null + internal var jmax: Int? = null + internal var s1: Int? = null + internal var s2: Int? = null + internal var h1: Long? = null + internal var h2: Long? = null + internal var h3: Long? = null + internal var h4: Long? = null + fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint } fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive } @@ -75,6 +132,18 @@ open class WireguardConfig protected constructor( fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex } + fun setUseProtocolExtension(useProtocolExtension: Boolean) = apply { this.useProtocolExtension = useProtocolExtension } + + fun setJc(jc: Int) = apply { this.jc = jc } + fun setJmin(jmin: Int) = apply { this.jmin = jmin } + fun setJmax(jmax: Int) = apply { this.jmax = jmax } + fun setS1(s1: Int) = apply { this.s1 = s1 } + fun setS2(s2: Int) = apply { this.s2 = s2 } + fun setH1(h1: Long) = apply { this.h1 = h1 } + fun setH2(h2: Long) = apply { this.h2 = h2 } + fun setH3(h3: Long) = apply { this.h3 = h3 } + fun setH4(h4: Long) = apply { this.h4 = h4 } + override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } } diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt index 6e37c9c2..08242525 100644 --- a/client/android/xray/src/main/kotlin/Xray.kt +++ b/client/android/xray/src/main/kotlin/Xray.kt @@ -130,8 +130,8 @@ class Xray : Protocol() { LibXray.initXray(assetsPath) val geoDir = File(assetsPath, "geo").absolutePath val configPath = File(context.cacheDir, "config.json") - Log.d(TAG, "xray.location.asset: $geoDir") - Log.d(TAG, "config: $configPath") + Log.v(TAG, "xray.location.asset: $geoDir") + Log.v(TAG, "config: $configPath") try { configPath.writeText(configJson) } catch (e: IOException) { diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake index 087f4961..2b5036c5 100644 --- a/client/cmake/3rdparty.cmake +++ b/client/cmake/3rdparty.cmake @@ -2,10 +2,6 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}") -if(NOT IOS AND NOT ANDROID) - include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake) -endif() - add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel) set(LIBS ${LIBS} SortFilterProxyModel) include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake) diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index c96d9ab8..34ca5bff 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -1,6 +1,6 @@ message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") -set(APP_ANDROID_MIN_SDK 24) +set(APP_ANDROID_MIN_SDK 26) set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING "The minimum API level supported by the application or library" FORCE) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 3f8684e0..31a561d8 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -12,6 +12,7 @@ #include "configurators/wireguard_configurator.h" #include "core/enums/apiEnums.h" #include "version.h" +#include "utilities.h" namespace { @@ -40,9 +41,10 @@ namespace constexpr char apiPayload[] = "api_payload"; constexpr char keyPayload[] = "key_payload"; - } - const QStringList proxyStorageUrl = { "" }; + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + } ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply) { @@ -94,8 +96,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); } else if (protocol == configKey::awg) { configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = serverConfig.value(config_key::containers).toArray(); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); if (containers.isEmpty()) { return; // todo process error } @@ -114,25 +116,30 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); container[containerName] = containerConfig; containers.replace(0, container); - serverConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(serverConfig).toJson()); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); } - QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2); - serverConfig[config_key::containers] = apiConfig.value(config_key::containers); - serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName); + QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); + serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); + serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); + serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { - serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion); - serverConfig[config_key::description] = apiConfig.value(config_key::description); - serverConfig[config_key::name] = apiConfig.value(config_key::name); + if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); + serverConfig[config_key::description] = newServerConfig.value(config_key::description); + serverConfig[config_key::name] = newServerConfig.value(config_key::name); } - auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString(); + auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); serverConfig[config_key::defaultContainer] = defaultContainer; + QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); + map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); + auto apiConfig = QJsonObject::fromVariantMap(map); + serverConfig[configKey::apiConfig] = apiConfig; + return; } @@ -146,6 +153,15 @@ QStringList ApiController::getProxyUrls() QList sslErrors; QNetworkReply *reply; + QStringList proxyStorageUrl; + if (m_isDevEnvironment) { + proxyStorageUrl = QStringList { DEV_S3_ENDPOINT }; + } else { + proxyStorageUrl = QStringList { PROD_S3_ENDPOINT }; + } + + QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + for (const auto &proxyStorageUrl : proxyStorageUrl) { request.setUrl(proxyStorageUrl); reply = amnApp->manager()->get(request); @@ -166,11 +182,23 @@ QStringList ApiController::getProxyUrls() EVP_PKEY *privateKey = nullptr; QByteArray responseBody; try { - QByteArray key = PROD_PROXY_STORAGE_KEY; - QSimpleCrypto::QRsa rsa; - privateKey = rsa.getPrivateKeyFromByteArray(key, ""); - responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING); + if (!m_isDevEnvironment) { + QCryptographicHash hash(QCryptographicHash::Sha512); + hash.addData(key); + QByteArray hashResult = hash.result().toHex(); + + QByteArray key = QByteArray::fromHex(hashResult.left(64)); + QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); + + QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); + + QSimpleCrypto::QBlockCipher blockCipher; + responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); + } else { + responseBody = encryptedResponseBody; + } } catch (...) { + Utils::logException(); qCritical() << "error loading private key from environment variables or decrypting payload"; return {}; } @@ -316,7 +344,8 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody) } ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig) + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, + QJsonObject &serverConfig) { #ifdef Q_OS_IOS IosController::Instance()->requestInetAccess(); @@ -339,6 +368,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co } apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::uuid] = installationUuid; + apiPayload[configKey::authData] = authData; QSimpleCrypto::QBlockCipher blockCipher; QByteArray key = blockCipher.generatePrivateSalt(32); @@ -357,10 +387,11 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co EVP_PKEY *publicKey = nullptr; try { - QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; QSimpleCrypto::QRsa rsa; - publicKey = rsa.getPublicKeyFromByteArray(key); + publicKey = rsa.getPublicKeyFromByteArray(rsaKey); } catch (...) { + Utils::logException(); qCritical() << "error loading public key from environment variables"; return ErrorCode::ApiMissingAgwPublicKey; } @@ -370,7 +401,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); qCritical() << "error when encrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; } QJsonObject requestBody; @@ -416,7 +449,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); qCritical() << "error when decrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; } return errorCode; diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index 1f811498..bcb25f96 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -21,7 +21,7 @@ public slots: ErrorCode getServicesList(QByteArray &responseBody); ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig); + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); signals: void errorOccurred(ErrorCode errorCode); diff --git a/client/core/defs.h b/client/core/defs.h index ebc07f4b..d00d347b 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -96,6 +96,7 @@ namespace amnezia // import and install errors ImportInvalidConfigError = 900, + ImportOpenConfigError = 901, // Android errors AndroidError = 1000, @@ -107,6 +108,7 @@ namespace amnezia ApiConfigTimeoutError = 1103, ApiConfigSslError = 1104, ApiMissingAgwPublicKey = 1105, + ApiConfigDecryptionError = 1106, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 8c16d786..49534606 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -50,6 +50,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; + case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break; // Android errors case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; @@ -61,6 +62,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break; case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break; case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; + case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3e237e9c..a234860b 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) { return false; } - if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { + if (!dnsutils()->restoreResolvers()) { return false; } @@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) { } bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { - if (!supportDnsUtils()) { - return true; - } - if ((config.m_hopType == InterfaceConfig::MultiHopExit) || (config.m_hopType == InterfaceConfig::SingleHop)) { QList resolvers; @@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) { } // Cleanup DNS - if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { - return false; - } - - if (!wgutils()->interfaceExists()) { - logger.warning() << "Wireguard interface does not exist."; - return false; + if (!dnsutils()->restoreResolvers()) { + logger.warning() << "Failed to restore DNS resolvers."; } // Cleanup peers and routing @@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) { } m_excludedAddrSet.clear(); - // Delete the interface - if (!wgutils()->deleteInterface()) { - return false; - } - m_connections.clear(); - return true; + // Delete the interface + return wgutils()->deleteInterface(); } QString Daemon::logs() { diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h index d3d8c34d..3d418d70 100644 --- a/client/daemon/daemon.h +++ b/client/daemon/daemon.h @@ -69,7 +69,6 @@ class Daemon : public QObject { virtual WireguardUtils* wgutils() const = 0; virtual bool supportIPUtils() const { return false; } virtual IPUtils* iputils() { return nullptr; } - virtual bool supportDnsUtils() const { return false; } virtual DnsUtils* dnsutils() { return nullptr; } static bool parseStringList(const QJsonObject& obj, const QString& name, diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 1a49b7e5..edbc4c9b 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { logger.debug() << "Command received:" << type; + // It is expected that sometimes the client will request backend logs + // before the first authentication. In these cases we just return empty + // logs. + if (type == "logs") { + QJsonObject obj; + obj.insert("type", "logs"); + obj.insert("logs", ""); + write(obj); + return; + } + if (type == "activate") { InterfaceConfig config; if (!Daemon::parseConfig(obj, config)) { @@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { if (type == "status") { QJsonObject obj = Daemon::instance()->getStatus(); obj.insert("type", "status"); - m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); - m_socket->write("\n"); + write(obj); return; } @@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { QJsonObject obj; obj.insert("type", "logs"); obj.insert("logs", Daemon::instance()->logs().replace("\n", "|")); - m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); - m_socket->write("\n"); + write(obj); return; } diff --git a/client/main.cpp b/client/main.cpp index 3a719096..aca9e62b 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -15,13 +15,24 @@ #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +bool isAnotherInstanceRunning() +{ + QLocalSocket socket; + socket.connectToServer("AmneziaVPNInstance"); + if (socket.waitForConnected(500)) { + qWarning() << "AmneziaVPN is already running"; + return true; + } + return false; +} +#endif + int main(int argc, char *argv[]) { Migrations migrationsManager; migrationsManager.doMigrations(); - QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); - #ifdef Q_OS_WIN AllowSetForegroundWindow(ASFW_ANY); #endif @@ -32,16 +43,14 @@ int main(int argc, char *argv[]) qputenv("ANDROID_OPENSSL_SUFFIX", "_3"); #endif -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); -#else - AmneziaApplication app(argc, argv, true, - SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); - if (!app.isPrimary()) { +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (isAnotherInstanceRunning()) { QTimer::singleShot(1000, &app, [&]() { app.quit(); }); return app.exec(); } + app.startLocalServer(); #endif // Allow to raise app window if secondary instance launched diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 4d040288..5e9f0f97 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -34,8 +34,8 @@ LocalSocketController::LocalSocketController() { m_socket = new QLocalSocket(this); connect(m_socket, &QLocalSocket::connected, this, &LocalSocketController::daemonConnected); - connect(m_socket, &QLocalSocket::disconnected, this, - &LocalSocketController::disconnected); + connect(m_socket, &QLocalSocket::disconnected, this, + [&] { errorOccurred(QLocalSocket::PeerClosedError); }); connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalSocketController::errorOccurred); connect(m_socket, &QLocalSocket::readyRead, this, diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 6abae584..85fb50b7 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -499,6 +499,20 @@ bool IosController::setupWireGuard() wgConfig.insert(config_key::persistent_keep_alive, "25"); } + if (config.contains(config_key::isObfuscationEnabled) && config.value(config_key::isObfuscationEnabled).toBool()) { + wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]); + wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]); + wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]); + wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]); + + wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); + wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); + + wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); + wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); + wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]); + } + QJsonDocument wgConfigDoc(wgConfig); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); diff --git a/client/platforms/linux/daemon/linuxdaemon.h b/client/platforms/linux/daemon/linuxdaemon.h index 7f5d27b7..dbac8cee 100644 --- a/client/platforms/linux/daemon/linuxdaemon.h +++ b/client/platforms/linux/daemon/linuxdaemon.h @@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon { protected: WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } IPUtils* iputils() override { return m_iputils; } diff --git a/client/platforms/macos/daemon/macosdaemon.h b/client/platforms/macos/daemon/macosdaemon.h index a48c326c..4181648e 100644 --- a/client/platforms/macos/daemon/macosdaemon.h +++ b/client/platforms/macos/daemon/macosdaemon.h @@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon { protected: WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } IPUtils* iputils() override { return m_iputils; } diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h index 9d051bae..7e38c41e 100644 --- a/client/platforms/windows/daemon/windowsdaemon.h +++ b/client/platforms/windows/daemon/windowsdaemon.h @@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon { protected: bool run(Op op, const InterfaceConfig& config) override; WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } private: diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 39941933..c4e893b2 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) { // device should contain : for e.g C: return ""; } - QByteArray buffer(2048, 0xFF); + QByteArray buffer(2048, 0xFFu); auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(), buffer.size() / 2); diff --git a/client/platforms/windows/windowscommons.cpp b/client/platforms/windows/windowscommons.cpp index c0a14dda..4c0d8176 100644 --- a/client/platforms/windows/windowscommons.cpp +++ b/client/platforms/windows/windowscommons.cpp @@ -21,7 +21,7 @@ #include "platforms/windows/windowsutils.h" constexpr const char* VPN_NAME = "AmneziaVPN"; -constexpr const char* WIREGUARD_DIR = "WireGuard"; +constexpr const char* WIREGUARD_DIR = "AmneziaWG"; constexpr const char* DATA_DIR = "Data"; namespace { diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 56be0d7d..865edae4 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -65,6 +65,7 @@ namespace amnezia constexpr char last_config[] = "last_config"; constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + constexpr char isObfuscationEnabled[] = "isObfuscationEnabled"; constexpr char junkPacketCount[] = "Jc"; constexpr char junkPacketMinSize[] = "Jmin"; diff --git a/client/settings.h b/client/settings.h index c0ab0559..f41f4d29 100644 --- a/client/settings.h +++ b/client/settings.h @@ -237,7 +237,7 @@ private: mutable SecureQSettings m_settings; QString m_gatewayEndpoint; - bool m_isDevGatewayEnv; + bool m_isDevGatewayEnv = false; }; #endif // SETTINGS_H diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index db8beed1..f8516f6e 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -51,6 +51,9 @@ void ConnectionController::openConnection() if (configVersion == ApiConfigSources::Telegram && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromTelegram(); + } else if (configVersion == ApiConfigSources::AmneziaGateway + && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + emit updateApiConfigFromGateway(); } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { qDebug() << "attempt to update api config by end_date event"; if (configVersion == ApiConfigSources::Telegram) { diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 32170fb6..f7e96bff 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -39,11 +39,12 @@ namespace const QString amneziaConfigPatternUserName = "userName"; const QString amneziaConfigPatternPassword = "password"; const QString amneziaFreeConfigPattern = "api_key"; + const QString amneziaPremiumConfigPattern = "auth_data"; const QString backupPattern = "Servers/serversList"; if (config.contains(backupPattern)) { return ConfigTypes::Backup; - } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) + } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) && config.contains(amneziaConfigPatternPassword))) { return ConfigTypes::Amnezia; @@ -84,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName) return extractConfigFromData(data); } - emit importErrorOccurred(tr("Unable to open file"), false); + emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); return false; } @@ -188,12 +189,12 @@ bool ImportController::extractConfigFromData(QString data) if (!m_serversModel->getServersCount()) { emit restoreAppConfig(config.toUtf8()); } else { - emit importErrorOccurred(tr("Invalid configuration file"), false); + emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); } break; } case ConfigTypes::Invalid: { - emit importErrorOccurred(tr("Invalid configuration file"), false); + emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); break; } } @@ -242,24 +243,26 @@ void ImportController::processNativeWireGuardConfig() auto containers = m_config.value(config_key::containers).toArray(); if (!containers.isEmpty()) { auto container = containers.at(0).toObject(); - auto containerConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject(); - auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); + 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 junkPacketMinSize = QString::number(10); QString junkPacketMaxSize = QString::number(50); - protocolConfig[config_key::junkPacketCount] = junkPacketCount; - protocolConfig[config_key::junkPacketMinSize] = junkPacketMinSize; - protocolConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; - protocolConfig[config_key::initPacketJunkSize] = "0"; - protocolConfig[config_key::responsePacketJunkSize] = "0"; - protocolConfig[config_key::initPacketMagicHeader] = "1"; - protocolConfig[config_key::responsePacketMagicHeader] = "2"; - protocolConfig[config_key::underloadPacketMagicHeader] = "3"; - protocolConfig[config_key::transportPacketMagicHeader] = "4"; + clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount; + clientProtocolConfig[config_key::junkPacketMinSize] = junkPacketMinSize; + clientProtocolConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; + clientProtocolConfig[config_key::initPacketJunkSize] = "0"; + clientProtocolConfig[config_key::responsePacketJunkSize] = "0"; + clientProtocolConfig[config_key::initPacketMagicHeader] = "1"; + clientProtocolConfig[config_key::responsePacketMagicHeader] = "2"; + clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3"; + clientProtocolConfig[config_key::transportPacketMagicHeader] = "4"; - containerConfig[config_key::last_config] = QString(QJsonDocument(protocolConfig).toJson()); - container["wireguard"] = containerConfig; + clientProtocolConfig[config_key::isObfuscationEnabled] = true; + + serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson()); + container["wireguard"] = serverProtocolConfig; containers.replace(0, container); m_config[config_key::containers] = containers; } diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 61205253..05e320a5 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -54,7 +54,6 @@ public slots: signals: void importFinished(); - void importErrorOccurred(const QString &errorMessage, bool goToPageHome); void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); void qrDecodingFinished(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 5a64d092..4ac0bc32 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -32,6 +32,7 @@ namespace constexpr char availableCountries[] = "available_countries"; constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; } } @@ -110,10 +111,10 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; } else if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); + containerConfig.insert(config_key::password, Utils::getRandomString(16)); } else if (container == DockerContainer::Socks5Proxy) { containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); + containerConfig.insert(config_key::password, Utils::getRandomString(16)); } config.insert(config_key::container, ContainerProps::containerToString(container)); @@ -743,7 +744,7 @@ bool InstallController::checkSshConnection(QSharedPointer serv } else { if (output.contains(tr("Please login as the user"))) { output.replace("\n", ""); - emit installationErrorOccurred(output); + emit wrongInstallationUser(output); return false; } } @@ -801,7 +802,7 @@ bool InstallController::installServiceFromApi() ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig); + m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig); if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorCode); return false; @@ -828,19 +829,19 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); + auto authData = serverConfig.value(configKey::authData).toObject(); QJsonObject newServerConfig; - ErrorCode errorCode = - apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), - apiConfig.value(configKey::serviceType).toString(), - apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig); + ErrorCode errorCode = apiController.getConfigForService( + m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), + apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, + authData, newServerConfig); if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorCode); return false; } QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); - newApiConfig.insert(configKey::serviceInfo, apiConfig.value(configKey::serviceInfo)); newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 7eea216a..d7ab3553 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -75,8 +75,8 @@ signals: void removeAllContainersFinished(const QString &finishedMessage); void removeProcessedContainerFinished(const QString &finishedMessage); - void installationErrorOccurred(const QString &errorMessage); void installationErrorOccurred(ErrorCode errorCode); + void wrongInstallationUser(const QString &message); void serverAlreadyExists(int serverIndex); diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 7d3be2cb..7445d60f 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -77,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co { beginResetModel(); m_clientsTable = QJsonArray(); + endResetModel(); ErrorCode error = ErrorCode::NoError; @@ -90,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error); if (error != ErrorCode::NoError) { logger.error() << "Failed to get the clientsTable file from the server"; - endResetModel(); return error; } + beginResetModel(); m_clientsTable = QJsonDocument::fromJson(clientsTableString).array(); if (m_clientsTable.isEmpty()) { @@ -601,5 +602,6 @@ QHash ClientManagementModel::roleNames() const roles[LatestHandshakeRole] = "latestHandshake"; roles[DataReceivedRole] = "dataReceived"; roles[DataSentRole] = "dataSent"; + roles[AllowedIpsRole] = "allowedIps"; return roles; } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 3aac1555..92048f36 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -37,7 +37,7 @@ PageType { Connections { target: ImportController - function onImportErrorOccurred(errorMessage, goToPageHome) { + function onImportErrorOccurred(error, goToPageHome) { if (goToPageHome) { PageController.goToStartPage() } else { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index bb6663fb..640c61ef 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -123,6 +123,10 @@ PageType { } } + function onWrongInstallationUser(message) { + onInstallationErrorOccurred(message) + } + function onUpdateContainerFinished(message) { PageController.showNotificationMessage(message) PageController.closePage() diff --git a/client/utilities.cpp b/client/utilities.cpp index ed91f5fc..1cc69aeb 100755 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -69,14 +69,13 @@ QString Utils::getNextDriverLetter() QString Utils::getRandomString(int len) { - const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - + const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString randomString; + for (int i = 0; i < len; ++i) { - quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length(); - QChar nextChar = possibleCharacters.at(index); - randomString.append(nextChar); + randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length()))); } + return randomString; } @@ -335,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) } #endif + +void Utils::logException(const std::exception &e) +{ + qCritical() << e.what(); + try { + std::rethrow_if_nested(e); + } catch (const std::exception &nested) { + logException(nested); + } catch (...) {} +} + +void Utils::logException(const std::exception_ptr &eptr) +{ + try { + if (eptr) std::rethrow_exception(eptr); + } catch (const std::exception &e) { + logException(e); + } catch (...) {} +} diff --git a/client/utilities.h b/client/utilities.h index b3e3b50b..4a1985b1 100755 --- a/client/utilities.h +++ b/client/utilities.h @@ -35,6 +35,9 @@ public: static QString certUtilPath(); static QString tun2socksPath(); + static void logException(const std::exception &e); + static void logException(const std::exception_ptr &eptr = std::current_exception()); + #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); static QString getNextDriverLetter();