Merge branch 'dev' into bugfix/win_check_ps
This commit is contained in:
commit
c9cd860654
57 changed files with 434 additions and 1379 deletions
17
.github/workflows/deploy.yml
vendored
17
.github/workflows/deploy.yml
vendored
|
|
@ -16,7 +16,10 @@ jobs:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
|
|
@ -83,7 +86,10 @@ jobs:
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
|
|
@ -146,7 +152,10 @@ jobs:
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -238,7 +247,10 @@ jobs:
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.6
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -301,10 +313,13 @@ jobs:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.7.2
|
QT_VERSION: 6.7.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
|
||||||
3
.github/workflows/tag-deploy.yml
vendored
3
.github/workflows/tag-deploy.yml
vendored
|
|
@ -16,7 +16,10 @@ jobs:
|
||||||
QT_VERSION: 6.4.1
|
QT_VERSION: 6.4.1
|
||||||
QIF_VERSION: 4.5
|
QIF_VERSION: 4.5
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
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_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.1.0
|
project(${PROJECT} VERSION 4.8.2.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 62)
|
set(APP_ANDROID_VERSION_CODE 2067)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
||||||
2
client/3rd/OpenVPNAdapter
vendored
2
client/3rd/OpenVPNAdapter
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit dea6040996298e947d63fb172709e6abfec2ba93
|
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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 <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QSharedMemory>
|
|
||||||
|
|
||||||
#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<InstancesInfo*>( 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();
|
|
||||||
}
|
|
||||||
|
|
@ -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 <QtCore/QtGlobal>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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 <cstdlib>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include <QtCore/QDir>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QDataStream>
|
|
||||||
#include <QtCore/QElapsedTimer>
|
|
||||||
#include <QtCore/QCryptographicHash>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
#include <QtCore/QRandomGenerator>
|
|
||||||
#else
|
|
||||||
#include <QtCore/QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX 1
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#include <lmcons.h>
|
|
||||||
#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<InstancesInfo*>(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<InstancesInfo*>( 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 <InstancesInfo*>( 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 <InstancesInfo*>( 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<int>(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<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber;
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(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 <quint64>( initMsg.length() );
|
|
||||||
|
|
||||||
socket->write( header );
|
|
||||||
socket->write( initMsg );
|
|
||||||
bool result = socket->waitForBytesWritten( static_cast<int>(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<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const
|
|
||||||
{
|
|
||||||
qint64 pid;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() const
|
|
||||||
{
|
|
||||||
QByteArray username;
|
|
||||||
|
|
||||||
memory->lock();
|
|
||||||
auto *inst = static_cast<InstancesInfo*>( 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 <ConnectionType>( 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<quint32>(msgBytes.length() - sizeof(quint16))));
|
|
||||||
#else
|
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(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<uint>::max() );
|
|
||||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
|
||||||
{
|
|
||||||
appDataList.push_back(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleApplicationPrivate::appData() const
|
|
||||||
{
|
|
||||||
return appDataList;
|
|
||||||
}
|
|
||||||
|
|
@ -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 <QtCore/QSharedMemory>
|
|
||||||
#include <QtNetwork/QLocalServer>
|
|
||||||
#include <QtNetwork/QLocalSocket>
|
|
||||||
#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<QLocalSocket*, ConnectionInfo> connectionMap;
|
|
||||||
QStringList appDataList;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
|
||||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_P_H
|
|
||||||
|
|
@ -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_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||||
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_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_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
|
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
set(PACKAGES ${PACKAGES} Multimedia)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QLocalServer>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/models/installedAppsModel.h"
|
#include "ui/models/installedAppsModel.h"
|
||||||
|
|
@ -28,13 +30,7 @@
|
||||||
#include <AmneziaVPN-Swift.h>
|
#include <AmneziaVPN-Swift.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
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);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
|
@ -115,10 +111,11 @@ void AmneziaApplication::init()
|
||||||
qFatal("Android controller initialization failed");
|
qFatal("Android controller initialization failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||||
m_pageController->goToPageHome();
|
emit m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
data.clear();
|
||||||
|
emit m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
|
|
@ -126,16 +123,16 @@ void AmneziaApplication::init()
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->initialize();
|
IosController::Instance()->initialize();
|
||||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||||
m_pageController->goToPageHome();
|
emit m_pageController->goToPageHome();
|
||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
emit m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||||
m_pageController->goToPageHome();
|
emit m_pageController->goToPageHome();
|
||||||
m_pageController->goToPageSettingsBackup();
|
m_pageController->goToPageSettingsBackup();
|
||||||
m_settingsController->importBackupFromOutside(filePath);
|
emit m_settingsController->importBackupFromOutside(filePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
|
|
@ -180,16 +177,6 @@ void AmneziaApplication::init()
|
||||||
m_pageController->showOnStartup();
|
m_pageController->showOnStartup();
|
||||||
#endif
|
#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
|
// Android TextArea clipboard workaround
|
||||||
// Text from TextArea always has "text/html" mime-type:
|
// Text from TextArea always has "text/html" mime-type:
|
||||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||||
|
|
@ -294,6 +281,24 @@ bool AmneziaApplication::parseCommands()
|
||||||
return true;
|
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
|
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||||
{
|
{
|
||||||
return m_engine;
|
return m_engine;
|
||||||
|
|
|
||||||
|
|
@ -53,22 +53,14 @@
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||||
#else
|
#else
|
||||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
#define AMNEZIA_BASE_CLASS QApplication
|
||||||
#define QAPPLICATION_CLASS QApplication
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication(int &argc, char *argv[]);
|
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();
|
virtual ~AmneziaApplication();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
@ -78,6 +70,10 @@ public:
|
||||||
void updateTranslator(const QLocale &locale);
|
void updateTranslator(const QLocale &locale);
|
||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
void startLocalServer();
|
||||||
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
QNetworkAccessManager *manager() { return m_nam; }
|
QNetworkAccessManager *manager() { return m_nam; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
package org.amnezia.vpn.protocol.awg
|
package org.amnezia.vpn.protocol.awg
|
||||||
|
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
import org.amnezia.vpn.util.optStringOrNull
|
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
class Awg : Wireguard() {
|
class Awg : Wireguard() {
|
||||||
|
|
||||||
override val ifName: String = "awg0"
|
override val ifName: String = "awg0"
|
||||||
|
|
||||||
override fun parseConfig(config: JSONObject): AwgConfig {
|
override fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configData = config.getJSONObject("awg_config_data")
|
val configData = config.getJSONObject("awg_config_data")
|
||||||
return AwgConfig.build {
|
return WireguardConfig.build {
|
||||||
|
setUseProtocolExtension(true)
|
||||||
|
configExtensionParameters(configData)
|
||||||
configWireguard(config, configData)
|
configWireguard(config, configData)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(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()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
|
||||||
# For development copy and set local values for these parameters in local.properties
|
# For development copy and set local values for these parameters in local.properties
|
||||||
#androidCompileSdkVersion=android-34
|
#androidCompileSdkVersion=android-34
|
||||||
#androidBuildToolsVersion=34.0.0
|
#androidBuildToolsVersion=34.0.0
|
||||||
#qtMinSdkVersion=24
|
#qtMinSdkVersion=26
|
||||||
#qtTargetSdkVersion=34
|
#qtTargetSdkVersion=34
|
||||||
#androidNdkVersion=26.1.10909125
|
#androidNdkVersion=26.1.10909125
|
||||||
#qtTargetAbiList=x86_64
|
#qtTargetAbiList=x86_64
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package org.amnezia.vpn.protocol
|
package org.amnezia.vpn.protocol
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.IpPrefix
|
import android.net.IpPrefix
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
|
@ -8,9 +7,6 @@ import android.net.VpnService.Builder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.os.Messenger
|
import android.os.Messenger
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.WindowManager.LayoutParams
|
import android.view.WindowManager.LayoutParams
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
@ -158,7 +159,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
*/
|
*/
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity")
|
||||||
loadLibs()
|
loadLibs()
|
||||||
window.apply {
|
window.apply {
|
||||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
|
@ -200,7 +201,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Log.d(
|
Log.v(
|
||||||
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
||||||
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
||||||
)
|
)
|
||||||
|
|
@ -214,7 +215,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.v(TAG, "onNewIntent: $intent")
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,7 +404,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun startVpn(vpnConfig: String) {
|
private fun startVpn(vpnConfig: String) {
|
||||||
getVpnProto(vpnConfig)?.let { proto ->
|
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 (isServiceConnected) {
|
||||||
if (proto.serviceClass == vpnProto?.serviceClass) {
|
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||||
vpnProto = proto
|
vpnProto = proto
|
||||||
|
|
@ -516,7 +517,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
it?.data?.let { uri ->
|
it?.data?.let { uri ->
|
||||||
Log.d(TAG, "Save file to $uri")
|
Log.v(TAG, "Save file to $uri")
|
||||||
try {
|
try {
|
||||||
contentResolver.openOutputStream(uri)?.use { os ->
|
contentResolver.openOutputStream(uri)?.use { os ->
|
||||||
os.bufferedWriter().use { it.write(data) }
|
os.bufferedWriter().use { it.write(data) }
|
||||||
|
|
@ -565,7 +566,7 @@ class AmneziaActivity : QtActivity() {
|
||||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||||
onAny = {
|
onAny = {
|
||||||
val uri = it?.data?.toString() ?: ""
|
val uri = it?.data?.toString() ?: ""
|
||||||
Log.d(TAG, "Open file: $uri")
|
Log.v(TAG, "Open file: $uri")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
qtInitialized.await()
|
qtInitialized.await()
|
||||||
QtAndroidController.onFileOpened(uri)
|
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
|
* Utils methods
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import androidx.annotation.MainThread
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
|
@ -127,6 +128,8 @@ open class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
||||||
|
|
||||||
|
is UnknownHostException -> onError("Unknown host")
|
||||||
|
|
||||||
else -> throw e
|
else -> throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
|
||||||
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
) {
|
) {
|
||||||
it?.action?.let { action ->
|
it?.action?.let { action ->
|
||||||
Log.d(TAG, "Broadcast request received: $action")
|
Log.v(TAG, "Broadcast request received: $action")
|
||||||
when (action) {
|
when (action) {
|
||||||
ACTION_CONNECT -> connect()
|
ACTION_CONNECT -> connect()
|
||||||
ACTION_DISCONNECT -> disconnect()
|
ACTION_DISCONNECT -> disconnect()
|
||||||
|
|
@ -314,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
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) {
|
if (state == false) {
|
||||||
enableNotification()
|
enableNotification()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -447,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
|
||||||
serviceNotification.isNotificationEnabled() &&
|
serviceNotification.isNotificationEnabled() &&
|
||||||
getSystemService<PowerManager>()?.isInteractive != false
|
getSystemService<PowerManager>()?.isInteractive != false
|
||||||
) {
|
) {
|
||||||
Log.d(TAG, "Launch traffic stats update")
|
Log.v(TAG, "Launch traffic stats update")
|
||||||
trafficStats.reset()
|
trafficStats.reset()
|
||||||
startTrafficStatsUpdateJob()
|
startTrafficStatsUpdateJob()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||||
super.onAuthenticationSucceeded(result)
|
super.onAuthenticationSucceeded(result)
|
||||||
Log.d(TAG, "Authentication succeeded")
|
Log.v(TAG, "Authentication succeeded")
|
||||||
QtAndroidController.onAuthResult(true)
|
QtAndroidController.onAuthResult(true)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Import Config Activity: $intent")
|
Log.v(TAG, "Create Import Config Activity: $intent")
|
||||||
intent?.let(::readConfig)
|
intent?.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.v(TAG, "onNewIntent: $intent")
|
||||||
intent.let(::readConfig)
|
intent.let(::readConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readConfig(intent: Intent) {
|
private fun readConfig(intent: Intent) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_SEND -> {
|
ACTION_SEND -> {
|
||||||
Log.d(TAG, "Process SEND action, type: ${intent.type}")
|
Log.v(TAG, "Process SEND action, type: ${intent.type}")
|
||||||
when (intent.type) {
|
when (intent.type) {
|
||||||
"application/octet-stream" -> {
|
"application/octet-stream" -> {
|
||||||
intent.getUriCompat()?.let { uri ->
|
intent.getUriCompat()?.let { uri ->
|
||||||
|
|
@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_VIEW -> {
|
ACTION_VIEW -> {
|
||||||
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||||
when (intent.scheme) {
|
when (intent.scheme) {
|
||||||
"file", "content" -> {
|
"file", "content" -> {
|
||||||
intent.data?.let { uri ->
|
intent.data?.let { uri ->
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
|
||||||
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
||||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||||
|
|
||||||
Log.d(TAG, "Build notification: $serverName, $state")
|
Log.v(TAG, "Build notification: $serverName, $state")
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||||
|
|
@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
|
||||||
fun isNotificationEnabled(): Boolean {
|
fun isNotificationEnabled(): Boolean {
|
||||||
if (!context.isNotificationPermissionGranted()) return false
|
if (!context.isNotificationPermissionGranted()) return false
|
||||||
if (!notificationManager.areNotificationsEnabled()) return false
|
if (!notificationManager.areNotificationsEnabled()) return false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
|
||||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
it.importance != NotificationManager.IMPORTANCE_NONE
|
||||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
} ?: true
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||||
if (context.isNotificationPermissionGranted()) {
|
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))
|
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ object LibraryLoader {
|
||||||
System.loadLibrary(libraryName)
|
System.loadLibrary(libraryName)
|
||||||
return
|
return
|
||||||
} catch (_: UnsatisfiedLinkError) {
|
} 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
|
var tempFile: File? = null
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package org.amnezia.vpn.util
|
package org.amnezia.vpn.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.icu.text.DateFormat
|
|
||||||
import android.icu.text.SimpleDateFormat
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
|
||||||
import java.nio.channels.FileLock
|
import java.nio.channels.FileLock
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import org.amnezia.vpn.util.Log.Priority.D
|
import org.amnezia.vpn.util.Log.Priority.D
|
||||||
import org.amnezia.vpn.util.Log.Priority.E
|
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 |
|
* | | | create a report and/or terminate the process |
|
||||||
*/
|
*/
|
||||||
object Log {
|
object Log {
|
||||||
private val dateTimeFormat: Any =
|
private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
|
||||||
else object : ThreadLocal<DateFormat>() {
|
|
||||||
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var logDir: File
|
private lateinit var logDir: File
|
||||||
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
|
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 {
|
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||||
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||||
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
|
|
||||||
} else {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
|
|
||||||
}
|
|
||||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||||
"$tag: $msg\n"
|
"$tag: $msg\n"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,12 @@ class NetworkState(
|
||||||
private val networkCallback: NetworkCallback by lazy(NONE) {
|
private val networkCallback: NetworkCallback by lazy(NONE) {
|
||||||
object : NetworkCallback() {
|
object : NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
Log.d(TAG, "onAvailable: $network")
|
Log.v(TAG, "onAvailable: $network")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
checkNetworkState(network, networkCapabilities)
|
||||||
checkNetworkState(network, networkCapabilities)
|
|
||||||
} else {
|
|
||||||
handler.post {
|
|
||||||
checkNetworkState(network, networkCapabilities)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
|
|
@ -73,11 +67,11 @@ class NetworkState(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
||||||
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
|
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
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")
|
Log.d(TAG, "Bind network listener")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else {
|
||||||
val numberAttempts = 300
|
val numberAttempts = 300
|
||||||
var attemptCount = 0
|
var attemptCount = 0
|
||||||
while(true) {
|
while(true) {
|
||||||
|
|
@ -108,8 +102,6 @@ class NetworkState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
|
||||||
}
|
}
|
||||||
isListenerBound = true
|
isListenerBound = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ open class Wireguard : Protocol() {
|
||||||
try {
|
try {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
var log = getLogcat(time)
|
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,
|
// check that there is a connection log,
|
||||||
// to avoid infinite connection
|
// to avoid infinite connection
|
||||||
if (!log.contains("Attaching to interface")) {
|
if (!log.contains("Attaching to interface")) {
|
||||||
|
|
@ -129,12 +129,29 @@ open class Wireguard : Protocol() {
|
||||||
val port = configData.getInt("port")
|
val port = configData.getInt("port")
|
||||||
setEndpoint(InetEndpoint(host, port))
|
setEndpoint(InetEndpoint(host, port))
|
||||||
|
|
||||||
|
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||||
|
setUseProtocolExtension(true)
|
||||||
|
configExtensionParameters(configData)
|
||||||
|
}
|
||||||
|
|
||||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(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) {
|
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
if (tunnelHandle != -1) {
|
if (tunnelHandle != -1) {
|
||||||
Log.w(TAG, "Tunnel already up")
|
Log.w(TAG, "Tunnel already up")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||||
import org.amnezia.vpn.util.net.InetEndpoint
|
import org.amnezia.vpn.util.net.InetEndpoint
|
||||||
|
|
||||||
|
|
@ -12,7 +13,17 @@ open class WireguardConfig protected constructor(
|
||||||
val persistentKeepalive: Int,
|
val persistentKeepalive: Int,
|
||||||
val publicKeyHex: String,
|
val publicKeyHex: String,
|
||||||
val preSharedKeyHex: 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) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
|
|
@ -21,7 +32,17 @@ open class WireguardConfig protected constructor(
|
||||||
builder.persistentKeepalive,
|
builder.persistentKeepalive,
|
||||||
builder.publicKeyHex,
|
builder.publicKeyHex,
|
||||||
builder.preSharedKeyHex,
|
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()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
|
|
@ -33,6 +54,30 @@ open class WireguardConfig protected constructor(
|
||||||
|
|
||||||
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
||||||
appendLine("private_key=$privateKeyHex")
|
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) {
|
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||||
|
|
@ -65,6 +110,18 @@ open class WireguardConfig protected constructor(
|
||||||
|
|
||||||
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
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 setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||||
|
|
||||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
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 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) }
|
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ class Xray : Protocol() {
|
||||||
LibXray.initXray(assetsPath)
|
LibXray.initXray(assetsPath)
|
||||||
val geoDir = File(assetsPath, "geo").absolutePath
|
val geoDir = File(assetsPath, "geo").absolutePath
|
||||||
val configPath = File(context.cacheDir, "config.json")
|
val configPath = File(context.cacheDir, "config.json")
|
||||||
Log.d(TAG, "xray.location.asset: $geoDir")
|
Log.v(TAG, "xray.location.asset: $geoDir")
|
||||||
Log.d(TAG, "config: $configPath")
|
Log.v(TAG, "config: $configPath")
|
||||||
try {
|
try {
|
||||||
configPath.writeText(configJson)
|
configPath.writeText(configJson)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
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)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
||||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||||
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
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
|
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||||
"The minimum API level supported by the application or library" FORCE)
|
"The minimum API level supported by the application or library" FORCE)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
#include "core/enums/apiEnums.h"
|
#include "core/enums/apiEnums.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
@ -40,9 +41,10 @@ namespace
|
||||||
|
|
||||||
constexpr char apiPayload[] = "api_payload";
|
constexpr char apiPayload[] = "api_payload";
|
||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
}
|
|
||||||
|
|
||||||
const QStringList proxyStorageUrl = { "" };
|
constexpr char apiConfig[] = "api_config";
|
||||||
|
constexpr char authData[] = "auth_data";
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
|
|
@ -94,8 +96,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
|
||||||
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||||
} else if (protocol == configKey::awg) {
|
} else if (protocol == configKey::awg) {
|
||||||
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
auto containers = newServerConfig.value(config_key::containers).toArray();
|
||||||
if (containers.isEmpty()) {
|
if (containers.isEmpty()) {
|
||||||
return; // todo process error
|
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);
|
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||||
container[containerName] = containerConfig;
|
container[containerName] = containerConfig;
|
||||||
containers.replace(0, container);
|
containers.replace(0, container);
|
||||||
serverConfig[config_key::containers] = containers;
|
newServerConfig[config_key::containers] = containers;
|
||||||
configStr = QString(QJsonDocument(serverConfig).toJson());
|
configStr = QString(QJsonDocument(newServerConfig).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
|
||||||
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
|
||||||
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
|
||||||
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
|
||||||
|
|
||||||
if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||||
serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
|
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
|
||||||
serverConfig[config_key::description] = apiConfig.value(config_key::description);
|
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
|
||||||
serverConfig[config_key::name] = apiConfig.value(config_key::name);
|
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;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +153,15 @@ QStringList ApiController::getProxyUrls()
|
||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
QNetworkReply *reply;
|
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) {
|
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||||
request.setUrl(proxyStorageUrl);
|
request.setUrl(proxyStorageUrl);
|
||||||
reply = amnApp->manager()->get(request);
|
reply = amnApp->manager()->get(request);
|
||||||
|
|
@ -166,11 +182,23 @@ QStringList ApiController::getProxyUrls()
|
||||||
EVP_PKEY *privateKey = nullptr;
|
EVP_PKEY *privateKey = nullptr;
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
try {
|
try {
|
||||||
QByteArray key = PROD_PROXY_STORAGE_KEY;
|
if (!m_isDevEnvironment) {
|
||||||
QSimpleCrypto::QRsa rsa;
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
privateKey = rsa.getPrivateKeyFromByteArray(key, "");
|
hash.addData(key);
|
||||||
responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
|
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 (...) {
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
qCritical() << "error loading private key from environment variables or decrypting payload";
|
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -316,7 +344,8 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
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
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
|
|
@ -339,6 +368,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
}
|
}
|
||||||
apiPayload[configKey::serviceType] = serviceType;
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
apiPayload[configKey::authData] = authData;
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
|
|
@ -357,10 +387,11 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
|
|
||||||
EVP_PKEY *publicKey = nullptr;
|
EVP_PKEY *publicKey = nullptr;
|
||||||
try {
|
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;
|
QSimpleCrypto::QRsa rsa;
|
||||||
publicKey = rsa.getPublicKeyFromByteArray(key);
|
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
return ErrorCode::ApiMissingAgwPublicKey;
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
}
|
}
|
||||||
|
|
@ -370,7 +401,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestBody;
|
QJsonObject requestBody;
|
||||||
|
|
@ -416,7 +449,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
|
Utils::logException();
|
||||||
qCritical() << "error when decrypting the request body";
|
qCritical() << "error when decrypting the request body";
|
||||||
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorCode;
|
return errorCode;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public slots:
|
||||||
|
|
||||||
ErrorCode getServicesList(QByteArray &responseBody);
|
ErrorCode getServicesList(QByteArray &responseBody);
|
||||||
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
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:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ namespace amnezia
|
||||||
|
|
||||||
// import and install errors
|
// import and install errors
|
||||||
ImportInvalidConfigError = 900,
|
ImportInvalidConfigError = 900,
|
||||||
|
ImportOpenConfigError = 901,
|
||||||
|
|
||||||
// Android errors
|
// Android errors
|
||||||
AndroidError = 1000,
|
AndroidError = 1000,
|
||||||
|
|
@ -107,6 +108,7 @@ namespace amnezia
|
||||||
ApiConfigTimeoutError = 1103,
|
ApiConfigTimeoutError = 1103,
|
||||||
ApiConfigSslError = 1104,
|
ApiConfigSslError = 1104,
|
||||||
ApiMissingAgwPublicKey = 1105,
|
ApiMissingAgwPublicKey = 1105,
|
||||||
|
ApiConfigDecryptionError = 1106,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ QString errorString(ErrorCode code) {
|
||||||
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
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::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
|
// Android errors
|
||||||
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
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::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); 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::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
|
||||||
|
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
if (!dnsutils()->restoreResolvers()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||||
if (!supportDnsUtils()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||||
QList<QHostAddress> resolvers;
|
QList<QHostAddress> resolvers;
|
||||||
|
|
@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup DNS
|
// Cleanup DNS
|
||||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
if (!dnsutils()->restoreResolvers()) {
|
||||||
return false;
|
logger.warning() << "Failed to restore DNS resolvers.";
|
||||||
}
|
|
||||||
|
|
||||||
if (!wgutils()->interfaceExists()) {
|
|
||||||
logger.warning() << "Wireguard interface does not exist.";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup peers and routing
|
// Cleanup peers and routing
|
||||||
|
|
@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||||
}
|
}
|
||||||
m_excludedAddrSet.clear();
|
m_excludedAddrSet.clear();
|
||||||
|
|
||||||
// Delete the interface
|
|
||||||
if (!wgutils()->deleteInterface()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_connections.clear();
|
m_connections.clear();
|
||||||
return true;
|
// Delete the interface
|
||||||
|
return wgutils()->deleteInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Daemon::logs() {
|
QString Daemon::logs() {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ class Daemon : public QObject {
|
||||||
virtual WireguardUtils* wgutils() const = 0;
|
virtual WireguardUtils* wgutils() const = 0;
|
||||||
virtual bool supportIPUtils() const { return false; }
|
virtual bool supportIPUtils() const { return false; }
|
||||||
virtual IPUtils* iputils() { return nullptr; }
|
virtual IPUtils* iputils() { return nullptr; }
|
||||||
virtual bool supportDnsUtils() const { return false; }
|
|
||||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||||
|
|
||||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||||
|
|
||||||
logger.debug() << "Command received:" << type;
|
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") {
|
if (type == "activate") {
|
||||||
InterfaceConfig config;
|
InterfaceConfig config;
|
||||||
if (!Daemon::parseConfig(obj, config)) {
|
if (!Daemon::parseConfig(obj, config)) {
|
||||||
|
|
@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||||
if (type == "status") {
|
if (type == "status") {
|
||||||
QJsonObject obj = Daemon::instance()->getStatus();
|
QJsonObject obj = Daemon::instance()->getStatus();
|
||||||
obj.insert("type", "status");
|
obj.insert("type", "status");
|
||||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
write(obj);
|
||||||
m_socket->write("\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj.insert("type", "logs");
|
obj.insert("type", "logs");
|
||||||
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
|
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
|
||||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
write(obj);
|
||||||
m_socket->write("\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,24 @@
|
||||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||||
#endif
|
#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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
Migrations migrationsManager;
|
Migrations migrationsManager;
|
||||||
migrationsManager.doMigrations();
|
migrationsManager.doMigrations();
|
||||||
|
|
||||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
AllowSetForegroundWindow(ASFW_ANY);
|
AllowSetForegroundWindow(ASFW_ANY);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -32,16 +43,14 @@ int main(int argc, char *argv[])
|
||||||
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
|
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
|
||||||
AmneziaApplication app(argc, argv);
|
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(); });
|
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
app.startLocalServer();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Allow to raise app window if secondary instance launched
|
// Allow to raise app window if secondary instance launched
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ LocalSocketController::LocalSocketController() {
|
||||||
connect(m_socket, &QLocalSocket::connected, this,
|
connect(m_socket, &QLocalSocket::connected, this,
|
||||||
&LocalSocketController::daemonConnected);
|
&LocalSocketController::daemonConnected);
|
||||||
connect(m_socket, &QLocalSocket::disconnected, this,
|
connect(m_socket, &QLocalSocket::disconnected, this,
|
||||||
&LocalSocketController::disconnected);
|
[&] { errorOccurred(QLocalSocket::PeerClosedError); });
|
||||||
connect(m_socket, &QLocalSocket::errorOccurred, this,
|
connect(m_socket, &QLocalSocket::errorOccurred, this,
|
||||||
&LocalSocketController::errorOccurred);
|
&LocalSocketController::errorOccurred);
|
||||||
connect(m_socket, &QLocalSocket::readyRead, this,
|
connect(m_socket, &QLocalSocket::readyRead, this,
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,20 @@ bool IosController::setupWireGuard()
|
||||||
wgConfig.insert(config_key::persistent_keep_alive, "25");
|
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);
|
QJsonDocument wgConfigDoc(wgConfig);
|
||||||
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
bool supportDnsUtils() const override { return true; }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
bool supportDnsUtils() const override { return true; }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon {
|
||||||
protected:
|
protected:
|
||||||
bool run(Op op, const InterfaceConfig& config) override;
|
bool run(Op op, const InterfaceConfig& config) override;
|
||||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
bool supportDnsUtils() const override { return true; }
|
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) {
|
||||||
// device should contain : for e.g C:
|
// device should contain : for e.g C:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
QByteArray buffer(2048, 0xFF);
|
QByteArray buffer(2048, 0xFFu);
|
||||||
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
|
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
|
||||||
(wchar_t*)buffer.data(), buffer.size() / 2);
|
(wchar_t*)buffer.data(), buffer.size() / 2);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
#include "platforms/windows/windowsutils.h"
|
#include "platforms/windows/windowsutils.h"
|
||||||
|
|
||||||
constexpr const char* VPN_NAME = "AmneziaVPN";
|
constexpr const char* VPN_NAME = "AmneziaVPN";
|
||||||
constexpr const char* WIREGUARD_DIR = "WireGuard";
|
constexpr const char* WIREGUARD_DIR = "AmneziaWG";
|
||||||
constexpr const char* DATA_DIR = "Data";
|
constexpr const char* DATA_DIR = "Data";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ namespace amnezia
|
||||||
constexpr char last_config[] = "last_config";
|
constexpr char last_config[] = "last_config";
|
||||||
|
|
||||||
constexpr char isThirdPartyConfig[] = "isThirdPartyConfig";
|
constexpr char isThirdPartyConfig[] = "isThirdPartyConfig";
|
||||||
|
constexpr char isObfuscationEnabled[] = "isObfuscationEnabled";
|
||||||
|
|
||||||
constexpr char junkPacketCount[] = "Jc";
|
constexpr char junkPacketCount[] = "Jc";
|
||||||
constexpr char junkPacketMinSize[] = "Jmin";
|
constexpr char junkPacketMinSize[] = "Jmin";
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ private:
|
||||||
mutable SecureQSettings m_settings;
|
mutable SecureQSettings m_settings;
|
||||||
|
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevGatewayEnv;
|
bool m_isDevGatewayEnv = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGS_H
|
#endif // SETTINGS_H
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ void ConnectionController::openConnection()
|
||||||
if (configVersion == ApiConfigSources::Telegram
|
if (configVersion == ApiConfigSources::Telegram
|
||||||
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
emit updateApiConfigFromTelegram();
|
emit updateApiConfigFromTelegram();
|
||||||
|
} else if (configVersion == ApiConfigSources::AmneziaGateway
|
||||||
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
emit updateApiConfigFromGateway();
|
||||||
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
|
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
|
||||||
qDebug() << "attempt to update api config by end_date event";
|
qDebug() << "attempt to update api config by end_date event";
|
||||||
if (configVersion == ApiConfigSources::Telegram) {
|
if (configVersion == ApiConfigSources::Telegram) {
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,12 @@ namespace
|
||||||
const QString amneziaConfigPatternUserName = "userName";
|
const QString amneziaConfigPatternUserName = "userName";
|
||||||
const QString amneziaConfigPatternPassword = "password";
|
const QString amneziaConfigPatternPassword = "password";
|
||||||
const QString amneziaFreeConfigPattern = "api_key";
|
const QString amneziaFreeConfigPattern = "api_key";
|
||||||
|
const QString amneziaPremiumConfigPattern = "auth_data";
|
||||||
const QString backupPattern = "Servers/serversList";
|
const QString backupPattern = "Servers/serversList";
|
||||||
|
|
||||||
if (config.contains(backupPattern)) {
|
if (config.contains(backupPattern)) {
|
||||||
return ConfigTypes::Backup;
|
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(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||||
&& config.contains(amneziaConfigPatternPassword))) {
|
&& config.contains(amneziaConfigPatternPassword))) {
|
||||||
return ConfigTypes::Amnezia;
|
return ConfigTypes::Amnezia;
|
||||||
|
|
@ -84,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
||||||
return extractConfigFromData(data);
|
return extractConfigFromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit importErrorOccurred(tr("Unable to open file"), false);
|
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,12 +189,12 @@ bool ImportController::extractConfigFromData(QString data)
|
||||||
if (!m_serversModel->getServersCount()) {
|
if (!m_serversModel->getServersCount()) {
|
||||||
emit restoreAppConfig(config.toUtf8());
|
emit restoreAppConfig(config.toUtf8());
|
||||||
} else {
|
} else {
|
||||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConfigTypes::Invalid: {
|
case ConfigTypes::Invalid: {
|
||||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,24 +243,26 @@ void ImportController::processNativeWireGuardConfig()
|
||||||
auto containers = m_config.value(config_key::containers).toArray();
|
auto containers = m_config.value(config_key::containers).toArray();
|
||||||
if (!containers.isEmpty()) {
|
if (!containers.isEmpty()) {
|
||||||
auto container = containers.at(0).toObject();
|
auto container = containers.at(0).toObject();
|
||||||
auto containerConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
||||||
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||||
|
|
||||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
||||||
QString junkPacketMinSize = QString::number(10);
|
QString junkPacketMinSize = QString::number(10);
|
||||||
QString junkPacketMaxSize = QString::number(50);
|
QString junkPacketMaxSize = QString::number(50);
|
||||||
protocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
||||||
protocolConfig[config_key::junkPacketMinSize] = junkPacketMinSize;
|
clientProtocolConfig[config_key::junkPacketMinSize] = junkPacketMinSize;
|
||||||
protocolConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize;
|
clientProtocolConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize;
|
||||||
protocolConfig[config_key::initPacketJunkSize] = "0";
|
clientProtocolConfig[config_key::initPacketJunkSize] = "0";
|
||||||
protocolConfig[config_key::responsePacketJunkSize] = "0";
|
clientProtocolConfig[config_key::responsePacketJunkSize] = "0";
|
||||||
protocolConfig[config_key::initPacketMagicHeader] = "1";
|
clientProtocolConfig[config_key::initPacketMagicHeader] = "1";
|
||||||
protocolConfig[config_key::responsePacketMagicHeader] = "2";
|
clientProtocolConfig[config_key::responsePacketMagicHeader] = "2";
|
||||||
protocolConfig[config_key::underloadPacketMagicHeader] = "3";
|
clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3";
|
||||||
protocolConfig[config_key::transportPacketMagicHeader] = "4";
|
clientProtocolConfig[config_key::transportPacketMagicHeader] = "4";
|
||||||
|
|
||||||
containerConfig[config_key::last_config] = QString(QJsonDocument(protocolConfig).toJson());
|
clientProtocolConfig[config_key::isObfuscationEnabled] = true;
|
||||||
container["wireguard"] = containerConfig;
|
|
||||||
|
serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson());
|
||||||
|
container["wireguard"] = serverProtocolConfig;
|
||||||
containers.replace(0, container);
|
containers.replace(0, container);
|
||||||
m_config[config_key::containers] = containers;
|
m_config[config_key::containers] = containers;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ public slots:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void importFinished();
|
void importFinished();
|
||||||
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
|
||||||
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||||
|
|
||||||
void qrDecodingFinished();
|
void qrDecodingFinished();
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace
|
||||||
constexpr char availableCountries[] = "available_countries";
|
constexpr char availableCountries[] = "available_countries";
|
||||||
|
|
||||||
constexpr char apiConfig[] = "api_config";
|
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;
|
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
|
||||||
} else if (container == DockerContainer::Sftp) {
|
} else if (container == DockerContainer::Sftp) {
|
||||||
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
|
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) {
|
} else if (container == DockerContainer::Socks5Proxy) {
|
||||||
containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName);
|
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));
|
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||||
|
|
@ -743,7 +744,7 @@ bool InstallController::checkSshConnection(QSharedPointer<ServerController> serv
|
||||||
} else {
|
} else {
|
||||||
if (output.contains(tr("Please login as the user"))) {
|
if (output.contains(tr("Please login as the user"))) {
|
||||||
output.replace("\n", "");
|
output.replace("\n", "");
|
||||||
emit installationErrorOccurred(output);
|
emit wrongInstallationUser(output);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -801,7 +802,7 @@ bool InstallController::installServiceFromApi()
|
||||||
|
|
||||||
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
|
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
|
||||||
m_apiServicesModel->getSelectedServiceType(),
|
m_apiServicesModel->getSelectedServiceType(),
|
||||||
m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig);
|
m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit installationErrorOccurred(errorCode);
|
emit installationErrorOccurred(errorCode);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -828,19 +829,19 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
|
||||||
|
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||||
|
auto authData = serverConfig.value(configKey::authData).toObject();
|
||||||
|
|
||||||
QJsonObject newServerConfig;
|
QJsonObject newServerConfig;
|
||||||
ErrorCode errorCode =
|
ErrorCode errorCode = apiController.getConfigForService(
|
||||||
apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
|
m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
|
||||||
apiConfig.value(configKey::serviceType).toString(),
|
apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode,
|
||||||
apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig);
|
authData, newServerConfig);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit installationErrorOccurred(errorCode);
|
emit installationErrorOccurred(errorCode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject();
|
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::userCountryCode, apiConfig.value(configKey::userCountryCode));
|
||||||
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
||||||
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ signals:
|
||||||
void removeAllContainersFinished(const QString &finishedMessage);
|
void removeAllContainersFinished(const QString &finishedMessage);
|
||||||
void removeProcessedContainerFinished(const QString &finishedMessage);
|
void removeProcessedContainerFinished(const QString &finishedMessage);
|
||||||
|
|
||||||
void installationErrorOccurred(const QString &errorMessage);
|
|
||||||
void installationErrorOccurred(ErrorCode errorCode);
|
void installationErrorOccurred(ErrorCode errorCode);
|
||||||
|
void wrongInstallationUser(const QString &message);
|
||||||
|
|
||||||
void serverAlreadyExists(int serverIndex);
|
void serverAlreadyExists(int serverIndex);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_clientsTable = QJsonArray();
|
m_clientsTable = QJsonArray();
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
ErrorCode error = ErrorCode::NoError;
|
ErrorCode error = ErrorCode::NoError;
|
||||||
|
|
||||||
|
|
@ -90,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
|
||||||
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
|
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
logger.error() << "Failed to get the clientsTable file from the server";
|
logger.error() << "Failed to get the clientsTable file from the server";
|
||||||
endResetModel();
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
|
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
|
||||||
|
|
||||||
if (m_clientsTable.isEmpty()) {
|
if (m_clientsTable.isEmpty()) {
|
||||||
|
|
@ -601,5 +602,6 @@ QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||||
roles[LatestHandshakeRole] = "latestHandshake";
|
roles[LatestHandshakeRole] = "latestHandshake";
|
||||||
roles[DataReceivedRole] = "dataReceived";
|
roles[DataReceivedRole] = "dataReceived";
|
||||||
roles[DataSentRole] = "dataSent";
|
roles[DataSentRole] = "dataSent";
|
||||||
|
roles[AllowedIpsRole] = "allowedIps";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ PageType {
|
||||||
Connections {
|
Connections {
|
||||||
target: ImportController
|
target: ImportController
|
||||||
|
|
||||||
function onImportErrorOccurred(errorMessage, goToPageHome) {
|
function onImportErrorOccurred(error, goToPageHome) {
|
||||||
if (goToPageHome) {
|
if (goToPageHome) {
|
||||||
PageController.goToStartPage()
|
PageController.goToStartPage()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ PageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onWrongInstallationUser(message) {
|
||||||
|
onInstallationErrorOccurred(message)
|
||||||
|
}
|
||||||
|
|
||||||
function onUpdateContainerFinished(message) {
|
function onUpdateContainerFinished(message) {
|
||||||
PageController.showNotificationMessage(message)
|
PageController.showNotificationMessage(message)
|
||||||
PageController.closePage()
|
PageController.closePage()
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,13 @@ QString Utils::getNextDriverLetter()
|
||||||
|
|
||||||
QString Utils::getRandomString(int len)
|
QString Utils::getRandomString(int len)
|
||||||
{
|
{
|
||||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||||
|
|
||||||
QString randomString;
|
QString randomString;
|
||||||
|
|
||||||
for (int i = 0; i < len; ++i) {
|
for (int i = 0; i < len; ++i) {
|
||||||
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
|
||||||
QChar nextChar = possibleCharacters.at(index);
|
|
||||||
randomString.append(nextChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomString;
|
return randomString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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 (...) {}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ public:
|
||||||
static QString certUtilPath();
|
static QString certUtilPath();
|
||||||
static QString tun2socksPath();
|
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
|
#ifdef Q_OS_WIN
|
||||||
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
|
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
|
||||||
static QString getNextDriverLetter();
|
static QString getNextDriverLetter();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue