Compare commits

...

72 Commits
1.3 ... 1.7

Author SHA1 Message Date
pokamest
d4c0e519d9 ui fixes 2021-05-19 00:15:40 +03:00
pokamest
4ba964db47 win7 support fixes
wizard added
2021-05-18 15:50:52 +03:00
pokamest
45e5ec76dd Windows 7 tap support improved 2021-05-14 23:30:13 +03:00
pokamest
df27003998 win build fix 2021-05-14 23:29:09 +03:00
pokamest
9002568474 Macos cached config fix 2021-05-14 04:27:30 -07:00
pokamest
5c5411261a macos dns setup fixed 2021-05-13 08:23:56 -07:00
pokamest
acf878c8dd Macos route add reimplemented using system call 2021-05-13 08:20:38 -07:00
pokamest
491a09b175 macos autostart fix 2021-05-12 13:07:22 -07:00
pokamest
51f7e6811e macos ui fix 2021-05-12 02:49:36 -07:00
pokamest
eee6b8b10f main.cpp default font removed 2021-05-11 12:15:33 -07:00
pokamest
dc4a1c6eca macos fix 2021-05-11 09:36:43 -07:00
pokamest
02810ff844 bug fixes 2021-05-11 17:04:04 +03:00
pokamest
1bb2ef9e30 ui fixes 2021-05-10 20:51:38 +03:00
pokamest
df2a6dc278 cloak for macos fixed 2021-05-10 05:25:20 -07:00
pokamest
835f767c3f import/export fixes 2021-05-10 14:19:36 +03:00
pokamest
e3fb239de9 Config export 2021-05-10 02:33:31 +03:00
pokamest
de67f244da Multiprotocol support 2021-05-07 23:28:37 +03:00
pokamest
d424bb24cf refactoring
Protocol to DockerContainer
2021-04-26 23:19:19 +03:00
pokamest
615bba69e5 refactoring 2021-04-26 22:54:31 +03:00
pokamest
7bba7a9eab cygwin grep added 2021-04-20 02:11:14 +03:00
pokamest
a5e9cea22f Release 1.6 WIP 2021-04-20 02:09:47 +03:00
pokamest
f9affb083b Macos route delete fix 2021-04-19 14:34:47 +03:00
pokamest
85b6b06cc9 - no dockerhub
- trafic masking
2021-04-04 23:12:36 +03:00
pokamest
059c6404ab gitignore updated 2021-04-04 23:09:31 +03:00
pokamest
0d989a7fae meta version updated 2021-04-04 23:08:01 +03:00
pokamest
7dfc002316 cloak exe added 2021-04-04 23:06:54 +03:00
pokamest
8d8d392e84 cygwin updated to x64 2021-04-04 23:04:31 +03:00
pokamest
c6e75d5f86 server script fixes 2021-03-25 23:26:59 +03:00
pokamest
99bfd56ef4 minor release 1.5.3 2021-03-19 15:03:44 +03:00
pokamest
c2f6c7d939 route delete fixed (Windows) 2021-03-18 22:13:05 +03:00
pokamest
d831d68e73 ShadowSocks protocol fixes:
- remote for OpenVPN is set to real ip address
- remote ip will be added as alias in docker container
- ss-local graceful shutdown
- crash fixes
2021-03-18 18:45:08 +03:00
pokamest
84e4b776ac openvpn target host is 10.8.0.1 via ss route 2021-03-17 03:47:33 +03:00
pokamest
f9e1b2c6dc QR code lib added
ShadowSocks export
ui stylesheet fixes
ip:port regexp fixed
dns settings reset bug fixed
2021-03-17 03:45:38 +03:00
pokamest
54fca5bebc cygpcre-1.dll added 2021-03-16 22:15:27 +03:00
pokamest
c5ce417d79 macos close button fix 2021-03-14 12:52:19 -07:00
pokamest
6765142ebc ssh key auth fix 2021-03-14 12:51:52 -07:00
pokamest
ca898a6759 Ssh key auth support added
yum/apt install support
2021-03-14 21:19:11 +03:00
pokamest
a2bb382652 ShadowSocks password - sha256 2021-03-13 14:16:24 +03:00
pokamest
02f966bc67 Win7 fix
Connection import fix
2021-03-13 13:56:52 +03:00
pokamest
65acdc8c09 readme added 2021-03-09 18:48:59 +03:00
pokamest
407ea77a9e ssh connection strict check disabled 2021-03-09 18:45:41 +03:00
pokamest
3e4a9f54a7 cygwin binaries added 2021-03-09 18:44:45 +03:00
pokamest
6b77bd5f13 Server install fix 2021-03-08 18:17:50 +03:00
pokamest
fb6de25e5f Migrate to cygwin sh 2021-03-06 15:07:43 +03:00
pokamest
ffbe5107e2 Secondary instance fix 2021-03-06 14:59:55 +03:00
pokamest
40a5b2e3f3 sites list ui fix 2021-02-25 23:11:46 +03:00
pokamest
c683884868 sites list reimplement 2021-02-25 21:16:00 +03:00
pokamest
65961d8d2e vpnconnection.cpp crash fix
ss server sript fix
2021-02-25 18:05:42 +03:00
pokamest
7dc1f1e225 Utils ip address regexp 2021-02-25 18:03:24 +03:00
pokamest
a47ab15aef mainwindow.ui fix 2021-02-25 01:06:02 +03:00
pokamest
c74efdaa9b ui fix for macos 2021-02-24 13:41:32 -08:00
pokamest
39224c7bf7 SingleApplication 2021-02-24 13:38:23 -08:00
pokamest
96aa3d409d SingleApplication 2021-02-24 23:40:57 +03:00
pokamest
c63990f720 Auto start
Auto connect
Dns settings
ui fixes
2021-02-24 21:58:32 +03:00
pokamest
ad643bf76e new icon 2021-02-22 18:07:39 +03:00
pokamest
c7ea4966fd minor fixes:
-build_windows.bat
-win build fix
-qdebug fix
2021-02-22 16:31:43 +03:00
pokamest
8fd81be477 ShadowSocks fixes for MacOS 2021-02-21 09:44:53 -08:00
pokamest
a1cb4ac544 Custom routing done
ShadowSocks enabled by default
2021-02-18 15:00:41 +03:00
pokamest
f91854594c Merge branch 'dev' into service_refact 2021-02-11 19:17:16 +03:00
pokamest
f661ea1d46 Merge branch 'macos_build_fix' into dev 2021-02-10 10:44:37 -08:00
pokamest
f50eea3eaf macos signing fixes 2021-02-10 06:57:26 -08:00
pokamest
c15b57e690 windows travis fix 2021-02-10 00:07:12 +03:00
pokamest
5f7ef31345 win cert updated 2021-02-09 00:33:26 +03:00
pokamest
447410a27a Macos build fix (#6)
macos deploy fixes
2021-02-08 23:57:35 +03:00
pokamest
2aa9f9cca9 macos build fix 2021-02-08 12:42:48 -08:00
pokamest
cba27d354d macos deploy fixes 2021-02-08 21:10:34 +03:00
pokamest
b398f42ada ipc process fix 2021-02-03 20:05:50 +03:00
pokamest
b6571d99de Qt ro refact 2021-02-03 15:42:36 +03:00
pokamest
b2392c1943 Qt Remote objects done 2021-02-02 22:51:31 +03:00
pokamest
048a673d31 Qt remote objects IPC 2021-02-02 01:47:40 +03:00
pokamest
c4df9c004b Merge branch 'dev' into service_refact 2021-01-30 15:03:01 +03:00
pokamest
22b33a4f25 remote_obj 2021-01-20 23:07:23 +03:00
206 changed files with 18446 additions and 3567 deletions

2
.gitignore vendored
View File

@@ -3,6 +3,7 @@
macOSPackage/
AmneziaVPN.dmg
AmneziaVPN.exe
AmneziaVPN_*.exe
deploy/build/*
winbuild.bat
@@ -35,6 +36,7 @@ CMakeLists.txt.user*
.DS_Store
._.DS_Store
._*
*.dmg
# tmp files
*.*~

View File

@@ -14,15 +14,7 @@ jobs:
env:
- QT_VERSION=5.15.1
before_install:
- export CERTIFICATE_P12=deploy/PrivacyTechAppleCert.p12
- export KEYCHAIN=build.keychain
- security create-keychain -p $MAC_CERT_PW $KEYCHAIN
- security default-keychain -s $KEYCHAIN
- security unlock-keychain -p $MAC_CERT_PW $KEYCHAIN
- security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MAC_CERT_PW -T /usr/bin/codesign
script:
- |
if [ ! -f $HOME/Qt/$QT_VERSION/clang_64/bin/qmake ]; then \
@@ -38,7 +30,7 @@ jobs:
token: $GH_TOKEN
skip_cleanup: true
file:
- "AmneziaVPN.dmg"
- "AmneziaVPN_unsigned.dmg"
on:
tags: true
branch: master
@@ -93,4 +85,4 @@ cache:
directories:
- $HOME/Qt
- /C/Qt
- $HOME/Library/Caches/Homebrew
- $HOME/Library/Caches/Homebrew

View File

@@ -1,2 +1,3 @@
TEMPLATE = subdirs
SUBDIRS = client service platform

View File

@@ -1,2 +1,43 @@
# Amnezia
# Amnezia VPN
## _The best client for self-hosted VPN_
[![Build Status](https://travis-ci.com/amnezia-vpn/desktop-client.svg?branch=master)](https://travis-ci.com/amnezia-vpn/desktop-client)
Amnezia is a VPN client with the key feature of deploying your own VPN server on you virtual server.
## Features
- Very easy to use - enter your ip address, ssh login and password, and Amnezia client will automatically install VPN docker containers to your server and connect to VPN.
- OpenVPN and OpenVPN over ShadowSocks protocols support.
- Custom VPN routing mode support - add any sites to client to enable VPN only for them.
- Windows and MacOS support.
- Unsecure sharing connection profile for family use.
## Tech
AmneziaVPN uses a number of open source projects to work:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [ShadowSocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [EasyRSA](https://github.com/OpenVPN/easy-rsa) - part of OpenVPN
- [CygWin](https://www.cygwin.com/) - only for Windiws, used for launching EasyRSA scripts
- [QtSsh](https://github.com/jaredtao/QtSsh) - forked form Qt Creator
- and more...
## Development
Want to contribute? Welcome!
Use Qt Creator for fast developing.
### Building sources and deployment
Easiest way to build your own executables - is to fork project and configure [Travis CI](https://travis-ci.com/)
Or you can build sources manually using Qt Creator. Qt >= 14.2 supported.
Look to the `build_macos.sh` and `build_windows.bat` scripts in `deploy` folder for details.
## License
GPL v.3
## Contacts
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - main support channel
[https://amnezia.org](https://amnezia.org) - our website

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
#if !defined(AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_)
#define AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
/////////////////////////////////////////////////////////////////////////////
//
#define QR_LEVEL_L 0
#define QR_LEVEL_M 1
#define QR_LEVEL_Q 2
#define QR_LEVEL_H 3
//
#define QR_MODE_NUMERAL 0
#define QR_MODE_ALPHABET 1
#define QR_MODE_8BIT 2
#define QR_MODE_KANJI 3
//
#define QR_VRESION_S 0
#define QR_VRESION_M 1
#define QR_VRESION_L 2
#define MAX_ALLCODEWORD 3706
#define MAX_DATACODEWORD 2956
#define MAX_CODEBLOCK 153
#define MAX_MODULESIZE 177
#define QR_MARGIN 0
/////////////////////////////////////////////////////////////////////////////
typedef struct tagRS_BLOCKINFO
{
int ncRSBlock;
int ncAllCodeWord;
int ncDataCodeWord;
} RS_BLOCKINFO, *LPRS_BLOCKINFO;
/////////////////////////////////////////////////////////////////////////////
typedef struct tagQR_VERSIONINFO
{
int nVersionNo;
int ncAllCodeWord;
int ncDataCodeWord[4];
int ncAlignPoint;
int nAlignPoint[6];
RS_BLOCKINFO RS_BlockInfo1[4];
RS_BLOCKINFO RS_BlockInfo2[4];
} QR_VERSIONINFO, *LPQR_VERSIONINFO;
/////////////////////////////////////////////////////////////////////////////
class CQR_Encode
{
public:
CQR_Encode();
~CQR_Encode();
public:
int m_nLevel;
int m_nVersion;
bool m_bAutoExtent;
int m_nMaskingNo;
public:
int m_nSymbleSize;
unsigned char m_byModuleData[MAX_MODULESIZE][MAX_MODULESIZE]; // [x][y]
private:
int m_ncDataCodeWordBit;
unsigned char m_byDataCodeWord[MAX_DATACODEWORD];
int m_ncDataBlock;
unsigned char m_byBlockMode[MAX_DATACODEWORD];
int m_nBlockLength[MAX_DATACODEWORD];
int m_ncAllCodeWord;
unsigned char m_byAllCodeWord[MAX_ALLCODEWORD];
unsigned char m_byRSWork[MAX_CODEBLOCK];
public:
bool EncodeData(int nLevel, int nVersion, bool bAutoExtent, int nMaskingNo, char* lpsSource, int ncSource = 0);
private:
int GetEncodeVersion(int nVersion, char* lpsSource, int ncLength);
bool EncodeSourceData(char* lpsSource, int ncLength, int nVerGroup);
int GetBitLength(unsigned char nMode, int ncData, int nVerGroup);
int SetBitStream(int nIndex, unsigned short wData, int ncData);
bool IsNumeralData(unsigned char c);
bool IsAlphabetData(unsigned char c);
bool IsKanjiData(unsigned char c1, unsigned char c2);
unsigned char AlphabetToBinaly(unsigned char c);
unsigned short KanjiToBinaly(unsigned short wc);
void GetRSCodeWord(unsigned char * lpbyRSWork, int ncDataCodeWord, int ncRSCodeWord);
private:
void FormatModule();
void SetFunctionModule();
void SetFinderPattern(int x, int y);
void SetAlignmentPattern(int x, int y);
void SetVersionPattern();
void SetCodeWordPattern();
void SetMaskingPattern(int nPatternNo);
void SetFormatInfoPattern(int nPatternNo);
int CountPenalty();
};
/////////////////////////////////////////////////////////////////////////////
#endif // !defined(AFX_QR_ENCODE_H__AC886DF7_C0AE_4C9F_AC7A_FCDA8CB1DD37__INCLUDED_)

View File

@@ -0,0 +1,5 @@
HEADERS += \
3rd/QRCodeGenerator/QRCodeGenerator.h \
SOURCES += \
3rd/QRCodeGenerator/QRCodeGenerator.cpp \

View File

@@ -823,11 +823,13 @@ void SshConnectionPrivate::createPrivateKey()
if (m_connParams.privateKeyFile.isEmpty())
throw SshClientException(SshKeyFileError, tr("No private key file given."));
QFile keyFile(m_connParams.privateKeyFile);
if (!keyFile.open(QIODevice::ReadOnly)) {
throw SshClientException(SshKeyFileError,
tr("Private key file error: %1").arg(keyFile.errorString()));
if (keyFile.open(QIODevice::ReadOnly)) {
m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
else {
m_sendFacility.createAuthenticationKey(m_connParams.privateKeyFile.toUtf8());
}
m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)

View File

@@ -0,0 +1,274 @@
// 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();
}

View File

@@ -0,0 +1,154 @@
// 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

View File

@@ -0,0 +1,15 @@
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
}

View File

@@ -0,0 +1,486 @@
// 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;
}

View File

@@ -0,0 +1,104 @@
// 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

View File

@@ -1,4 +1,4 @@
QT += widgets core gui network xml
QT += widgets core gui network xml remoteobjects
TARGET = AmneziaVPN
TEMPLATE = app
@@ -8,48 +8,65 @@ DEFINES += QT_DEPRECATED_WARNINGS
include("3rd/QtSsh/src/ssh/ssh.pri")
include("3rd/QtSsh/src/botan/botan.pri")
include("3rd/SingleApplication/singleapplication.pri")
include("3rd/QRCodeGenerator/QRCodeGenerator.pri")
HEADERS += \
communicator.h \
../ipc/ipc.h \
configurators/cloak_configurator.h \
configurators/shadowsocks_configurator.h \
configurators/vpn_configurator.h \
core/defs.h \
core/errorstrings.h \
core/openvpnconfigurator.h \
core/ipcclient.h \
configurators/openvpn_configurator.h \
core/scripts_registry.h \
core/server_defs.h \
core/servercontroller.h \
debug.h \
defines.h \
localclient.h \
managementserver.h \
message.h \
protocols/openvpnovercloakprotocol.h \
protocols/protocols_defs.h \
protocols/shadowsocksvpnprotocol.h \
runguard.h \
settings.h \
ui/Controls/SlidingStackedWidget.h \
ui/mainwindow.h \
ui/qautostart.h \
ui/server_widget.h \
utils.h \
vpnconnection.h \
protocols/vpnprotocol.h \
protocols/openvpnprotocol.h \
SOURCES += \
communicator.cpp \
core/openvpnconfigurator.cpp \
configurators/cloak_configurator.cpp \
configurators/shadowsocks_configurator.cpp \
configurators/vpn_configurator.cpp \
core/errorstrings.cpp \
core/ipcclient.cpp \
configurators/openvpn_configurator.cpp \
core/scripts_registry.cpp \
core/server_defs.cpp \
core/servercontroller.cpp \
debug.cpp \
localclient.cpp \
main.cpp \
managementserver.cpp \
message.cpp \
protocols/openvpnovercloakprotocol.cpp \
protocols/protocols_defs.cpp \
protocols/shadowsocksvpnprotocol.cpp \
runguard.cpp \
settings.cpp \
ui/Controls/SlidingStackedWidget.cpp \
ui/mainwindow.cpp \
ui/qautostart.cpp \
ui/server_widget.cpp \
utils.cpp \
vpnconnection.cpp \
protocols/vpnprotocol.cpp \
protocols/openvpnprotocol.cpp \
FORMS += ui/mainwindow.ui
FORMS += ui/mainwindow.ui \
ui/server_widget.ui
RESOURCES += \
resources.qrc
@@ -100,3 +117,6 @@ macx {
LIBS += -framework Cocoa -framework ApplicationServices -framework CoreServices -framework Foundation -framework AppKit
}
REPC_REPLICA += ../ipc/ipcinterface.rep

View File

@@ -1,79 +0,0 @@
#include "communicator.h"
#include "defines.h"
#include "localclient.h"
#include "utils.h"
Communicator::Communicator(QObject* parent) : QObject(parent),
m_localClient(nullptr)
{
connectToServer();
}
Communicator::~Communicator()
{
}
void Communicator::connectToServer()
{
if (m_localClient) {
delete m_localClient;
}
m_localClient = new LocalClient(this);
connect(m_localClient, &LocalClient::connected, this, &Communicator::onConnected);
connect(m_localClient, &LocalClient::lineAvailable, this, &Communicator::onLineAvailable);
m_localClient->connectToServer(Utils::serverName());
}
void Communicator::onConnected()
{
qDebug().noquote() << QString("Connected to local server '%1'").arg(m_localClient->serverName());
Message message(Message::State::Initialize, QStringList({"Client"}));
sendMessage(message);
}
void Communicator::onLineAvailable(const QString& line)
{
Message message(line);
if (!message.isValid()) {
qDebug() << "Message is not valid";
return;
}
emit messageReceived(message);
}
bool Communicator::isConnected() const
{
if (!m_localClient) {
return false;
}
return m_localClient->connectedState();
}
QString Communicator::readData()
{
return QString();
}
bool Communicator::writeData(const QString& data)
{
return m_localClient->write(data.toUtf8());
}
void Communicator::sendMessage(const Message& message)
{
if (!isConnected()) {
return;
}
const QString data = message.toString();
bool status = writeData(data + "\n");
qDebug().noquote() << QString("Send message '%1',%2 status '%2'").
arg(static_cast<int>(message.state())).
arg(data).
arg(Utils::toString(status));
}

View File

@@ -1,41 +0,0 @@
#ifndef COMMUNICATOR_H
#define COMMUNICATOR_H
#include <QObject>
#include <QStringList>
#include "message.h"
class LocalClient;
class Communicator : public QObject
{
Q_OBJECT
public:
explicit Communicator(QObject* parent = nullptr);
~Communicator();
bool isConnected() const;
void sendMessage(const Message& message);
signals:
void messageReceived(const Message& message);
void comminicatorConnected();
void comminicatorDisconnected();
protected slots:
void onConnected();
void onLineAvailable(const QString& line);
protected:
QString readData();
bool writeData(const QString& data);
void connectToServer();
LocalClient* m_localClient;
};
#endif // COMMUNICATOR_H

View File

@@ -0,0 +1,48 @@
#include "cloak_configurator.h"
#include <QFile>
#include <QJsonObject>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ErrorCode e = ErrorCode::NoError;
QString cloakPublicKey = ServerController::getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckPublicKeyPath, &e);
cloakPublicKey.replace("\n", "");
QString cloakBypassUid = ServerController::getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
cloakBypassUid.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
return "";
}
QJsonObject config;
config.insert("Transport", "direct");
config.insert("ProxyMethod", "openvpn");
config.insert("EncryptionMethod", "aes-gcm");
config.insert("UID", cloakBypassUid);
config.insert("PublicKey", cloakPublicKey);
config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS");
config.insert("NumConn", 4);
config.insert("BrowserSig", "chrome");
config.insert("StreamTimeout", 300);
// transfer params to protocol runner
config.insert(config_key::transport_proto, "$OPENVPN_TRANSPORT_PROTO");
config.insert(config_key::remote, credentials.hostName);
config.insert(config_key::port, "$CLOAK_SERVER_PORT");
QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(),
ServerController::genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg;
}

View File

@@ -0,0 +1,18 @@
#ifndef CLOAK_CONFIGURATOR_H
#define CLOAK_CONFIGURATOR_H
#include <QObject>
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class CloakConfigurator
{
public:
static QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // CLOAK_CONFIGURATOR_H

View File

@@ -0,0 +1,304 @@
#include "openvpn_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QDebug>
#include <QTemporaryFile>
#include <utils.h>
#include "core/server_defs.h"
#include "protocols/protocols_defs.h"
#include "core/scripts_registry.h"
QString OpenVpnConfigurator::getEasyRsaShPath()
{
#ifdef Q_OS_WIN
// easyrsa sh path should looks like
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
easyRsaShPath = "\"" + easyRsaShPath + "\"";
qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath;
return easyRsaShPath;
#else
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
#endif
}
QProcessEnvironment OpenVpnConfigurator::prepareEnv()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnvVar = env.value("PATH");
#ifdef Q_OS_WIN
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\i386;");
#else
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
//qDebug().noquote() << "ENV PATH" << pathEnvVar;
return env;
}
ErrorCode OpenVpnConfigurator::initPKI(const QString &path)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " init-pki"));
qDebug().noquote() << "EasyRsa tmp path" << path;
qDebug().noquote() << "EasyRsa args" << p.nativeArguments();
#else
p.setProgram(getEasyRsaShPath());
p.setArguments(QStringList() << "init-pki");
#endif
p.setWorkingDirectory(path);
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
qDebug().noquote() << "Init PKI" << p.readAll();
});
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &clientId)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"ash.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass"));
qDebug().noquote() << "EasyRsa args" << p.nativeArguments();
#else
p.setArguments(QStringList() << "gen-req" << clientId << "nopass");
p.setProgram(getEasyRsaShPath());
#endif
p.setWorkingDirectory(path);
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
QString data = p.readAll();
qDebug().noquote() << data;
if (data.contains("Common Name (eg: your user, host, or server name)")) {
p.write("\n");
}
});
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
{
OpenVpnConfigurator::ConnectionData connData;
connData.clientId = Utils::getRandomString(32);
QTemporaryDir dir;
// if (dir.isValid()) {
// // dir.path() returns the unique directory path
// }
QString path = dir.path();
initPKI(path);
ErrorCode errorCode = genReq(path, connData.clientId);
Q_UNUSED(errorCode)
QFile req(path + "/pki/reqs/" + connData.clientId + ".req");
req.open(QIODevice::ReadOnly);
connData.request = req.readAll();
QFile key(path + "/pki/private/" + connData.clientId + ".key");
key.open(QIODevice::ReadOnly);
connData.privKey = key.readAll();
// qDebug().noquote() << connData.request;
// qDebug().noquote() << connData.privKey;
return connData;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing;
return connData;
}
QString reqFileName = QString("%1/%2.req").
arg(amnezia::protocols::openvpn::clientsDirPath).
arg(connData.clientId);
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
e = signCert(container, credentials, connData.clientId);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e);
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials,
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
}
return connData;
}
Settings &OpenVpnConfigurator::m_settings()
{
static Settings s;
return s;
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{
QString config = ServerController::replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
ServerController::genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode && *errorCode) {
return "";
}
config.replace("$OPENVPN_CA_CERT", connData.caCert);
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", connData.taKey);
}
else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
}
#ifdef Q_OS_MAC
config.replace("block-outside-dns", "");
#endif
//qDebug().noquote() << config;
return processConfigWithLocalSettings(config);
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config)
{
// TODO replace DNS if it already set
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
if (m_settings().customRouting()) {
config.replace("redirect-gateway def1 bypass-dhcp", "");
}
else {
if(!config.contains("redirect-gateway def1 bypass-dhcp")) {
config.append("redirect-gateway def1 bypass-dhcp\n");
}
}
#ifdef Q_OS_MAC
config.replace("block-outside-dns", "");
QString dnsConf = QString(
"\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n").
arg(qApp->applicationDirPath());
config.append(dnsConf);
#endif
return config;
}
QString OpenVpnConfigurator::convertOpenSShKey(const QString &key)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
QTemporaryFile tmp;
#ifdef QT_DEBUG
tmp.setAutoRemove(false);
#endif
tmp.open();
tmp.write(key.toUtf8());
tmp.close();
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
#else
p.setProgram("ssh-keygen");
p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName());
#endif
p.start();
p.waitForFinished();
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
tmp.open();
return tmp.readAll();
}
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
{
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
"easyrsa import-req %2/%3.req %3\"")
.arg(amnezia::containerToString(container))
.arg(amnezia::protocols::openvpn::clientsDirPath)
.arg(clientId);
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
"easyrsa sign-req client %2\"")
.arg(amnezia::containerToString(container))
.arg(clientId);
QStringList scriptList {script_import, script_sign};
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
return ServerController::runScript(ServerController::sshParams(credentials), script);
}

View File

@@ -1,12 +1,12 @@
#ifndef OPENVPNCONFIGURATOR_H
#define OPENVPNCONFIGURATOR_H
#ifndef OPENVPN_CONFIGURATOR_H
#define OPENVPN_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "defs.h"
#include "servercontroller.h"
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class OpenVpnConfigurator
{
@@ -22,11 +22,17 @@ public:
QString host; // host ip
};
static QString genOpenVpnConfig(const ServerCredentials &credentials, Protocol proto,
ErrorCode *errorCode = nullptr);
static QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
static QString processConfigWithLocalSettings(QString config);
static QString convertOpenSShKey(const QString &key);
static ErrorCode signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId);
private:
static QString getRandomString(int len);
static QString getEasyRsaShPath();
static QProcessEnvironment prepareEnv();
@@ -36,7 +42,9 @@ private:
static ConnectionData createCertRequest();
static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode = nullptr);
DockerContainer container, ErrorCode *errorCode = nullptr);
static Settings &m_settings();
};
#endif // OPENVPNCONFIGURATOR_H
#endif // OPENVPN_CONFIGURATOR_H

View File

@@ -0,0 +1,37 @@
#include "shadowsocks_configurator.h"
#include <QFile>
#include <QJsonObject>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ErrorCode e = ErrorCode::NoError;
QString ssKey = ServerController::getTextFileFromContainer(container, credentials,
amnezia::protocols::shadowsocks::ssKeyPath, &e);
ssKey.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
return "";
}
QJsonObject config;
config.insert("server", credentials.hostName);
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
config.insert("password", ssKey);
config.insert("timeout", 60);
config.insert("method", "$SHADOWSOCKS_CIPHER");
QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(),
ServerController::genVarsForScript(credentials, container, containerConfig));
qDebug().noquote() << textCfg;
return textCfg;
}

View File

@@ -0,0 +1,18 @@
#ifndef SHADOWSOCKS_CONFIGURATOR_H
#define SHADOWSOCKS_CONFIGURATOR_H
#include <QObject>
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class ShadowSocksConfigurator
{
public:
static QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // SHADOWSOCKS_CONFIGURATOR_H

View File

@@ -0,0 +1,33 @@
#include "vpn_configurator.h"
#include "openvpn_configurator.h"
#include "cloak_configurator.h"
#include "shadowsocks_configurator.h"
//#include "wireguard_configurator.h"
#include <QFile>
#include <QJsonObject>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode)
{
switch (proto) {
case Protocol::OpenVpn:
return OpenVpnConfigurator::genOpenVpnConfig(credentials, container, containerConfig, errorCode);
case Protocol::ShadowSocks:
return ShadowSocksConfigurator::genShadowSocksConfig(credentials, container, containerConfig, errorCode);
case Protocol::Cloak:
return CloakConfigurator::genCloakConfig(credentials, container, containerConfig, errorCode);
// case Protocol::WireGuard:
// return WireGuardConfigurator::genWireGuardConfig(credentials, container, containerConfig, errorCode);
default:
return "";
}
}

View File

@@ -0,0 +1,18 @@
#ifndef VPN_CONFIGURATOR_H
#define VPN_CONFIGURATOR_H
#include <QObject>
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class VpnConfigurator
{
public:
static QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode = nullptr);
};
#endif // VPN_CONFIGURATOR_H

View File

@@ -1,29 +1,19 @@
#ifndef DEFS_H
#define DEFS_H
#include <QMetaEnum>
#include <QObject>
namespace amnezia {
enum class Protocol {
Any,
OpenVpn,
ShadowSocks,
WireGuard
};
enum class DockerContainer {
OpenVpn,
ShadowSocks,
WireGuard
};
struct ServerCredentials
{
QString hostName;
QString userName;
QString password;
int port = 22;
bool isValid() { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
};
enum ErrorCode
@@ -34,8 +24,10 @@ enum ErrorCode
InternalError,
NotImplementedError,
// Server errorz
// Server errors
ServerCheckFailed,
ServerPortAlreadyAllocatedError,
ServerContainerMissingError,
// Ssh connection errors
SshSocketError, SshTimeoutError, SshProtocolError,
@@ -45,6 +37,7 @@ enum ErrorCode
// Ssh remote process errors
SshRemoteProcessCreationError,
FailedToStartRemoteProcessError, RemoteProcessCrashError,
SshSftpError,
// Local errors
FailedToSaveConfigData,
@@ -55,13 +48,29 @@ enum ErrorCode
// Distro errors
OpenVpnExecutableMissing,
EasyRsaExecutableMissing,
ShadowSocksExecutableMissing,
CloakExecutableMissing,
AmneziaServiceConnectionFailed,
// VPN errors
OpenVpnAdaptersInUseError,
OpenVpnUnknownError
OpenVpnUnknownError,
// 3rd party utils errors
OpenVpnExecutableCrashed,
ShadowSocksExecutableCrashed,
CloakExecutableCrashed
};
namespace config {
// config keys
const char key_openvpn_config_data[] = "openvpn_config_data";
const char key_openvpn_config_path[] = "openvpn_config_path";
const char key_shadowsocks_config_data[] = "shadowsocks_config_data";
const char key_cloak_config_data[] = "cloak_config_data";
}
} // namespace amnezia
#endif // DEFS_H

View File

@@ -0,0 +1,58 @@
#include "errorstrings.h"
using namespace amnezia;
QString errorString(ErrorCode code){
switch (code) {
// General error codes
case(NoError): return QObject::tr("No error");
case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): return QObject::tr("Function not implemented");
// Server errors
case(ServerCheckFailed): return QObject::tr("Server check failed");
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
// Ssh connection errors
case(SshSocketError): return QObject::tr("Ssh connection error");
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
case(SshProtocolError): return QObject::tr("Ssh protocol error");
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
case(SshKeyFileError): return QObject::tr("Ssh key file error");
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
case(SshInternalError): return QObject::tr("Ssh internal error");
// Ssh remote process errors
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
// Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case(InternalError):
default:
return QObject::tr("Internal error");
}
}
QDebug operator<<(QDebug debug, const ErrorCode &e)
{
QDebugStateSaver saver(debug);
debug.nospace() << "ErrorCode::" << int(e) << "(" << errorString(e) << ")";
return debug;
}

View File

@@ -1,52 +1,14 @@
#ifndef ERRORSTRINGS_H
#define ERRORSTRINGS_H
#include <QDebug>
#include "defs.h"
using namespace amnezia;
static QString errorString(ErrorCode code){
switch (code) {
// General error codes
case(NoError): return QObject::tr("No error");
case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): return QObject::tr("Function not implemented");
case(ServerCheckFailed): return QObject::tr("Server check failed");
// Ssh connection errors
case(SshSocketError): return QObject::tr("Ssh connection error");
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
case(SshProtocolError): return QObject::tr("Ssh protocol error");
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
case(SshKeyFileError): return QObject::tr("Ssh key file error");
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
case(SshInternalError): return QObject::tr("Ssh internal error");
// Ssh remote process errors
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
// Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case(InternalError):
default:
return QObject::tr("Internal error");
}
}
QString errorString(ErrorCode code);
QDebug operator<<(QDebug debug, const ErrorCode &e);
#endif // ERRORSTRINGS_H

79
client/core/ipcclient.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "ipcclient.h"
#include <QRemoteObjectNode>
IpcClient &IpcClient::Instance()
{
static IpcClient s;
return s;
}
bool IpcClient::init()
{
Instance().m_localSocket->waitForConnected();
if (!Instance().m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
return Instance().m_ipcClient->isReplicaValid();
}
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
{
if (! Instance().m_ipcClient || ! Instance().m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance().m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(1000);
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance().m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
pd->ipcProcess.reset(pd->replicaNode->acquire<IpcProcessInterfaceReplica>());
if (!pd->ipcProcess) {
qWarning() << "Acquire IpcProcessInterfaceReplica failed";
}
else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "IpcProcessInterfaceReplica replica is not connected!";
}
connect(pd->ipcProcess.data(), &IpcProcessInterfaceReplica::destroyed, pd->ipcProcess.data(), [pd](){
pd->replicaNode->deleteLater();
});
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto proccessReplica = QSharedPointer<IpcProcessInterfaceReplica>(pd->ipcProcess);
return proccessReplica;
}
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
m_localSocket.reset(new QLocalSocket(this));
connect(m_localSocket.data(), &QLocalSocket::connected, &m_ClientNode, [this]() {
m_ClientNode.addClientSideConnection(m_localSocket.data());
m_ipcClient.reset(m_ClientNode.acquire<IpcInterfaceReplica>());
m_ipcClient->waitForSource(1000);
if (!m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
});
m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
}

42
client/core/ipcclient.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef IPCCLIENT_H
#define IPCCLIENT_H
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipcinterface_replica.h"
class IpcClient : public QObject
{
Q_OBJECT
public:
static IpcClient &Instance();
static bool init();
static QSharedPointer<IpcInterfaceReplica> Interface() { return Instance().m_ipcClient; }
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
signals:
private:
explicit IpcClient(QObject *parent = nullptr);
QRemoteObjectNode m_ClientNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QSharedPointer<QLocalSocket> m_localSocket;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<IpcProcessInterfaceReplica>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<IpcProcessInterfaceReplica> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
};
#endif // IPCCLIENT_H

View File

@@ -1,228 +0,0 @@
#include "openvpnconfigurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
#include <QRandomGenerator>
#include <QTemporaryDir>
#include <QDebug>
QString OpenVpnConfigurator::getRandomString(int len)
{
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString;
for(int i=0; i<len; ++i) {
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
QChar nextChar = possibleCharacters.at(index);
randomString.append(nextChar);
}
return randomString;
}
QString OpenVpnConfigurator::getEasyRsaShPath()
{
#ifdef Q_OS_WIN
// easyrsa sh path should looks like
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
easyRsaShPath.replace("C:\\", "");
easyRsaShPath.replace("\\", "/");
easyRsaShPath.prepend("/");
//return "\"" + easyRsaShPath + "\"";
return "\"/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa\"";
#else
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
#endif
}
QProcessEnvironment OpenVpnConfigurator::prepareEnv()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnvVar = env.value("PATH");
#ifdef Q_OS_WIN
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\bin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\i386;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\x64;");
#else
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
return env;
}
ErrorCode OpenVpnConfigurator::initPKI(const QString &path)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " init-pki"));
#else
p.setProgram(getEasyRsaShPath());
p.setArguments(QStringList() << "init-pki");
#endif
p.setWorkingDirectory(path);
// QObject::connect(&p, &QProcess::channelReadyRead, [&](){
// qDebug().noquote() << p.readAll();
// });
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &clientId)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass"));
#else
p.setArguments(QStringList() << "gen-req" << clientId << "nopass");
p.setProgram(getEasyRsaShPath());
#endif
p.setWorkingDirectory(path);
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
QString data = p.readAll();
//qDebug().noquote() << data;
if (data.contains("Common Name (eg: your user, host, or server name)")) {
p.write("\n");
}
});
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
{
OpenVpnConfigurator::ConnectionData connData;
connData.clientId = getRandomString(32);
QTemporaryDir dir;
// if (dir.isValid()) {
// // dir.path() returns the unique directory path
// }
QString path = dir.path();
initPKI(path);
ErrorCode errorCode = genReq(path, connData.clientId);
Q_UNUSED(errorCode)
QFile req(path + "/pki/reqs/" + connData.clientId + ".req");
req.open(QIODevice::ReadOnly);
connData.request = req.readAll();
QFile key(path + "/pki/private/" + connData.clientId + ".key");
key.open(QIODevice::ReadOnly);
connData.privKey = key.readAll();
// qDebug().noquote() << connData.request;
// qDebug().noquote() << connData.privKey;
return connData;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing;
return connData;
}
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
DockerContainer container;
if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn;
else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks;
else {
if (errorCode) *errorCode = ErrorCode::InternalError;
return connData;
}
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
e = ServerController::signCert(container, credentials, connData.clientId);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::caCertPath(), &e);
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
}
ServerController::setupServerFirewall(credentials);
return connData;
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{
QFile configTemplFile;
if (proto == Protocol::OpenVpn)
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
else if (proto == Protocol::ShadowSocks) {
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
}
configTemplFile.open(QIODevice::ReadOnly);
QString config = configTemplFile.readAll();
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
if (proto == Protocol::OpenVpn)
config.replace("$PROTO", "udp");
else if (proto == Protocol::ShadowSocks) {
config.replace("$PROTO", "tcp");
config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort()));
}
config.replace("$REMOTE_HOST", connData.host);
config.replace("$REMOTE_PORT", "1194");
config.replace("$CA_CERT", connData.caCert);
config.replace("$CLIENT_CERT", connData.clientCert);
config.replace("$PRIV_KEY", connData.privKey);
config.replace("$TA_KEY", connData.taKey);
return config;
}

View File

@@ -0,0 +1,68 @@
#include "scripts_registry.h"
#include <QObject>
#include <QDebug>
#include <QFile>
QString amnezia::scriptFolder(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn: return QLatin1String("openvpn");
case DockerContainer::OpenVpnOverCloak: return QLatin1String("openvpn_cloak");
case DockerContainer::OpenVpnOverShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
default: return "";
}
}
QString amnezia::scriptName(SharedScriptType type)
{
switch (type) {
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
}
}
QString amnezia::scriptName(ProtocolScriptType type)
{
switch (type) {
case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile");
case ProtocolScriptType::run_container: return QLatin1String("run_container.sh");
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
}
}
QString amnezia::scriptData(amnezia::SharedScriptType type)
{
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
QFile file(fileName);
if (! file.open(QIODevice::ReadOnly)) {
qDebug() << "Error opening script" << fileName;
return "";
}
QByteArray ba = file.readAll();
if (ba.isEmpty()) {
qDebug() << "Error, script is empty" << fileName;
}
return ba;
}
QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container)
{
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type));
QFile file(fileName);
if (! file.open(QIODevice::ReadOnly)) {
qDebug() << "Error opening script" << fileName;
return "";
}
QByteArray data = file.readAll();
data.replace("\r", "");
return data;
}

View File

@@ -0,0 +1,39 @@
#ifndef SCRIPTS_REGISTRY_H
#define SCRIPTS_REGISTRY_H
#include <QLatin1String>
#include "core/defs.h"
#include "protocols/protocols_defs.h"
namespace amnezia {
enum SharedScriptType {
// General scripts
prepare_host,
install_docker,
build_container,
remove_container,
remove_all_containers,
setup_host_firewall,
check_connection
};
enum ProtocolScriptType {
// Protocol scripts
dockerfile,
run_container,
configure_container,
container_startup,
openvpn_template
};
QString scriptFolder(DockerContainer container);
QString scriptName(SharedScriptType type);
QString scriptName(ProtocolScriptType type);
QString scriptData(SharedScriptType type);
QString scriptData(ProtocolScriptType type, DockerContainer container);
}
#endif // SCRIPTS_REGISTRY_H

View File

@@ -0,0 +1,16 @@
#include "server_defs.h"
//QString amnezia::containerToString(amnezia::DockerContainer container)
//{
// switch (container) {
// case(DockerContainer::OpenVpn): return "amnezia-openvpn";
// case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak";
// case(DockerContainer::OpenVpnOverShadowSocks): return "amnezia-shadowsocks";
// default: return "";
// }
//}
QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container)
{
return "/opt/amnezia/" + containerToString(container);
}

15
client/core/server_defs.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef SERVER_DEFS_H
#define SERVER_DEFS_H
#include <QObject>
#include "protocols/protocols_defs.h"
namespace amnezia {
namespace server {
//QString getContainerName(amnezia::DockerContainer container);
QString getDockerfileFolder(amnezia::DockerContainer container);
}
}
#endif // SERVER_DEFS_H

View File

@@ -1,5 +1,6 @@
#include "servercontroller.h"
#include <QCryptographicHash>
#include <QFile>
#include <QEventLoop>
#include <QLoggingCategory>
@@ -7,28 +8,31 @@
#include <QTimer>
#include <QJsonObject>
#include <QJsonDocument>
#include <QApplication>
#include <QTemporaryFile>
#include "sftpchannel.h"
#include "sshconnectionmanager.h"
#include "protocols/protocols_defs.h"
#include "server_defs.h"
#include "scripts_registry.h"
#include "utils.h"
using namespace QSsh;
QString ServerController::getContainerName(DockerContainer container)
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
{
switch (container) {
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
default: return "";
}
}
ErrorCode ServerController::runScript(DockerContainer container,
const SshConnectionParameters &sshParams, QString script)
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
SshConnection *client = connectToHost(sshParams);
if (client->state() != SshConnection::State::Connected) {
if (client->state() == SshConnection::State::Connecting) {
qDebug() << "ServerController::runScript aborted, connectToHost in progress";
return ErrorCode::SshTimeoutError;
}
else if (client->state() != SshConnection::State::Connected) {
qDebug() << "ServerController::runScript connectToHost error: " << fromSshConnectionErrorCode(client->errorState());
return fromSshConnectionErrorCode(client->errorState());
}
@@ -36,17 +40,36 @@ ErrorCode ServerController::runScript(DockerContainer container,
qDebug() << "Run script";
QString totalLine;
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
QString line = lines.at(i);
line.replace("$CONTAINER_NAME", getContainerName(container));
QString currentLine = lines.at(i);
QString nextLine;
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
if (line.startsWith("#")) {
if (totalLine.isEmpty()) {
totalLine = currentLine;
}
else {
totalLine = totalLine + "\n" + currentLine;
}
QString lineToExec;
if (currentLine.endsWith("\\")) {
continue;
}
else {
lineToExec = totalLine;
totalLine.clear();
}
// Run collected line
if (totalLine.startsWith("#")) {
continue;
}
qDebug().noquote() << "EXEC" << line;
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
qDebug().noquote() << "EXEC" << lineToExec;
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
@@ -66,18 +89,20 @@ ErrorCode ServerController::runScript(DockerContainer container,
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, &wait, [proc, cbReadStdOut](){
QString s = proc->readAllStandardOutput();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << s;
qDebug().noquote() << "stdout" << s;
}
if (cbReadStdOut) cbReadStdOut(s, proc);
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
QString s = proc->readAllStandardError();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << s;
qDebug().noquote() << "stderr" << s;
}
if (cbReadStdErr) cbReadStdErr(s, proc);
});
proc->start();
@@ -95,63 +120,45 @@ ErrorCode ServerController::runScript(DockerContainer container,
}
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, QString &file, const QString &path)
const ServerCredentials &credentials, const QString &file, const QString &path)
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
if (e) return e;
QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
arg(getContainerName(container)).arg(file).arg(path);
QString stdOut;
auto cbReadStd = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
stdOut += data + "\n";
};
qDebug().noquote() << script;
e = runScript(sshParams(credentials),
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
if (e) return e;
if (stdOut.contains("Error: No such container:")) {
return ErrorCode::ServerContainerMissingError;
}
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
runScript(sshParams(credentials),
replaceVars(QString("sudo shred %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
}
runScript(sshParams(credentials),
replaceVars(QString("sudo rm %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
QEventLoop wait;
int exitStatus = -1;
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
// qDebug() << "Command started";
// });
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
//qDebug() << "Remote process exited with status" << status;
exitStatus = status;
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
qDebug().noquote() << proc->readAllStandardOutput();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
qDebug().noquote() << proc->readAllStandardError();
});
proc->start();
//wait.exec();
if (exitStatus < 0) {
wait.exec();
}
return fromSshProcessExitStatus(exitStatus);
return e;
}
QString ServerController::getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
{
QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\"").
arg(getContainerName(container)).arg(path);
if (errorCode) *errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
arg(amnezia::containerToString(container)).arg(path);
qDebug().noquote() << "Copy file from container\n" << script;
@@ -195,28 +202,12 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
return proc->readAllStandardOutput();
}
ErrorCode ServerController::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
{
QString script_import = QString("docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
"easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
.arg(getContainerName(container)).arg(clientId);
QString script_sign = QString("docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
"easyrsa sign-req client %2\"")
.arg(getContainerName(container)).arg(clientId);
QStringList script {script_import, script_sign};
return runScript(container, sshParams(credentials), script.join("\n"));
}
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
{
QString caCert = ServerController::getTextFileFromContainer(container,
credentials, ServerController::caCertPath());
credentials, amnezia::protocols::openvpn::caCertPath);
QString taKey = ServerController::getTextFileFromContainer(container,
credentials, ServerController::taKeyPath());
credentials, amnezia::protocols::openvpn::taKeyPath);
if (!caCert.isEmpty() && !taKey.isEmpty()) {
return ErrorCode::NoError;
@@ -226,6 +217,68 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const
}
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath)
{
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
}
bool err = false;
QEventLoop wait;
QTimer timer;
timer.setSingleShot(true);
timer.start(3000);
QSharedPointer<SftpChannel> sftp = client->createSftpChannel();
sftp->initialize();
QObject::connect(sftp.data(), &SftpChannel::initialized, &wait, [&](){
timer.stop();
wait.quit();
});
QObject::connect(&timer, &QTimer::timeout, &wait, [&](){
err= true;
wait.quit();
});
wait.exec();
if (!sftp) {
qCritical() << "Failed to create SftpChannel, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
}
QTemporaryFile localFile;
localFile.open();
localFile.write(data);
localFile.close();
auto job = sftp->uploadFile(localFile.fileName(), remotePath, QSsh::SftpOverwriteMode::SftpOverwriteExisting);
QObject::connect(sftp.data(), &SftpChannel::finished, &wait, [&](QSsh::SftpJobId j, const QString &error){
if (job == j) {
qDebug() << "Sftp finished with status" << error;
wait.quit();
}
});
QObject::connect(sftp.data(), &SftpChannel::channelError, &wait, [&](const QString &reason){
qDebug() << "Sftp finished with error" << reason;
err= true;
wait.quit();
});
wait.exec();
if (err) {
return ErrorCode::SshSftpError;
}
else {
return ErrorCode::NoError;
}
}
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
{
switch (error) {
@@ -256,127 +309,282 @@ ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
SshConnectionParameters ServerController::sshParams(const ServerCredentials &credentials)
{
QSsh::SshConnectionParameters sshParams;
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
sshParams.privateKeyFile = credentials.password;
}
else {
sshParams.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
sshParams.password = credentials.password;
}
sshParams.host = credentials.hostName;
sshParams.userName = credentials.userName;
sshParams.password = credentials.password;
sshParams.timeout = 10;
sshParams.port = credentials.port;
sshParams.hostKeyCheckingMode = QSsh::SshHostKeyCheckingMode::SshHostKeyCheckingNone;
sshParams.options = SshIgnoreDefaultProxy;
return sshParams;
}
ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto)
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
{
QString scriptFileName;
DockerContainer container;
ErrorCode errorCode;
if (proto == Protocol::Any) {
removeServer(credentials, Protocol::OpenVpn);
removeServer(credentials, Protocol::ShadowSocks);
return ErrorCode::NoError;
}
else if (proto == Protocol::OpenVpn) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::OpenVpn;
}
else if (proto == Protocol::ShadowSocks) {
scriptFileName = ":/server_scripts/remove_container.sh";
container = DockerContainer::ShadowSocks;
}
else return ErrorCode::NotImplementedError;
QString scriptData;
QFile file(scriptFileName);
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError;
return runScript(container, sshParams(credentials), scriptData);
return runScript(sshParams(credentials),
amnezia::scriptData(SharedScriptType::remove_all_containers));
}
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
{
if (proto == Protocol::OpenVpn) {
return setupOpenVpnServer(credentials);
}
else if (proto == Protocol::ShadowSocks) {
return setupShadowSocksServer(credentials);
}
else if (proto == Protocol::Any) {
return ErrorCode::NotImplementedError;
// TODO: run concurently
// return setupOpenVpnServer(credentials);
//setupShadowSocksServer(credentials);
}
return ErrorCode::NotImplementedError;
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
genVarsForScript(credentials, container)));
}
ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credentials)
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
QString scriptData;
QString scriptFileName = ":/server_scripts/setup_openvpn_server.sh";
QFile file(scriptFileName);
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
qDebug().noquote() << "ServerController::setupContainer" << containerToString(container);
qDebug().noquote() << QJsonDocument(config).toJson();
ErrorCode e = ErrorCode::NoError;
scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError;
ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData);
e = installDockerWorker(credentials, container);
if (e) return e;
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
}
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
{
// Setup openvpn part
QString scriptData;
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
QFile file(scriptFileName);
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
scriptData = file.readAll();
if (scriptData.isEmpty()) return ErrorCode::InternalError;
ErrorCode e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), scriptData);
e = prepareHostWorker(credentials, container, config);
if (e) return e;
// Create ss config
QJsonObject ssConfig;
ssConfig.insert("server", "0.0.0.0");
ssConfig.insert("server_port", ssRemotePort());
ssConfig.insert("local_port", ssContainerPort());
ssConfig.insert("password", credentials.password);
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ssEncryption());
QString configData = QJsonDocument(ssConfig).toJson();
QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
removeContainer(credentials, container);
qDebug().noquote() << configData;
configData.replace("\"", "\\\"");
qDebug().noquote() << configData;
e = buildContainerWorker(credentials, container, config);
if (e) return e;
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
e = runContainerWorker(credentials, container, config);
if (e) return e;
// Start ss
QString script = QString("docker exec -i %1 sh -c \"ss-server -c %2 &\"").
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
e = configureContainerWorker(credentials, container, config);
if (e) return e;
return startupContainerWorker(credentials, container, config);
}
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
bool reinstallRequred = isReinstallContainerRequred(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred;
if (reinstallRequred) {
return setupContainer(credentials, container, newConfig);
}
else {
ErrorCode e = configureContainerWorker(credentials, container, newConfig);
if (e) return e;
return startupContainerWorker(credentials, container, newConfig);
}
}
bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
if (container == DockerContainer::OpenVpn) {
const QJsonObject &oldProtoConfig = oldConfig[config_key::openvpn].toObject();
const QJsonObject &newProtoConfig = newConfig[config_key::openvpn].toObject();
if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) !=
newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto))
return true;
if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) !=
newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort))
return true;
}
if (container == DockerContainer::OpenVpnOverCloak) {
const QJsonObject &oldProtoConfig = oldConfig[config_key::cloak].toObject();
const QJsonObject &newProtoConfig = newConfig[config_key::cloak].toObject();
if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) !=
newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort))
return true;
}
if (container == DockerContainer::OpenVpnOverShadowSocks) {
const QJsonObject &oldProtoConfig = oldConfig[config_key::shadowsocks].toObject();
const QJsonObject &newProtoConfig = newConfig[config_key::shadowsocks].toObject();
if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) !=
newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
return true;
}
return false;
}
ErrorCode ServerController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
if (data.contains("Automatically restart Docker daemon?")) {
proc->write("yes\n");
}
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
stdOut += data + "\n";
};
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
genVarsForScript(credentials, container)),
cbReadStdOut, cbReadStdErr);
}
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
// create folder on host
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
genVarsForScript(credentials, container)));
}
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
if (e) return e;
// QString stdOut;
// auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
genVarsForScript(credentials, container, config)));
}
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
ErrorCode e = runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError;
if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError;
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
return e;
}
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
genVarsForScript(credentials, container, config)));
}
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
ErrorCode e = uploadTextFileToContainer(container, credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::container_startup, container),
genVarsForScript(credentials, container, config)),
"/opt/amnezia/start.sh");
if (e) return e;
return runScript(sshParams(credentials),
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"",
genVarsForScript(credentials, container, config)));
}
ServerController::Vars ServerController:: genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
{
const QJsonObject &openvpnConfig = config.value(config_key::openvpn).toObject();
const QJsonObject &cloakConfig = config.value(config_key::cloak).toObject();
const QJsonObject &ssConfig = config.value(config_key::shadowsocks).toObject();
//
Vars vars;
vars.append({{"$REMOTE_HOST", credentials.hostName}});
// OpenVPN vars
vars.append({{"$VPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(amnezia::protocols::vpnDefaultSubnetAddress) }});
vars.append({{"$VPN_SUBNET_MASK_VAL", openvpnConfig.value(config_key::subnet_mask_val).toString(amnezia::protocols::vpnDefaultSubnetMaskVal) }});
vars.append({{"$VPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(amnezia::protocols::vpnDefaultSubnetMask) }});
vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(amnezia::protocols::openvpn::defaultPort) }});
vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(amnezia::protocols::openvpn::defaultTransportProto) }});
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(amnezia::protocols::openvpn::defaultNcpDisable);
vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }});
vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(amnezia::protocols::openvpn::defaultCipher) }});
vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(amnezia::protocols::openvpn::defaultHash) }});
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(amnezia::protocols::openvpn::defaultTlsAuth);
vars.append({{"$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" }});
if (!isTlsAuth) {
// erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig
vars.append({{"$OPENVPN_TA_KEY", "" }});
}
// ShadowSocks vars
vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(amnezia::protocols::shadowsocks::defaultPort) }});
vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(amnezia::protocols::shadowsocks::defaultLocalProxyPort) }});
vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(amnezia::protocols::shadowsocks::defaultCipher) }});
vars.append({{"$CONTAINER_NAME", amnezia::containerToString(container)}});
vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::containerToString(container)}});
// Cloak vars
vars.append({{"$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) }});
vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }});
QString serverIp = Utils::getIPAddress(credentials.hostName);
if (!serverIp.isEmpty()) {
vars.append({{"$SERVER_IP_ADDRESS", serverIp}});
}
else {
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
}
return vars;
}
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
stdOut += data + "\n";
};
auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
stdOut += data + "\n";
};
ErrorCode e = runScript(sshParams(credentials),
amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
if (errorCode) *errorCode = e;
return stdOut;
}
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
{
SshConnection *client = acquireConnection(sshParams);
if (!client) return nullptr;
QEventLoop waitssh;
QObject::connect(client, &SshConnection::connected, &waitssh, [&]() {
@@ -427,11 +635,26 @@ SshConnection *ServerController::connectToHost(const SshConnectionParameters &ss
return client;
}
void ServerController::disconnectFromHost(const ServerCredentials &credentials)
{
SshConnection *client = acquireConnection(sshParams(credentials));
if (client) client->disconnectFromHost();
}
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{
QFile file(":/server_scripts/setup_firewall.sh");
file.open(QIODevice::ReadOnly);
QString script = file.readAll();
return runScript(DockerContainer::OpenVpn, sshParams(credentials), script);
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)));
}
QString ServerController::replaceVars(const QString &script, const Vars &vars)
{
QString s = script;
for (const QPair<QString, QString> &var : vars) {
//qDebug() << "Replacing" << var.first << var.second;
s.replace(var.first, var.second);
}
//qDebug().noquote() << script;
return s;
}

View File

@@ -1,10 +1,13 @@
#ifndef SERVERCONTROLLER_H
#define SERVERCONTROLLER_H
#include <QJsonObject>
#include <QObject>
#include "sshconnection.h"
#include "sshremoteprocess.h"
#include "defs.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
@@ -12,46 +15,54 @@ class ServerController : public QObject
{
Q_OBJECT
public:
typedef QList<QPair<QString, QString>> Vars;
static ErrorCode fromSshConnectionErrorCode(QSsh::SshError error);
// QSsh exitCode and exitStatus are different things
static ErrorCode fromSshProcessExitStatus(int exitStatus);
static QString caCertPath() { return "/opt/amneziavpn_data/pki/ca.crt"; }
static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; }
static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; }
static QString getContainerName(amnezia::DockerContainer container);
static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
static void disconnectFromHost(const ServerCredentials &credentials);
static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto);
static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto);
static ErrorCode removeAllContainers(const ServerCredentials &credentials);
static ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
static ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
static ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, const QJsonObject &newConfig = QJsonObject());
static bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
static ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath);
static ErrorCode uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, QString &file, const QString &path);
const ServerCredentials &credentials, const QString &file, const QString &path);
static QString getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
static ErrorCode signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId);
static int ssRemotePort() { return 6789; } // TODO move to ShadowSocksDefs.h
static int ssContainerPort() { return 8585; } // TODO move to ShadowSocksDefs.h
static QString ssEncryption() { return "chacha20-ietf-poly1305"; } // TODO move to ShadowSocksDefs.h
static ErrorCode setupServerFirewall(const ServerCredentials &credentials);
static QString replaceVars(const QString &script, const Vars &vars);
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdOut = nullptr,
const std::function<void(const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr = nullptr);
static Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject());
static QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
private:
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
static ErrorCode runScript(DockerContainer container,
const QSsh::SshConnectionParameters &sshParams, QString script);
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);
static ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
static ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
static ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
static ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
static ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
static ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
};

View File

@@ -4,6 +4,6 @@
#define APPLICATION_NAME "AmneziaVPN"
#define SERVICE_NAME "AmneziaVPN-service"
#define ORGANIZATION_NAME "AmneziaVPN.ORG"
#define APP_VERSION "1.0.0.0"
#define APP_VERSION "1.6.1.0"
#endif // DEFINES_H

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 136 KiB

BIN
client/images/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 55 KiB

BIN
client/images/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

BIN
client/images/reload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
client/images/uncheck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

View File

@@ -1,62 +0,0 @@
#include <QDebug>
#include <QtNetwork>
#include "localclient.h"
LocalClient::LocalClient(QObject *parent) : QObject(parent),
m_socket(new QLocalSocket(this))
{
m_in.setDevice(m_socket);
m_in.setVersion(QDataStream::Qt_5_10);
connect(m_socket, &QLocalSocket::readyRead, this, &LocalClient::onReadyRead);
connect(m_socket, &QLocalSocket::connected, this, &LocalClient::onConnected);
connect(m_socket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &LocalClient::displayError);
}
void LocalClient::connectToServer(const QString& name)
{
m_blockSize = 0;
m_socket->abort();
m_socket->connectToServer(name);
}
QString LocalClient::serverName() const
{
return m_socket->serverName();
}
void LocalClient::onConnected()
{
emit connected();
}
bool LocalClient::connectedState() const
{
return (m_socket->state() == QLocalSocket::ConnectedState);
}
quint64 LocalClient::write(const QByteArray& data)
{
return m_socket->write(data);
}
void LocalClient::onReadyRead()
{
if (m_socket->canReadLine()) {
char buf[1024];
qint64 lineLength = m_socket->readLine(buf, sizeof(buf));
if (lineLength != -1) {
QString line = buf;
line = line.simplified();
qDebug().noquote() << QString("Read line: '%1'").arg(line);
emit lineAvailable(line);
}
}
}
void LocalClient::displayError(QLocalSocket::LocalSocketError socketError)
{
Q_UNUSED(socketError)
qDebug().noquote() << QString("The following error occurred: %1.").arg(m_socket->errorString());
}

View File

@@ -1,34 +0,0 @@
#ifndef LOCALCLIENT_H
#define LOCALCLIENT_H
#include <QDataStream>
#include <QLocalSocket>
class LocalClient : public QObject
{
Q_OBJECT
public:
explicit LocalClient(QObject *parent = nullptr);
QString serverName() const;
bool connectedState() const;
quint64 write(const QByteArray& data);
void connectToServer(const QString& name);
signals:
void connected();
void lineAvailable(const QString& line);
private slots:
void displayError(QLocalSocket::LocalSocketError socketError);
void onConnected();
void onReadyRead();
private:
QLocalSocket* m_socket;
QDataStream m_in;
quint32 m_blockSize;
};
#endif // LOCALCLIENT_H

View File

@@ -3,13 +3,19 @@
#include <QCommandLineParser>
#include <QMessageBox>
#include <QTranslator>
#include <QTimer>
#include <QLoggingCategory>
#include "debug.h"
#include "defines.h"
#include "runguard.h"
#include "singleapplication.h"
#include "ui/mainwindow.h"
#ifdef Q_OS_WIN
#include "Windows.h"
#endif
static void loadTranslator()
{
QTranslator* translator = new QTranslator;
@@ -20,17 +26,27 @@ static void loadTranslator()
int main(int argc, char *argv[])
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
RunGuard::instance(APPLICATION_NAME).activate();
QApplication app(argc, argv);
loadTranslator();
#ifdef Q_OS_WIN
AllowSetForegroundWindow(ASFW_ANY);
#endif
if (!RunGuard::instance().tryToRun()) {
qDebug() << "Tried to run second instance. Exiting...";
QMessageBox::information(NULL, QObject::tr("Notification"), QObject::tr("AmneziaVPN is already running."));
return 0;
SingleApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification);
if (!app.isPrimary()) {
QTimer::singleShot(1000, &app, [&](){
app.quit();
});
return app.exec();
}
#ifdef Q_OS_WIN
AllowSetForegroundWindow(0);
#endif
loadTranslator();
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf");
@@ -52,18 +68,34 @@ int main(int argc, char *argv[])
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"};
parser.addOption(c_autostart);
parser.process(app);
if (!Debug::init()) {
qWarning() << "Initialization of debug subsystem failed";
}
QFont f("Lato Regular", 10);
f.setStyleStrategy(QFont::PreferAntialias);
app.setFont(f);
app.setQuitOnLastWindowClosed(false);
MainWindow mainWindow;
mainWindow.show();
#ifdef Q_OS_WIN
if (parser.isSet("a")) mainWindow.showOnStartup();
else mainWindow.show();
#else
mainWindow.showOnStartup();
#endif
if (app.isPrimary()) {
QObject::connect(&app, &SingleApplication::instanceStarted, &mainWindow, [&](){
qDebug() << "Secondary instance started, showing this window instead";
mainWindow.show();
mainWindow.showNormal();
mainWindow.raise();
});
}
return app.exec();
}

View File

@@ -22,7 +22,7 @@ bool ManagementServer::isOpen() const
void ManagementServer::stop()
{
m_tcpServer->close();
if (m_tcpServer) m_tcpServer->close();
}
void ManagementServer::onAcceptError(QAbstractSocket::SocketError socketError)
@@ -37,7 +37,9 @@ qint64 ManagementServer::writeCommand(const QString& message)
}
const QString command = message + "\n";
return m_socket->write(command.toStdString().c_str());
qint64 bytesWritten = m_socket->write(command.toStdString().c_str());
m_socket->flush();
return bytesWritten;
}
void ManagementServer::onNewConnection()
@@ -45,7 +47,7 @@ void ManagementServer::onNewConnection()
qDebug() << "New incoming connection";
m_socket = QPointer<QTcpSocket>(m_tcpServer->nextPendingConnection());
m_tcpServer->close();
if (m_tcpServer) m_tcpServer->close();
QObject::connect(m_socket.data(), SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
QObject::connect(m_socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
@@ -54,8 +56,7 @@ void ManagementServer::onNewConnection()
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
{
Q_UNUSED(socketError);
Q_UNUSED(socketError)
qDebug().noquote() << QString("Mananement server error: %1").arg(m_socket->errorString());
}
@@ -79,9 +80,7 @@ void ManagementServer::onReadyRead()
bool ManagementServer::start(const QString& host, unsigned int port)
{
if (m_tcpServer) {
m_tcpServer->deleteLater();
}
if (m_tcpServer) m_tcpServer->close();
m_tcpServer = QSharedPointer<QTcpServer>(new QTcpServer(this), [](QTcpServer *s){
if (s) s->deleteLater();

View File

@@ -1,106 +0,0 @@
#include "message.h"
Message::Message(State state, const QStringList& args) :
m_valid(true),
m_state(state),
m_args(args)
{
}
bool Message::isValid() const
{
return m_valid;
}
QString Message::textState() const
{
switch (m_state) {
case State::Unknown: return "Unknown";
case State::Initialize: return "Initialize";
case State::StartRequest: return "StartRequest";
case State::Started: return "Started";
case State::FinishRequest: return "FinishRequest";
case State::Finished: return "Finished";
case State::RoutesAddRequest: return "RoutesAddRequest";
case State::RouteDeleteRequest: return "RouteDeleteRequest";
case State::ClearSavedRoutesRequest: return "ClearSavedRoutesRequest";
case State::FlushDnsRequest: return "FlushDnsRequest";
case State::InstallDriverRequest: return "InstallDriverRequest";
default:
;
}
return QString();
}
QString Message::rawData() const
{
return m_rawData;
}
Message::State Message::state() const
{
return m_state;
}
QString Message::toString() const
{
if (!isValid()) {
return QString();
}
return QString("%1%2%3")
.arg(textState())
.arg(m_dataSeparator)
.arg(argsToString());
}
QString Message::argAtIndex(int index) const
{
if ((index + 1) > args().size()) {
return QString();
}
return args().at(index);
}
QStringList Message::args() const
{
return m_args;
}
QString Message::argsToString() const
{
return m_args.join(m_argSeparator);
}
Message::Message(const QString& data)
{
m_rawData = data;
m_valid = false;
if (data.isEmpty()) {
return;
}
QStringList dataList = data.split(m_dataSeparator);
if ((dataList.size() != 2)) {
return;
}
bool stateFound = false;
for (int i = static_cast<int>(State::Unknown); i <= static_cast<int>(State::InstallDriverRequest); i++ ) {
m_state = static_cast<State>(i);
if (textState() == dataList.at(0)) {
stateFound = true;
break;
}
}
if (!stateFound) {
return;
}
m_args = dataList.at(1).split(m_argSeparator);
m_valid = true;
}

View File

@@ -1,34 +0,0 @@
#ifndef MESSAGE_H
#define MESSAGE_H
#include <QStringList>
class Message {
public:
enum class State {Unknown, Initialize, StartRequest, Started, FinishRequest, Finished,
RoutesAddRequest, RouteDeleteRequest, ClearSavedRoutesRequest, FlushDnsRequest, InstallDriverRequest};
Message(State state, const QStringList& args);
Message(const QString& data);
QString argAtIndex(int index) const;
QString argsToString() const;
QString toString() const;
QStringList args() const;
State state() const;
bool isValid() const;
QString rawData() const;
protected:
QString textState() const;
const QString m_argSeparator = ",";
const QString m_dataSeparator = "|";
bool m_valid;
State m_state;
QStringList m_args;
QString m_rawData;
};
#endif // MESSAGE_H

View File

@@ -3,11 +3,11 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
IDI_ICON1 ICON "../images/app.ico"
#define VER_FILEVERSION 1,1,1,1
#define VER_FILEVERSION_STR "1.1.1.1\0"
#define VER_FILEVERSION 1,6,1,0
#define VER_FILEVERSION_STR "1.6.1.0\0"
#define VER_PRODUCTVERSION 1,1,1,1
#define VER_PRODUCTVERSION_STR "1.1.1.1\0"
#define VER_PRODUCTVERSION 1,6,1,0
#define VER_PRODUCTVERSION_STR "1.6.1.0\0"
#define VER_COMPANYNAME_STR "AmneziaVPN"
#define VER_FILEDESCRIPTION_STR "AmneziaVPN"

View File

@@ -0,0 +1,109 @@
#include "openvpnovercloakprotocol.h"
#include "core/servercontroller.h"
#include "utils.h"
#include "protocols/protocols_defs.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
OpenVpnOverCloakProtocol::OpenVpnOverCloakProtocol(const QJsonObject &configuration, QObject *parent):
OpenVpnProtocol(configuration, parent)
{
readCloakConfiguration(configuration);
}
OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol()
{
qDebug() << "OpenVpnOverCloakProtocol::~OpenVpnOverCloakProtocol";
OpenVpnOverCloakProtocol::stop();
QThread::msleep(200);
m_ckProcess.close();
}
ErrorCode OpenVpnOverCloakProtocol::start()
{
if (Utils::processIsRunning(Utils::executable("ck-client", false))) {
Utils::killProcessByName(Utils::executable("ck-client", false));
}
#ifdef QT_DEBUG
m_cloakCfgFile.setAutoRemove(false);
#endif
m_cloakCfgFile.open();
m_cloakCfgFile.write(QJsonDocument(m_cloakConfig).toJson());
m_cloakCfgFile.close();
QStringList args = QStringList() << "-c" << m_cloakCfgFile.fileName()
<< "-s" << m_cloakConfig.value(config_key::remote).toString()
<< "-p" << m_cloakConfig.value(config_key::port).toString(amnezia::protocols::cloak::defaultPort)
<< "-l" << amnezia::protocols::openvpn::defaultPort;
if (m_cloakConfig.value(config_key::transport_proto).toString() == protocols::UDP) {
args << "-u";
}
qDebug().noquote() << "OpenVpnOverCloakProtocol::start()"
<< cloakExecPath() << args.join(" ");
m_ckProcess.setProcessChannelMode(QProcess::MergedChannels);
m_ckProcess.setProgram(cloakExecPath());
m_ckProcess.setArguments(args);
connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this](){
qDebug().noquote() << "ck-client:" << m_ckProcess.readAllStandardOutput();
});
connect(&m_ckProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
if (exitStatus != QProcess::NormalExit){
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
stop();
}
if (exitCode !=0 ){
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
});
m_ckProcess.start();
m_ckProcess.waitForStarted();
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
return OpenVpnProtocol::start();
}
else return ErrorCode::CloakExecutableMissing;
}
void OpenVpnOverCloakProtocol::stop()
{
OpenVpnProtocol::stop();
qDebug() << "OpenVpnOverCloakProtocol::stop()";
#ifdef Q_OS_WIN
Utils::signalCtrl(m_ckProcess.processId(), CTRL_C_EVENT);
#endif
m_ckProcess.terminate();
}
QString OpenVpnOverCloakProtocol::cloakExecPath()
{
#ifdef Q_OS_WIN
return Utils::executable(QString("cloak/ck-client"), true);
#else
return Utils::executable(QString("/ck-client"), true);
#endif
}
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
{
m_cloakConfig = configuration.value(config::key_cloak_config_data).toObject();
}

View File

@@ -0,0 +1,30 @@
#ifndef OPENVPNOVERCLOAKPROTOCOL_H
#define OPENVPNOVERCLOAKPROTOCOL_H
#include "openvpnprotocol.h"
#include "QProcess"
class OpenVpnOverCloakProtocol : public OpenVpnProtocol
{
public:
OpenVpnOverCloakProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~OpenVpnOverCloakProtocol() override;
ErrorCode start() override;
void stop() override;
protected:
void readCloakConfiguration(const QJsonObject &configuration);
protected:
QJsonObject m_cloakConfig;
private:
static QString cloakExecPath();
private:
QProcess m_ckProcess;
QTemporaryFile m_cloakCfgFile;
};
#endif // OPENVPNOVERCLOAKPROTOCOL_H

View File

@@ -4,84 +4,88 @@
#include <QRegularExpression>
#include <QTcpSocket>
#include "communicator.h"
#include "debug.h"
#include "openvpnprotocol.h"
#include "utils.h"
OpenVpnProtocol::OpenVpnProtocol(const QString& args, QObject* parent) :
VpnProtocol(args, parent),
m_requestFromUserToStop(false)
OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
setConfigFile(args);
connect(m_communicator, &Communicator::messageReceived, this, &OpenVpnProtocol::onMessageReceived);
readOpenVpnConfiguration(configuration);
connect(&m_managementServer, &ManagementServer::readyRead, this, &OpenVpnProtocol::onReadyReadDataFromManagementServer);
}
OpenVpnProtocol::~OpenVpnProtocol()
{
qDebug() << "OpenVpnProtocol::stop()";
qDebug() << "OpenVpnProtocol::~OpenVpnProtocol()";
OpenVpnProtocol::stop();
}
void OpenVpnProtocol::onMessageReceived(const Message& message)
{
if (!message.isValid()) {
qWarning().noquote() << QString("Message received: '%1', but it is not valid").arg(message.toString());
return;
}
switch (message.state()) {
case Message::State::Started:
qDebug() << "OpenVPN process started";
break;
case Message::State::Finished:
qDebug().noquote() << QString("OpenVPN process finished with status %1").arg(message.argAtIndex(1));
onOpenVpnProcessFinished(message.argAtIndex(1).toInt());
break;
default:
qDebug().noquote() << QString("Message received: '%1'").arg(message.toString());
;
}
QThread::msleep(200);
}
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
// TODO: need refactoring
// sendTermSignal() will evet return true while server connected
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == VpnProtocol::ConnectionState::Preparing) ||
(m_connectionState == VpnProtocol::ConnectionState::Connecting) ||
(m_connectionState == VpnProtocol::ConnectionState::Connected) ||
(m_connectionState == VpnProtocol::ConnectionState::TunnelReconnecting)) {
(m_connectionState == VpnProtocol::ConnectionState::Reconnecting)) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
m_managementServer.stop();
qApp->processEvents();
setConnectionState(VpnProtocol::ConnectionState::Disconnecting);
}
}
void OpenVpnProtocol::killOpenVpnProcess()
ErrorCode OpenVpnProtocol::checkAndSetupTapDriver()
{
// send command to kill openvpn process (if any).
if (!IpcClient::Interface()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
QRemoteObjectPendingReply<QStringList> resultCheck = IpcClient::Interface()->getTapList();
resultCheck.waitForFinished();
if (resultCheck.returnValue().isEmpty()){
QRemoteObjectPendingReply<bool> resultInstall = IpcClient::Interface()->checkAndInstallDriver();
resultInstall.waitForFinished();
if (!resultInstall.returnValue()) return ErrorCode::OpenVpnUnknownError;
}
return ErrorCode::NoError;
}
bool OpenVpnProtocol::setConfigFile(const QString& configFileNamePath)
void OpenVpnProtocol::killOpenVpnProcess()
{
m_configFileName = configFileNamePath;
QFileInfo file(m_configFileName);
if (file.fileName().isEmpty()) {
m_configFileName = Utils::defaultVpnConfigFileName();
if (m_openVpnProcess){
m_openVpnProcess->close();
}
}
if (m_configFileName.isEmpty()) {
return false;
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{
if (configuration.contains(config::key_openvpn_config_data)) {
m_configFile.open();
m_configFile.write(configuration.value(config::key_openvpn_config_data).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName;
}
else if (configuration.contains(config::key_openvpn_config_path)) {
m_configFileName = configuration.value(config::key_openvpn_config_path).toString();
QFileInfo file(m_configFileName);
qDebug().noquote() << QString("Set config file: '%1'").arg(configPath());
if (file.fileName().isEmpty()) {
m_configFileName = Utils::defaultVpnConfigFileName();
}
return false;
qDebug().noquote() << QString("Set config file: '%1'").arg(configPath());
}
}
bool OpenVpnProtocol::openVpnProcessIsRunning() const
@@ -99,17 +103,18 @@ QString OpenVpnProtocol::configPath() const
return m_configFileName;
}
void OpenVpnProtocol::writeCommand(const QString& command)
void OpenVpnProtocol::sendManagementCommand(const QString& command)
{
QIODevice *device = dynamic_cast<QIODevice*>(m_managementServer.socket().data());
if (device) {
QTextStream stream(device);
stream << command << endl;
stream << command << Qt::endl;
}
}
void OpenVpnProtocol::updateRouteGateway(QString line)
{
// TODO: fix for macos
line = line.split("ROUTE_GATEWAY", QString::SkipEmptyParts).at(1);
if (!line.contains("/")) return;
m_routeGateway = line.split("/", QString::SkipEmptyParts).first();
@@ -128,17 +133,9 @@ QString OpenVpnProtocol::openVpnExecPath() const
ErrorCode OpenVpnProtocol::start()
{
qDebug() << "Start OpenVPN connection";
m_requestFromUserToStop = false;
m_openVpnStateSigTermHandlerTimer.stop();
//qDebug() << "Start OpenVPN connection";
OpenVpnProtocol::stop();
if (communicator() && !communicator()->isConnected()) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return lastError();
}
if (!QFileInfo::exists(openVpnExecPath())) {
setLastError(ErrorCode::OpenVpnExecutableMissing);
return lastError();
@@ -152,39 +149,55 @@ ErrorCode OpenVpnProtocol::start()
QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log";
Utils::createEmptyFile(vpnLogFileNamePath);
QStringList args({openVpnExecPath(),
"--config" , configPath(),
"--management", m_managementHost, QString::number(m_managementPort),
"--management-client",
"--log-append", vpnLogFileNamePath
});
if (!m_managementServer.start(m_managementHost, m_managementPort)) {
setLastError(ErrorCode::OpenVpnManagementServerError);
return lastError();
}
setConnectionState(ConnectionState::Connecting);
m_communicator->sendMessage(Message(Message::State::StartRequest, args));
startTimeoutTimer();
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
if (!m_openVpnProcess) {
//qWarning() << "IpcProcess replica is not created!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->waitForSource(1000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(openVpnExecPath());
QStringList arguments({"--config" , configPath(),
"--management", m_managementHost, QString::number(m_managementPort),
"--management-client",
"--log-append", vpnLogFileNamePath
});
m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred, [&](QProcess::ProcessError error) {
qDebug() << "IpcProcessInterfaceReplica errorOccurred" << error;
});
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged, [&](QProcess::ProcessState newState) {
qDebug() << "IpcProcessInterfaceReplica stateChanged" << newState;
});
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this, [&]() {
setConnectionState(ConnectionState::Disconnected);
});
m_openVpnProcess->start();
//startTimeoutTimer();
return ErrorCode::NoError;
}
void OpenVpnProtocol::openVpnStateSigTermHandlerTimerEvent()
{
bool processStatus = openVpnProcessIsRunning();
if (processStatus) {
killOpenVpnProcess();
}
onOpenVpnProcessFinished(0);
}
void OpenVpnProtocol::openVpnStateSigTermHandler()
{
m_openVpnStateSigTermHandlerTimer.start(5000);
}
bool OpenVpnProtocol::sendTermSignal()
{
return m_managementServer.writeCommand("signal SIGTERM");
@@ -220,14 +233,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
if (line.contains("CONNECTED,SUCCESS")) {
sendByteCount();
stopTimeoutTimer();
updateVpnGateway();
setConnectionState(VpnProtocol::ConnectionState::Connected);
continue;
} else if (line.contains("EXITING,SIGTER")) {
openVpnStateSigTermHandler();
//openVpnStateSigTermHandler();
setConnectionState(VpnProtocol::ConnectionState::Disconnecting);
continue;
} else if (line.contains("RECONNECTING")) {
setConnectionState(VpnProtocol::ConnectionState::TunnelReconnecting);
setConnectionState(VpnProtocol::ConnectionState::Reconnecting);
continue;
}
}
@@ -236,6 +249,10 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
updateRouteGateway(line);
}
if (line.contains("PUSH: Received control message")) {
updateVpnGateway(line);
}
if (line.contains("FATAL")) {
if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) {
emit protocolError(ErrorCode::OpenVpnAdaptersInUseError);
@@ -262,59 +279,61 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
}
}
void OpenVpnProtocol::onOpenVpnProcessFinished(int exitCode)
void OpenVpnProtocol::updateVpnGateway(const QString &line)
{
m_openVpnStateSigTermHandlerTimer.stop();
if (m_connectionState == VpnProtocol::ConnectionState::Disconnected) {
qDebug() << "Already in disconnected state";
return;
}
// line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
qDebug().noquote() << QString("Process finished with code: %1").arg(exitCode);
QStringList params = line.split(",");
for (const QString &l : params) {
if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) {
m_vpnAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2);
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
}
void OpenVpnProtocol::updateVpnGateway()
{
QProcess ipconfig;
ipconfig.start("ipconfig", QStringList() << "/all");
ipconfig.waitForStarted();
ipconfig.waitForFinished();
QString d = ipconfig.readAll();
d.replace("\r", "");
//qDebug().noquote() << d;
QStringList adapters = d.split(":\n");
bool isTapV9Present = false;
QString tapV9;
for (int i = 0; i < adapters.size(); ++i) {
if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
isTapV9Present = true;
tapV9 = adapters.at(i);
break;
qDebug() << QString("Set vpn address %1, gw %2").arg(m_vpnAddress).arg(vpnGateway());
}
}
}
if (!isTapV9Present) {
m_vpnGateway = "";
}
QStringList lines = tapV9.split("\n");
for (int i = 0; i < lines.size(); ++i) {
if (!lines.at(i).contains("DHCP")) continue;
// QProcess ipconfig;
// ipconfig.start("ipconfig", QStringList() << "/all");
// ipconfig.waitForStarted();
// ipconfig.waitForFinished();
QRegularExpression re("(: )([\\d\\.]+)($)");
QRegularExpressionMatch match = re.match(lines.at(i));
// QString d = ipconfig.readAll();
// d.replace("\r", "");
// //qDebug().noquote() << d;
if (match.hasMatch()) {
qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
m_vpnGateway = match.captured(2);
return;
}
else continue;
}
// QStringList adapters = d.split(":\n");
m_vpnGateway = "";
// bool isTapV9Present = false;
// QString tapV9;
// for (int i = 0; i < adapters.size(); ++i) {
// if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
// isTapV9Present = true;
// tapV9 = adapters.at(i);
// break;
// }
// }
// if (!isTapV9Present) {
// m_vpnGateway = "";
// }
// QStringList lines = tapV9.split("\n");
// for (int i = 0; i < lines.size(); ++i) {
// if (!lines.at(i).contains("DHCP")) continue;
// QRegularExpression re("(: )([\\d\\.]+)($)");
// QRegularExpressionMatch match = re.match(lines.at(i));
// if (match.hasMatch()) {
// qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
// m_vpnGateway = match.captured(2);
// return;
// }
// else continue;
// }
// m_vpnGateway = "";
}

View File

@@ -6,51 +6,50 @@
#include <QTimer>
#include "managementserver.h"
#include "message.h"
#include "vpnprotocol.h"
#include "core/ipcclient.h"
class OpenVpnProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
explicit OpenVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~OpenVpnProtocol() override;
ErrorCode start() override;
void stop() override;
ErrorCode checkAndSetupTapDriver();
protected slots:
void onMessageReceived(const Message& message);
void onOpenVpnProcessFinished(int exitCode);
void onReadyReadDataFromManagementServer();
protected:
private:
QString configPath() const;
QString openVpnExecPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();
bool setConfigFile(const QString& configFileNamePath);
void readOpenVpnConfiguration(const QJsonObject &configuration);
void disconnectFromManagementServer();
void killOpenVpnProcess();
void openVpnStateSigTermHandler();
void openVpnStateSigTermHandlerTimerEvent();
void sendByteCount();
void sendInitialData();
void writeCommand(const QString& command);
void sendManagementCommand(const QString& command);
const QString m_managementHost = "127.0.0.1";
const unsigned int m_managementPort = 57775;
ManagementServer m_managementServer;
QString m_configFileName;
QTimer m_openVpnStateSigTermHandlerTimer;
bool m_requestFromUserToStop;
QTemporaryFile m_configFile;
private:
void updateRouteGateway(QString line);
void updateVpnGateway();
void updateVpnGateway(const QString &line);
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
};
#endif // OPENVPNPROTOCOL_H

View File

@@ -0,0 +1,80 @@
#include "protocols_defs.h"
QDebug operator<<(QDebug debug, const amnezia::Protocol &p)
{
QDebugStateSaver saver(debug);
debug.nospace() << protoToString(p);
return debug;
}
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
{
QDebugStateSaver saver(debug);
debug.nospace() << containerToString(c);
return debug;
}
amnezia::Protocol amnezia::protoFromString(QString proto){
if (proto == config_key::openvpn) return Protocol::OpenVpn;
if (proto == config_key::cloak) return Protocol::Cloak;
if (proto == config_key::shadowsocks) return Protocol::ShadowSocks;
if (proto == config_key::wireguard) return Protocol::WireGuard;
return Protocol::Any;
}
QString amnezia::protoToString(amnezia::Protocol proto){
switch (proto) {
case(Protocol::OpenVpn): return config_key::openvpn;
case(Protocol::Cloak): return config_key::cloak;
case(Protocol::ShadowSocks): return config_key::shadowsocks;
case(Protocol::WireGuard): return config_key::wireguard;
default: return "";
}
}
amnezia::DockerContainer amnezia::containerFromString(const QString &container){
if (container == config_key::amnezia_openvpn) return DockerContainer::OpenVpn;
if (container == config_key::amnezia_openvpn_cloak) return DockerContainer::OpenVpnOverCloak;
if (container == config_key::amnezia_shadowsocks) return DockerContainer::OpenVpnOverShadowSocks;
if (container == config_key::amnezia_wireguard) return DockerContainer::WireGuard;
return DockerContainer::None;
}
QString amnezia::containerToString(amnezia::DockerContainer container){
switch (container) {
case(DockerContainer::OpenVpn): return config_key::amnezia_openvpn;
case(DockerContainer::OpenVpnOverCloak): return config_key::amnezia_openvpn_cloak;
case(DockerContainer::OpenVpnOverShadowSocks): return config_key::amnezia_shadowsocks;
case(DockerContainer::WireGuard): return config_key::amnezia_wireguard;
default: return "none";
}
}
QVector<amnezia::Protocol> amnezia::allProtocols()
{
return QVector<amnezia::Protocol> {
Protocol::OpenVpn,
Protocol::ShadowSocks,
Protocol::Cloak,
Protocol::WireGuard
};
}
QVector<amnezia::Protocol> amnezia::protocolsForContainer(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn:
return { Protocol::OpenVpn };
case DockerContainer::OpenVpnOverShadowSocks:
return { Protocol::OpenVpn, Protocol::ShadowSocks };
case DockerContainer::OpenVpnOverCloak:
return { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak };
default:
return {};
}
}

View File

@@ -0,0 +1,134 @@
#ifndef PROTOCOLS_DEFS_H
#define PROTOCOLS_DEFS_H
#include <QObject>
#include <QDebug>
namespace amnezia {
namespace config_key {
// Json config strings
constexpr char hostName[] = "hostName";
constexpr char userName[] = "userName";
constexpr char password[] = "password";
constexpr char port[] = "port";
constexpr char local_port[] = "local_port";
constexpr char description[] = "description";
constexpr char containers[] = "containers";
constexpr char container[] = "container";
constexpr char defaultContainer[] = "defaultContainer";
constexpr char protocols[] = "protocols";
//constexpr char protocol[] = "protocol";
constexpr char remote[] = "remote";
constexpr char transport_proto[] = "transport_proto";
constexpr char cipher[] = "cipher";
constexpr char hash[] = "hash";
constexpr char ncp_disable[] = "ncp_disable";
constexpr char tls_auth[] = "tls_auth";
constexpr char site[] = "site";
constexpr char block_outside_dns[] = "block_outside_dns";
constexpr char subnet_address[] = "subnet_address";
constexpr char subnet_mask[] = "subnet_mask";
constexpr char subnet_mask_val[] = "subnet_mask_val";
// proto config keys
constexpr char last_config[] = "last_config";
constexpr char openvpn[] = "openvpn";
constexpr char shadowsocks[] = "shadowsocks";
constexpr char cloak[] = "cloak";
constexpr char wireguard[] = "wireguard";
// containers config keys
constexpr char amnezia_openvpn[] = "amnezia-openvpn";
constexpr char amnezia_shadowsocks[] = "amnezia-shadowsocks";
constexpr char amnezia_openvpn_cloak[] = "amnezia-openvpn-cloak";
constexpr char amnezia_wireguard[] = "amnezia-wireguard";
}
namespace protocols {
constexpr char vpnDefaultSubnetAddress[] = "10.8.0.0";
constexpr char vpnDefaultSubnetMask[] = "255.255.255.0";
constexpr char vpnDefaultSubnetMaskVal[] = "24";
constexpr char UDP[] = "udp"; // case sens
constexpr char TCP[] = "tcp";
namespace openvpn {
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued";
constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key";
constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients";
constexpr char defaultPort[] = "1194";
constexpr char defaultTransportProto[] = "udp";
constexpr char defaultCipher[] = "AES-256-GCM";
constexpr char defaultHash[] = "SHA512";
constexpr bool defaultBlockOutsideDns = true;
constexpr bool defaultNcpDisable = false;
constexpr bool defaultTlsAuth = true;
constexpr char ncpDisableString[] = "ncp-disable";
constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0";
}
namespace shadowsocks {
constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key";
constexpr char defaultPort[] = "6789";
constexpr char defaultLocalProxyPort[] = "8585";
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
}
namespace cloak {
constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key";
constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key";
constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key";
constexpr char defaultPort[] = "443";
constexpr char defaultRedirSite[] = "mail.ru";
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
}
} // namespace protocols
enum class Protocol {
Any,
OpenVpn,
ShadowSocks,
Cloak,
WireGuard
};
QVector<Protocol> allProtocols();
Protocol protoFromString(QString proto);
QString protoToString(Protocol proto);
enum class DockerContainer {
None,
OpenVpn,
OpenVpnOverShadowSocks,
OpenVpnOverCloak,
WireGuard
};
DockerContainer containerFromString(const QString &container);
QString containerToString(DockerContainer container);
QVector<Protocol> protocolsForContainer(DockerContainer container);
} // namespace amnezia
QDebug operator<<(QDebug debug, const amnezia::Protocol &p);
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
#endif // PROTOCOLS_DEFS_H

View File

@@ -1,52 +1,93 @@
#include "shadowsocksvpnprotocol.h"
#include "core/servercontroller.h"
#include "communicator.h"
#include "debug.h"
#include "utils.h"
#include "protocols/protocols_defs.h"
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QJsonObject>
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QString &args, QObject *parent):
OpenVpnProtocol(args, parent)
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QJsonObject &configuration, QObject *parent):
OpenVpnProtocol(configuration, parent)
{
m_shadowSocksConfig = args;
readShadowSocksConfiguration(configuration);
}
ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol()
{
qDebug() << "ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol";
ShadowSocksVpnProtocol::stop();
QThread::msleep(200);
m_ssProcess.close();
}
ErrorCode ShadowSocksVpnProtocol::start()
{
qDebug() << "ShadowSocksVpnProtocol::start()";
QJsonObject config = QJsonDocument::fromJson(m_shadowSocksConfig.toUtf8()).object();
if (Utils::processIsRunning(Utils::executable("ss-local", false))) {
Utils::killProcessByName(Utils::executable("ss-local", false));
}
ssProcess.setProcessChannelMode(QProcess::MergedChannels);
#ifdef QT_DEBUG
m_shadowSocksCfgFile.setAutoRemove(false);
#endif
m_shadowSocksCfgFile.open();
m_shadowSocksCfgFile.write(QJsonDocument(m_shadowSocksConfig).toJson());
m_shadowSocksCfgFile.close();
ssProcess.setProgram(shadowSocksExecPath());
ssProcess.setArguments(QStringList() << "-s" << config.value("server").toString()
<< "-p" << QString::number(config.value("server_port").toInt())
<< "-l" << QString::number(config.value("local_port").toInt())
<< "-m" << config.value("method").toString()
<< "-k" << config.value("password").toString()
);
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName()
<< "--no-delay";
ssProcess.start();
ssProcess.waitForStarted();
qDebug().noquote() << "ShadowSocksVpnProtocol::start()"
<< shadowSocksExecPath() << args.join(" ");
if (ssProcess.state() == QProcess::ProcessState::Running) {
m_ssProcess.setProcessChannelMode(QProcess::MergedChannels);
m_ssProcess.setProgram(shadowSocksExecPath());
m_ssProcess.setArguments(args);
connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this](){
qDebug().noquote() << "ss-local:" << m_ssProcess.readAllStandardOutput();
});
connect(&m_ssProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
if (exitStatus != QProcess::NormalExit){
emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed);
stop();
}
if (exitCode !=0 ){
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
});
m_ssProcess.start();
m_ssProcess.waitForStarted();
if (m_ssProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
return OpenVpnProtocol::start();
}
else return ErrorCode::FailedToStartRemoteProcessError;
else return ErrorCode::ShadowSocksExecutableMissing;
}
void ShadowSocksVpnProtocol::stop()
{
OpenVpnProtocol::stop();
qDebug() << "ShadowSocksVpnProtocol::stop()";
ssProcess.kill();
m_ssProcess.terminate();
#ifdef Q_OS_WIN
Utils::signalCtrl(m_ssProcess.processId(), CTRL_C_EVENT);
#endif
}
QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
QString ShadowSocksVpnProtocol::shadowSocksExecPath()
{
#ifdef Q_OS_WIN
return Utils::executable(QString("ss/ss-local"), true);
@@ -55,14 +96,7 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
#endif
}
QString ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto)
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
{
QJsonObject ssConfig;
ssConfig.insert("server", credentials.hostName);
ssConfig.insert("server_port", ServerController::ssRemotePort());
ssConfig.insert("local_port", ServerController::ssContainerPort());
ssConfig.insert("password", credentials.password);
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ServerController::ssEncryption());
return QJsonDocument(ssConfig).toJson();
m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data).toObject();
}

View File

@@ -3,25 +3,29 @@
#include "openvpnprotocol.h"
#include "QProcess"
#include "protocols/protocols_defs.h"
class ShadowSocksVpnProtocol : public OpenVpnProtocol
{
public:
ShadowSocksVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
ShadowSocksVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~ShadowSocksVpnProtocol() override;
ErrorCode start() override;
void stop() override;
static QString genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocks);
protected:
void readShadowSocksConfiguration(const QJsonObject &configuration);
protected:
QString shadowSocksExecPath() const;
protected:
QString m_shadowSocksConfig;
QJsonObject m_shadowSocksConfig;
private:
QProcess ssProcess;
static QString shadowSocksExecPath();
private:
QProcess m_ssProcess;
QTemporaryFile m_shadowSocksCfgFile;
};
#endif // SHADOWSOCKSVPNPROTOCOL_H

View File

@@ -1,42 +1,26 @@
#include <QDebug>
#include <QTimer>
#include "communicator.h"
#include "vpnprotocol.h"
#include "core/errorstrings.h"
Communicator* VpnProtocol::m_communicator = nullptr;
VpnProtocol::VpnProtocol(const QString& args, QObject* parent)
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent)
: QObject(parent),
m_connectionState(ConnectionState::Unknown),
m_rawConfig(configuration),
m_timeoutTimer(new QTimer(this)),
m_receivedBytes(0),
m_sentBytes(0)
{
m_timeoutTimer->setSingleShot(true);
connect(m_timeoutTimer, &QTimer::timeout, this, &VpnProtocol::onTimeout);
Q_UNUSED(args)
}
void VpnProtocol::initializeCommunicator(QObject* parent)
{
if (!m_communicator) {
m_communicator = new Communicator(parent);
}
}
Communicator* VpnProtocol::communicator()
{
return m_communicator;
}
void VpnProtocol::setLastError(ErrorCode lastError)
{
m_lastError = lastError;
if (lastError){
setConnectionState(ConnectionState::Disconnected);
setConnectionState(ConnectionState::Error);
}
qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError);
}
@@ -79,9 +63,14 @@ void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes)
void VpnProtocol::setConnectionState(VpnProtocol::ConnectionState state)
{
qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state);
if (m_connectionState == state) {
return;
}
if (m_connectionState == ConnectionState::Disconnected && state == ConnectionState::Disconnecting) {
return;
}
m_connectionState = state;
if (m_connectionState == ConnectionState::Disconnected) {
@@ -113,7 +102,7 @@ QString VpnProtocol::textConnectionState(ConnectionState connectionState)
case ConnectionState::Connecting: return tr("Connecting...");
case ConnectionState::Connected: return tr("Connected");
case ConnectionState::Disconnecting: return tr("Disconnecting...");
case ConnectionState::TunnelReconnecting: return tr("Reconnecting...");
case ConnectionState::Reconnecting: return tr("Reconnecting...");
case ConnectionState::Error: return tr("Error");
default:
;
@@ -127,12 +116,12 @@ QString VpnProtocol::textConnectionState() const
return textConnectionState(m_connectionState);
}
bool VpnProtocol::onConnected() const
bool VpnProtocol::isConnected() const
{
return m_connectionState == ConnectionState::Connected;
}
bool VpnProtocol::onDisconnected() const
bool VpnProtocol::isDisconnected() const
{
return m_connectionState == ConnectionState::Disconnected;
}

View File

@@ -3,30 +3,29 @@
#include <QObject>
#include <QString>
#include <QJsonObject>
#include "core/defs.h"
using namespace amnezia;
class QTimer;
class Communicator;
class VpnProtocol : public QObject
{
Q_OBJECT
public:
explicit VpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~VpnProtocol() override = default;
enum class ConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, TunnelReconnecting, Error};
enum ConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, Reconnecting, Error};
Q_ENUM(ConnectionState)
static Communicator* communicator();
static QString textConnectionState(ConnectionState connectionState);
static void initializeCommunicator(QObject* parent = nullptr);
virtual bool onConnected() const;
virtual bool onDisconnected() const;
virtual bool isConnected() const;
virtual bool isDisconnected() const;
virtual ErrorCode start() = 0;
virtual void stop() = 0;
@@ -54,18 +53,18 @@ protected:
virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
virtual void setConnectionState(VpnProtocol::ConnectionState state);
static Communicator* m_communicator;
ConnectionState m_connectionState;
QString m_routeGateway;
QString m_vpnAddress;
QString m_vpnGateway;
QJsonObject m_rawConfig;
private:
QTimer* m_timeoutTimer;
ErrorCode m_lastError;
quint64 m_receivedBytes;
quint64 m_sentBytes;
};
#endif // VPNPROTOCOL_H

View File

@@ -34,11 +34,32 @@
<file>images/server_settings.png</file>
<file>images/share.png</file>
<file>server_scripts/remove_container.sh</file>
<file>server_scripts/setup_openvpn_server.sh</file>
<file>server_scripts/template_openvpn.ovpn</file>
<file>images/background_connected.png</file>
<file>server_scripts/setup_shadowsocks_server.sh</file>
<file>server_scripts/template_shadowsocks.ovpn</file>
<file>server_scripts/setup_firewall.sh</file>
<file>server_scripts/setup_host_firewall.sh</file>
<file>images/reload.png</file>
<file>server_scripts/openvpn_cloak/Dockerfile</file>
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
<file>server_scripts/openvpn_cloak/start.sh</file>
<file>server_scripts/openvpn_cloak/template.ovpn</file>
<file>server_scripts/install_docker.sh</file>
<file>server_scripts/build_container.sh</file>
<file>server_scripts/prepare_host.sh</file>
<file>images/check.png</file>
<file>images/uncheck.png</file>
<file>images/settings_grey.png</file>
<file>images/plus.png</file>
<file>server_scripts/check_connection.sh</file>
<file>server_scripts/remove_all_containers.sh</file>
<file>server_scripts/openvpn_cloak/run_container.sh</file>
<file>server_scripts/openvpn/configure_container.sh</file>
<file>server_scripts/openvpn/run_container.sh</file>
<file>server_scripts/openvpn/template.ovpn</file>
<file>server_scripts/openvpn/Dockerfile</file>
<file>server_scripts/openvpn/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/template.ovpn</file>
</qresource>
</RCC>

View File

@@ -1,88 +0,0 @@
#include "runguard.h"
#include <QCryptographicHash>
namespace
{
QString generateKeyHash( const QString& key, const QString& salt )
{
QByteArray data;
data.append( key.toUtf8() );
data.append( salt.toUtf8() );
data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
return data;
}
}
RunGuard::RunGuard(const QString& key)
: key( key )
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
, sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
, sharedMem( sharedmemKey )
, memLock( memLockKey, 1 )
{
qDebug() << "RunGuard::RunGuard key" << key;
}
RunGuard &RunGuard::instance(const QString& key)
{
static RunGuard s(key);
return s;
}
void RunGuard::activate()
{
memLock.acquire();
{
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
fix.attach();
}
memLock.release();
}
RunGuard::~RunGuard()
{
release();
}
bool RunGuard::isAnotherRunning() const
{
if ( sharedMem.isAttached() )
return false;
memLock.acquire();
const bool isRunning = sharedMem.attach();
if ( isRunning )
sharedMem.detach();
memLock.release();
return isRunning;
}
bool RunGuard::tryToRun()
{
if ( isAnotherRunning() ) // Extra check
return false;
memLock.acquire();
const bool result = sharedMem.create( sizeof( quint64 ) );
memLock.release();
if ( !result )
{
release();
return false;
}
return true;
}
void RunGuard::release()
{
memLock.acquire();
if ( sharedMem.isAttached() )
sharedMem.detach();
memLock.release();
}

View File

@@ -1,37 +0,0 @@
#ifndef RUNGUARD_H
#define RUNGUARD_H
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
#include <QDebug>
/**
* @brief The RunGuard class - The application single instance (via shared memory)
*/
class RunGuard
{
public:
static RunGuard &instance(const QString& key = QString());
~RunGuard();
void activate();
bool isAnotherRunning() const;
bool tryToRun();
void release();
private:
RunGuard(const QString& key);
Q_DISABLE_COPY( RunGuard )
const QString key;
const QString memLockKey;
const QString sharedmemKey;
mutable QSharedMemory sharedMem;
mutable QSystemSemaphore memLock;
};
#endif // RUNGUARD_H

View File

@@ -0,0 +1 @@
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER

View File

@@ -0,0 +1 @@
uname -a

View File

@@ -0,0 +1,7 @@
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum";\
if [[ -f "$pm_apt" ]]; then pm=$pm_apt; docker_pkg="docker.io"; else pm=$pm_yum; docker_pkg="docker"; fi;\
if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi;\
docker_service=$(systemctl list-units --full -all | grep docker.service);\
if [[ -f "$pm_apt" ]]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if [[ -z "$docker_service" ]]; then sudo $pm update -y -q; sudo $pm install -y -q curl $docker_pkg; fi;\
sudo systemctl start docker

View File

@@ -0,0 +1,49 @@
FROM alpine:latest
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
RUN apk --update upgrade --no-cache
ENV EASYRSA_BATCH 1
ENV PATH="/usr/share/easy-rsa:${PATH}"
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]

View File

@@ -0,0 +1,27 @@
sudo docker exec -i $CONTAINER_NAME bash -c '\
echo -e "\
port $OPENVPN_PORT \\n\
proto $OPENVPN_TRANSPORT_PROTO \\n\
dev tun \\n\
ca /opt/amnezia/openvpn/ca.crt \\n\
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
dh /opt/amnezia/openvpn/dh.pem \\n\
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
ifconfig-pool-persist ipp.txt \\n\
duplicate-cn \\n\
keepalive 10 120 \\n\
$OPENVPN_NCP_DISABLE \\n\
cipher $OPENVPN_CIPHER \\n\
data-ciphers $OPENVPN_CIPHER \\n\
auth $OPENVPN_HASH \\n\
user nobody \\n\
group nobody \\n\
persist-key \\n\
persist-tun \\n\
status openvpn-status.log \\n\
verb 1 \\n\
tls-server \\n\
tls-version-min 1.2 \\n\
$OPENVPN_TLS_AUTH" >/opt/amnezia/openvpn/server.conf'

View File

@@ -0,0 +1,18 @@
# Run container
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO --name $CONTAINER_NAME $CONTAINER_NAME
# Create tun device if not exist
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
# Prevent to route packets outside of the container in case if server behind of the NAT
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
# OpenVPN config
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
cd /opt/amnezia/openvpn && easyrsa init-pki; \
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'

View File

@@ -0,0 +1,26 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
# kill daemons in case of restart
killall -KILL openvpn
# start daemons if configured
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
tail -f /dev/null

View File

@@ -0,0 +1,35 @@
client
dev tun
proto $OPENVPN_TRANSPORT_PROTO
resolv-retry infinite
nobind
persist-key
persist-tun
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
redirect-gateway def1 bypass-dhcp
dhcp-option DNS $PRIMARY_DNS
dhcp-option DNS $SECONDARY_DNS
block-outside-dns
remote $REMOTE_HOST $OPENVPN_PORT
<ca>
$OPENVPN_CA_CERT
</ca>
<cert>
$OPENVPN_CLIENT_CERT
</cert>
<key>
$OPENVPN_PRIV_KEY
</key>
<tls-auth>
$OPENVPN_TA_KEY
</tls-auth>

View File

@@ -0,0 +1,52 @@
FROM alpine:latest
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
RUN apk --update upgrade --no-cache
ENV EASYRSA_BATCH 1
ENV PATH="/usr/share/easy-rsa:${PATH}"
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
RUN curl -L https://github.com/cbeuw/Cloak/releases/download/v2.5.3/ck-server-linux-amd64-v2.5.3 > /usr/bin/ck-server
RUN chmod a+x /usr/bin/ck-server
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]

View File

@@ -0,0 +1,53 @@
sudo docker exec -i $CONTAINER_NAME bash -c '\
echo -e "\
port $OPENVPN_PORT \\n\
proto $OPENVPN_TRANSPORT_PROTO \\n\
dev tun \\n\
ca /opt/amnezia/openvpn/ca.crt \\n\
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
dh /opt/amnezia/openvpn/dh.pem \\n\
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
ifconfig-pool-persist ipp.txt \\n\
duplicate-cn \\n\
keepalive 10 120 \\n\
$OPENVPN_NCP_DISABLE \\n\
cipher $OPENVPN_CIPHER \\n\
data-ciphers $OPENVPN_CIPHER \\n\
auth $OPENVPN_HASH \\n\
user nobody \\n\
group nobody \\n\
persist-key \\n\
persist-tun \\n\
status openvpn-status.log \\n\
verb 1 \\n\
tls-server \\n\
tls-version-min 1.2 \\n\
$OPENVPN_TLS_AUTH" >/opt/amnezia/openvpn/server.conf'
# Cloak config
sudo docker exec -i $CONTAINER_NAME bash -c '\
mkdir -p /opt/amnezia/cloak; \
cd /opt/amnezia/cloak || exit 1; \
CLOAK_ADMIN_UID=$(ck-server -u) && echo $CLOAK_ADMIN_UID > /opt/amnezia/cloak/cloak_admin_uid.key; \
CLOAK_BYPASS_UID=$(ck-server -u) && echo $CLOAK_BYPASS_UID > /opt/amnezia/cloak/cloak_bypass_uid.key; \
IFS=, read CLOAK_PUBLIC_KEY CLOAK_PRIVATE_KEY <<<$(ck-server -k); \
echo $CLOAK_PUBLIC_KEY > /opt/amnezia/cloak/cloak_public.key; \
echo $CLOAK_PRIVATE_KEY > /opt/amnezia/cloak/cloak_private.key; \
echo -e "{\\n\
\"ProxyBook\": {\\n\
\"openvpn\": [\\n\
\"$OPENVPN_TRANSPORT_PROTO\",\\n\
\"localhost:$OPENVPN_PORT\"\\n\
]\\n\
},\\n\
\"BypassUID\": [\\n\
\"$CLOAK_BYPASS_UID\"\\n\
],\\n\
\"BindAddr\":[\":443\"],\\n\
\"RedirAddr\": \"$FAKE_WEB_SITE_ADDRESS\",\\n\
\"PrivateKey\": \"$CLOAK_PRIVATE_KEY\",\\n\
\"AdminUID\": \"$CLOAK_ADMIN_UID\",\\n\
\"DatabasePath\": \"userinfo.db\",\\n\
\"StreamTimeout\": 300\\n\
}" >/opt/amnezia/cloak/ck-config.json'

View File

@@ -0,0 +1,17 @@
# Run container
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $CLOAK_SERVER_PORT:443/tcp --name $CONTAINER_NAME $CONTAINER_NAME
# Create tun device if not exist
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
# Prevent to route packets outside of the container in case if server behind of the NAT
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
# OpenVPN config
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
cd /opt/amnezia/openvpn && easyrsa init-pki; \
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
# kill daemons in case of restart
killall -KILL openvpn
killall -KILL ck-server
# start daemons if configured
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
if [ -f /opt/amnezia/cloak/ck-config.json ]; then (ck-server -c /opt/amnezia/cloak/ck-config.json &); fi
tail -f /dev/null

View File

@@ -0,0 +1,36 @@
client
dev tun
proto $OPENVPN_TRANSPORT_PROTO
resolv-retry infinite
nobind
persist-key
persist-tun
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
redirect-gateway def1 bypass-dhcp
dhcp-option DNS $PRIMARY_DNS
dhcp-option DNS $SECONDARY_DNS
block-outside-dns
route $REMOTE_HOST 255.255.255.255 net_gateway
remote 127.0.0.1 1194
<ca>
$OPENVPN_CA_CERT
</ca>
<cert>
$OPENVPN_CLIENT_CERT
</cert>
<key>
$OPENVPN_PRIV_KEY
</key>
<tls-auth>
$OPENVPN_TA_KEY
</tls-auth>

View File

@@ -0,0 +1,53 @@
FROM alpine:latest
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
RUN apk --update upgrade --no-cache
ENV EASYRSA_BATCH 1
ENV PATH="/usr/share/easy-rsa:${PATH}"
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.10.9/shadowsocks-v1.10.9.x86_64-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
RUN chmod a+x /usr/bin/ssserver
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]

View File

@@ -0,0 +1,40 @@
sudo docker exec -i $CONTAINER_NAME bash -c '\
echo -e "\
port $OPENVPN_PORT \\n\
proto tcp \\n\
dev tun \\n\
ca /opt/amnezia/openvpn/ca.crt \\n\
cert /opt/amnezia/openvpn/AmneziaReq.crt \\n\
key /opt/amnezia/openvpn/AmneziaReq.key \\n\
dh /opt/amnezia/openvpn/dh.pem \\n\
server $VPN_SUBNET_IP $VPN_SUBNET_MASK \\n\
ifconfig-pool-persist ipp.txt \\n\
duplicate-cn \\n\
keepalive 10 120 \\n\
$OPENVPN_NCP_DISABLE \\n\
cipher $OPENVPN_CIPHER \\n\
data-ciphers $OPENVPN_CIPHER \\n\
auth $OPENVPN_HASH \\n\
user nobody \\n\
group nobody \\n\
persist-key \\n\
persist-tun \\n\
status openvpn-status.log \\n\
verb 1 \\n\
tls-server \\n\
tls-version-min 1.2 \\n\
$OPENVPN_TLS_AUTH" >/opt/amnezia/openvpn/server.conf'
# Cloak config
sudo docker exec -i $CONTAINER_NAME bash -c '\
mkdir -p /opt/amnezia/shadowsocks; \
cd /opt/amnezia/shadowsocks || exit 1; \
SHADOWSOCKS_PASSWORD=$(openssl rand -base64 32 | tr "=" "A" | tr "+" "A" | tr "/" "A") && echo $SHADOWSOCKS_PASSWORD > /opt/amnezia/shadowsocks/shadowsocks.key; \
echo -e "{\\n\
\"local_port\": 8585,\\n\
\"method\": \"$SHADOWSOCKS_CIPHER\",\\n\
\"password\": \"$SHADOWSOCKS_PASSWORD\",\\n\
\"server\": \"0.0.0.0\",\\n\
\"server_port\": $SHADOWSOCKS_SERVER_PORT,\\n\
\"timeout\": 60\\n\
}" >/opt/amnezia/shadowsocks/ss-config.json'

View File

@@ -0,0 +1,17 @@
# Run container
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp --name $CONTAINER_NAME $CONTAINER_NAME
# Create tun device if not exist
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
# Prevent to route packets outside of the container in case if server behind of the NAT
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
# OpenVPN config
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
cd /opt/amnezia/openvpn && easyrsa init-pki; \
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
# kill daemons in case of restart
killall -KILL openvpn
killall -KILL ssserver
# start daemons if configured
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
if [ -f /opt/amnezia/shadowsocks/ss-config.json ]; then (ssserver -c /opt/amnezia/shadowsocks/ss-config.json &); fi
tail -f /dev/null

View File

@@ -0,0 +1,37 @@
client
dev tun
proto tcp
resolv-retry infinite
nobind
persist-key
persist-tun
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
redirect-gateway def1 bypass-dhcp
dhcp-option DNS $PRIMARY_DNS
dhcp-option DNS $SECONDARY_DNS
block-outside-dns
socks-proxy 127.0.0.1 $SHADOWSOCKS_LOCAL_PORT
route $REMOTE_HOST 255.255.255.255 net_gateway
remote $REMOTE_HOST $OPENVPN_PORT
<ca>
$OPENVPN_CA_CERT
</ca>
<cert>
$OPENVPN_CLIENT_CERT
</cert>
<key>
$OPENVPN_PRIV_KEY
</key>
<tls-auth>
$OPENVPN_TA_KEY
</tls-auth>

View File

@@ -0,0 +1,3 @@
CUR_USER=$(whoami);\
sudo mkdir -p $DOCKERFILE_FOLDER;\
sudo chown $CUR_USER $DOCKERFILE_FOLDER

View File

@@ -0,0 +1,3 @@
sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker stop
sudo docker ps | grep amnezia | awk '{print $1}' | xargs sudo docker rm
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi

View File

@@ -1,2 +1,2 @@
docker stop $CONTAINER_NAME
docker rm -f $CONTAINER_NAME
sudo docker stop $CONTAINER_NAME
sudo docker rm -f $CONTAINER_NAME

View File

@@ -1,3 +0,0 @@
sysctl -w net.ipv4.ip_forward=1
iptables -P FORWARD ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

View File

@@ -0,0 +1,31 @@
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -C INPUT -p icmp --icmp-type echo-request -j DROP || sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
#sudo iptables -P FORWARD ACCEPT
sudo iptables -C FORWARD -j DOCKER-USER || sudo iptables -A FORWARD -j DOCKER-USER
sudo iptables -C FORWARD -j DOCKER-ISOLATION-STAGE-1 || sudo iptables -A FORWARD -j DOCKER-ISOLATION-STAGE-1; \
sudo iptables -C FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT || sudo iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; \
sudo iptables -C FORWARD -o docker0 -j DOCKER || sudo iptables -A FORWARD -o docker0 -j DOCKER; \
sudo iptables -C FORWARD -i docker0 ! -o docker0 -j ACCEPT || sudo iptables -A FORWARD -i docker0 ! -o docker0 -j ACCEPT; \
sudo iptables -C FORWARD -i docker0 -o docker0 -j ACCEPT || sudo iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT
# Tuning network
sudo sysctl fs.file-max=51200; \
sudo sysctl net.core.rmem_max=67108864; \
sudo sysctl net.core.wmem_max=67108864; \
sudo sysctl net.core.netdev_max_backlog=250000; \
sudo sysctl net.core.somaxconn=4096; \
sudo sysctl net.ipv4.tcp_syncookies=1; \
sudo sysctl net.ipv4.tcp_tw_reuse=1; \
sudo sysctl net.ipv4.tcp_tw_recycle=0; \
sudo sysctl net.ipv4.tcp_fin_timeout=30; \
sudo sysctl net.ipv4.tcp_keepalive_time=1200; \
sudo sysctl net.ipv4.ip_local_port_range="10000 65000"; \
sudo sysctl net.ipv4.tcp_max_syn_backlog=8192; \
sudo sysctl net.ipv4.tcp_max_tw_buckets=5000; \
sudo sysctl net.ipv4.tcp_fastopen=3; \
sudo sysctl net.ipv4.tcp_mem="25600 51200 102400"; \
sudo sysctl net.ipv4.tcp_rmem="4096 87380 67108864"; \
sudo sysctl net.ipv4.tcp_wmem="4096 65536 67108864"; \
sudo sysctl net.ipv4.tcp_mtu_probing=1; \
sudo sysctl net.ipv4.tcp_congestion_control=hybla

View File

@@ -1,24 +0,0 @@
#CONTAINER_NAME=... this var will be set in ServerController
apt-get update
iptables -P FORWARD ACCEPT
apt install -y docker.io curl
systemctl start docker
docker stop $CONTAINER_NAME
docker rm -f $CONTAINER_NAME
docker pull amneziavpn/openvpn:latest
docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/udp --name $CONTAINER_NAME amneziavpn/openvpn:latest
docker exec -i $CONTAINER_NAME sh -c "mkdir -p /opt/amneziavpn_data/clients"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/dh.pem /etc/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req MyReq nopass << EOF2 yes EOF2"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/ca.crt pki/issued/MyReq.crt pki/private/MyReq.key ta.key /etc/openvpn"
docker exec -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf"

View File

@@ -1,24 +0,0 @@
#CONTAINER_NAME=... this var will be set in ServerController
apt-get update
iptables -P FORWARD ACCEPT
apt install -y docker.io curl
systemctl start docker
docker stop $CONTAINER_NAME
docker rm -f $CONTAINER_NAME
docker pull amneziavpn/shadowsocks:latest
docker run -d --restart always --cap-add=NET_ADMIN -p 1194:1194/tcp -p 6789:6789/tcp --name $CONTAINER_NAME amneziavpn/shadowsocks:latest
docker exec -i $CONTAINER_NAME sh -c "mkdir -p /opt/amneziavpn_data/clients"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa init-pki"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa gen-dh"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/dh.pem /etc/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req MyReq nopass << EOF2 yes EOF2"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && easyrsa sign-req server MyReq << EOF3 yes EOF3"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && openvpn --genkey --secret ta.key << EOF4"
docker exec -i $CONTAINER_NAME sh -c "cd /opt/amneziavpn_data && cp pki/ca.crt pki/issued/MyReq.crt pki/private/MyReq.key ta.key /etc/openvpn"
docker exec -d $CONTAINER_NAME sh -c "openvpn --config /etc/openvpn/server.conf"

View File

@@ -1,29 +0,0 @@
client
dev tun
proto $PROTO
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA512
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
remote $REMOTE_HOST $REMOTE_PORT
<ca>
$CA_CERT
</ca>
<cert>
$CLIENT_CERT
</cert>
<key>
$PRIV_KEY
</key>
<tls-auth>
$TA_KEY
</tls-auth>

View File

@@ -1,31 +0,0 @@
client
dev tun
proto $PROTO
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA512
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
socks-proxy 127.0.0.1 $LOCAL_PROXY_PORT
route $REMOTE_HOST 255.255.255.255 net_gateway
remote $REMOTE_HOST $REMOTE_PORT
<ca>
$CA_CERT
</ca>
<cert>
$CLIENT_CERT
</cert>
<key>
$PRIV_KEY
</key>
<tls-auth>
$TA_KEY
</tls-auth>

View File

@@ -1,74 +1,236 @@
#include "defines.h"
#include "settings.h"
#include <QDebug>
#include "protocols/protocols_defs.h"
const char Settings::cloudFlareNs1[] = "1.1.1.1";
const char Settings::cloudFlareNs2[] = "1.0.0.1";
Settings::Settings(QObject* parent) :
QObject(parent),
m_settings (ORGANIZATION_NAME, APPLICATION_NAME, this)
{
read();
// Import old settings
if (serversCount() == 0) {
QString user = m_settings.value("Server/userName").toString();
QString password = m_settings.value("Server/password").toString();
QString serverName = m_settings.value("Server/serverName").toString();
int port = m_settings.value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){
QJsonObject server;
server.insert(config_key::userName, user);
server.insert(config_key::password, password);
server.insert(config_key::hostName, serverName);
server.insert(config_key::port, port);
server.insert(config_key::description, tr("Server #1"));
addServer(server);
m_settings.remove("Server/userName");
m_settings.remove("Server/password");
m_settings.remove("Server/serverName");
m_settings.remove("Server/serverPort");
}
}
}
void Settings::read()
int Settings::serversCount() const
{
m_settings.beginGroup("Server");
m_userName = m_settings.value("userName", QString()).toString();
m_password = m_settings.value("password", QString()).toString();
m_serverName = m_settings.value("serverName", QString()).toString();
m_serverPort = m_settings.value("serverPort", 22).toInt();
m_settings.endGroup();
return serversArray().size();
}
void Settings::save()
QJsonObject Settings::server(int index) const
{
m_settings.beginGroup("Server");
m_settings.setValue("userName", m_userName);
m_settings.setValue("password", m_password);
m_settings.setValue("serverName", m_serverName);
m_settings.setValue("serverPort", m_serverPort);
m_settings.endGroup();
const QJsonArray &servers = serversArray();
if (index >= servers.size()) return QJsonObject();
return servers.at(index).toObject();
}
bool Settings::haveAuthData() const
void Settings::addServer(const QJsonObject &server)
{
return (!serverName().isEmpty() && !userName().isEmpty() && !password().isEmpty());
QJsonArray servers = serversArray();
servers.append(server);
setServersArray(servers);
}
void Settings::setUserName(const QString& login)
void Settings::removeServer(int index)
{
m_userName = login;
QJsonArray servers = serversArray();
if (index >= servers.size()) return;
servers.removeAt(index);
setServersArray(servers);
}
void Settings::setPassword(const QString& password)
bool Settings::editServer(int index, const QJsonObject &server)
{
m_password = password;
QJsonArray servers = serversArray();
if (index >= servers.size()) return false;
servers.replace(index, server);
setServersArray(servers);
return true;
}
void Settings::setServerName(const QString& serverName)
void Settings::setDefaultContainer(int serverIndex, DockerContainer container)
{
m_serverName = serverName;
QJsonObject s = server(serverIndex);
s.insert(config_key::defaultContainer, containerToString(container));
editServer(serverIndex, s);
}
void Settings::setServerPort(int serverPort)
DockerContainer Settings::defaultContainer(int serverIndex) const
{
m_serverPort = serverPort;
return containerFromString(defaultContainerName(serverIndex));
}
void Settings::setServerCredentials(const ServerCredentials &credentials)
QString Settings::defaultContainerName(int serverIndex) const
{
setServerName(credentials.hostName);
setServerPort(credentials.port);
setUserName(credentials.userName);
setPassword(credentials.password);
QString name = server(serverIndex).value(config_key::defaultContainer).toString();
if (name.isEmpty()) {
return containerToString(DockerContainer::None);
}
else return name;
}
ServerCredentials Settings::serverCredentials()
QMap<DockerContainer, QJsonObject> Settings::containers(int serverIndex) const
{
const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray();
QMap<DockerContainer, QJsonObject> containersMap;
for (const QJsonValue &val : containers) {
containersMap.insert(containerFromString(val.toObject().value(config_key::container).toString()), val.toObject());
}
return containersMap;
}
void Settings::setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers)
{
QJsonObject s = server(serverIndex);
QJsonArray c;
for (const QJsonObject &o: containers) {
c.append(o);
}
s.insert(config_key::containers, c);
editServer(serverIndex, s);
}
QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container)
{
if (container == DockerContainer::None) return QJsonObject();
return containers(serverIndex).value(container);
}
void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config)
{
if (container == DockerContainer::None) {
qCritical() << "Settings::setContainerConfig trying to set config for container == DockerContainer::None";
return;
}
auto c = containers(serverIndex);
c[container] = config;
c[container][config_key::container] = containerToString(container);
setContainers(serverIndex, c);
}
void Settings::removeContainerConfig(int serverIndex, DockerContainer container)
{
if (container == DockerContainer::None) {
qCritical() << "Settings::removeContainerConfig trying to remove config for container == DockerContainer::None";
return;
}
auto c = containers(serverIndex);
c.remove(container);
setContainers(serverIndex, c);
}
QJsonObject Settings::protocolConfig(int serverIndex, DockerContainer container, Protocol proto)
{
const QJsonObject &c = containerConfig(serverIndex, container);
return c.value(protoToString(proto)).toObject();
}
void Settings::setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config)
{
QJsonObject c = containerConfig(serverIndex, container);
c.insert(protoToString(proto), config);
setContainerConfig(serverIndex, container, c);
}
void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto)
{
if (proto == Protocol::Any) {
for (Protocol p: { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak, Protocol::WireGuard}) {
clearLastConnectionConfig(serverIndex, container, p);
}
return;
}
QJsonObject c = protocolConfig(serverIndex, container, proto);
c.remove(config_key::last_config);
setProtocolConfig(serverIndex, container, proto, c);
qDebug() << "Settings::clearLastConnectionConfig for" << serverIndex << container << proto;
}
bool Settings::haveAuthData(int serverIndex) const
{
if (serverIndex < 0) return false;
ServerCredentials cred = serverCredentials(serverIndex);
return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty());
}
QString Settings::nextAvailableServerName() const
{
int i = 0;
bool nameExist = false;
do {
i++;
nameExist = false;
for (const QJsonValue &server: serversArray()) {
if (server.toObject().value(config_key::description).toString() == tr("Server") + " " + QString::number(i)) {
nameExist = true;
break;
}
}
} while (nameExist);
return tr("Server") + " " + QString::number(i);
}
QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); }
QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); }
//void Settings::setServerCredentials(const ServerCredentials &credentials)
//{
// setServerName(credentials.hostName);
// setServerPort(credentials.port);
// setUserName(credentials.userName);
// setPassword(credentials.password);
//}
ServerCredentials Settings::defaultServerCredentials() const
{
return serverCredentials(defaultServerIndex());
}
ServerCredentials Settings::serverCredentials(int index) const
{
const QJsonObject &s = server(index);
ServerCredentials credentials;
credentials.hostName = serverName();
credentials.userName = userName();
credentials.password = password();
credentials.port = serverPort();
credentials.hostName = s.value(config_key::hostName).toString();
credentials.userName = s.value(config_key::userName).toString();
credentials.password = s.value(config_key::password).toString();
credentials.port = s.value(config_key::port).toInt();
return credentials;
}

View File

@@ -5,7 +5,12 @@
#include <QString>
#include <QSettings>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include "core/defs.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
@@ -18,40 +23,84 @@ class Settings : public QObject
public:
explicit Settings(QObject* parent = nullptr);
void read();
void save();
ServerCredentials defaultServerCredentials() const;
ServerCredentials serverCredentials(int index) const;
//void setServerCredentials(const ServerCredentials &credentials);
void setUserName(const QString& login);
void setPassword(const QString& password);
void setServerName(const QString& serverName);
void setServerPort(int serverPort = 22);
void setServerCredentials(const ServerCredentials &credentials);
QJsonArray serversArray() const { return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); }
void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); }
QString userName() const { return m_userName; }
QString password() const { return m_password; }
QString serverName() const { return m_serverName; }
int serverPort() const { return m_serverPort; }
ServerCredentials serverCredentials();
// Servers section
int serversCount() const;
QJsonObject server(int index) const;
void addServer(const QJsonObject &server);
void removeServer(int index);
bool editServer(int index, const QJsonObject &server);
int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); }
void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); }
QJsonObject defaultServer() const { return server(defaultServerIndex()); }
bool haveAuthData() const;
void setDefaultContainer(int serverIndex, DockerContainer container);
DockerContainer defaultContainer(int serverIndex) const;
QString defaultContainerName(int serverIndex) const;
QMap<DockerContainer, QJsonObject> containers(int serverIndex) const;
void setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers);
QJsonObject containerConfig(int serverIndex, DockerContainer container);
void setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config);
void removeContainerConfig(int serverIndex, DockerContainer container);
QJsonObject protocolConfig(int serverIndex, DockerContainer container, Protocol proto);
void setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config);
void clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto = Protocol::Any);
bool haveAuthData(int serverIndex) const;
QString nextAvailableServerName() const;
// App settings section
bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", false).toBool(); }
void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); }
bool isStartMinimized() const { return m_settings.value("Conf/startMinimized", false).toBool(); }
void setStartMinimized(bool enabled) { m_settings.setValue("Conf/startMinimized", enabled); }
bool customRouting() const { return m_settings.value("Conf/customRouting", false).toBool(); }
void setCustomRouting(bool customRouting) { m_settings.setValue("Conf/customRouting", customRouting); }
// list of sites to pass blocking added by user
QStringList customSites() { return m_settings.value("customSites").toStringList(); }
void setCustomSites(const QStringList &customSites) { m_settings.setValue("customSites", customSites); }
QStringList customSites() { return m_settings.value("Conf/customSites").toStringList(); }
void setCustomSites(const QStringList &customSites) { m_settings.setValue("Conf/customSites", customSites); }
// list of ips to pass blocking generated from customSites
QStringList customIps() { return m_settings.value("customIps").toStringList(); }
void setCustomIps(const QStringList &customIps) { m_settings.setValue("customIps", customIps); }
QStringList customIps() { return m_settings.value("Conf/customIps").toStringList(); }
void setCustomIps(const QStringList &customIps) { m_settings.setValue("Conf/customIps", customIps); }
QString primaryDns() const;
QString secondaryDns() const;
//QString primaryDns() const { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); }
//QString secondaryDns() const { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); }
static const char cloudFlareNs1[];
static const char cloudFlareNs2[];
// static constexpr char openNicNs5[] = "94.103.153.176";
// static constexpr char openNicNs13[] = "144.76.103.143";
protected:
public:
private:
QSettings m_settings;
QString m_userName;
QString m_password;
QString m_serverName;
int m_serverPort;
};
#endif // SETTINGS_H

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
#include "SlidingStackedWidget.h"
#include <QEventLoop>
SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
: QStackedWidget(parent)
{
@@ -28,6 +30,14 @@ SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
m_wrap = false;
m_pnow = QPoint(0,0);
m_active = false;
animnow = new QPropertyAnimation();
animnext = new QPropertyAnimation();
animgroup = new QParallelAnimationGroup;
animgroup->addAnimation(animnow);
animgroup->addAnimation(animnext);
}
SlidingStackedWidget::~SlidingStackedWidget() {
@@ -90,12 +100,29 @@ void SlidingStackedWidget::slideInWidget(QWidget *widget, SlidingStackedWidget::
#endif
}
bool SlidingStackedWidget::isAnimationRunning()
{
return animgroup->state() == QAnimationGroup::Running;
}
void SlidingStackedWidget::waitForAnimation()
{
if (!isAnimationRunning()) return;
qDebug() << "Wait for stacked widget animation";
QEventLoop l;
connect(animgroup, &QParallelAnimationGroup::finished, &l, &QEventLoop::quit);
l.exec();
}
void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction direction) {
if (m_active) {
return;
}
else m_active = true;
m_nextWidget = newwidget;
enum t_direction directionhint;
int now = currentIndex(); // currentIndex() is a function inherited from QStackedWidget
int next = indexOf(newwidget);
@@ -149,22 +176,28 @@ void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction
widget(next)->raise();
// animate both, the now and next widget to the side, using animation framework
QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos");
//QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos");
animnow->setTargetObject(widget(now));
animnow->setPropertyName("pos");
animnow->setDuration(m_speed);
animnow->setEasingCurve(m_animationtype);
animnow->setStartValue(QPoint(pnow.x(), pnow.y()));
animnow->setEndValue(QPoint(offsetx + pnow.x(), offsety + pnow.y()));
QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos");
//QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos");
animnext->setTargetObject(widget(next));
animnext->setPropertyName("pos");
animnext->setDuration(m_speed);
animnext->setEasingCurve(m_animationtype);
animnext->setStartValue(QPoint(-offsetx + pnext.x(), offsety + pnext.y()));
animnext->setEndValue(QPoint(pnext.x(), pnext.y()));
QParallelAnimationGroup *animgroup = new QParallelAnimationGroup;
// QParallelAnimationGroup *animgroup = new QParallelAnimationGroup;
animgroup->addAnimation(animnow);
animgroup->addAnimation(animnext);
// animgroup->addAnimation(animnow);
// animgroup->addAnimation(animnext);
QObject::connect(animgroup, SIGNAL(finished()),this,SLOT(animationDoneSlot()));
m_next = next;
@@ -178,6 +211,18 @@ void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction
// that we implement here below in animationDoneSlot.
}
void SlidingStackedWidget::setCurrentWidget(QWidget *w)
{
m_nextWidget = w;
QStackedWidget::setCurrentWidget(w);
}
QWidget *SlidingStackedWidget::nextWidget() const
{
if (m_nextWidget == nullptr) return currentWidget();
return m_nextWidget;
}
void SlidingStackedWidget::animationDoneSlot(void) {
// when ready, call the QStackedWidget slot setCurrentIndex(int)
setCurrentIndex(m_next); // this function is inherited from QStackedWidget

View File

@@ -34,6 +34,9 @@ public:
SlidingStackedWidget(QWidget *parent);
~SlidingStackedWidget(void);
QWidget *nextWidget() const;
void setCurrentWidget(QWidget *w);
public slots:
// Some basic settings API
void setSpeed(int speed); // animation duration in milliseconds
@@ -47,6 +50,8 @@ public slots:
void slideInIdx(int idx, enum t_direction direction = AUTOMATIC);
void slideInWidget(QWidget *widget, enum t_direction direction = AUTOMATIC);
bool isAnimationRunning();
void waitForAnimation();
signals:
// this is used for internal purposes in the class engine
void animationFinished(void);
@@ -71,6 +76,12 @@ protected:
bool m_active;
QList<QWidget*> blockedPageList;
QPropertyAnimation *animnow;
QPropertyAnimation *animnext;
QParallelAnimationGroup *animgroup;
QWidget *m_nextWidget = nullptr;
};
#endif // SLIDINGSTACKEDWIDGET_H

Some files were not shown because too many files have changed in this diff Show More