Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4c0e519d9 | ||
|
|
4ba964db47 | ||
|
|
45e5ec76dd | ||
|
|
df27003998 | ||
|
|
9002568474 | ||
|
|
5c5411261a | ||
|
|
acf878c8dd | ||
|
|
491a09b175 | ||
|
|
51f7e6811e | ||
|
|
eee6b8b10f | ||
|
|
dc4a1c6eca | ||
|
|
02810ff844 | ||
|
|
1bb2ef9e30 | ||
|
|
df2a6dc278 | ||
|
|
835f767c3f | ||
|
|
e3fb239de9 | ||
|
|
de67f244da | ||
|
|
d424bb24cf | ||
|
|
615bba69e5 | ||
|
|
7bba7a9eab | ||
|
|
a5e9cea22f | ||
|
|
f9affb083b | ||
|
|
85b6b06cc9 | ||
|
|
059c6404ab | ||
|
|
0d989a7fae | ||
|
|
7dfc002316 | ||
|
|
8d8d392e84 | ||
|
|
c6e75d5f86 | ||
|
|
99bfd56ef4 | ||
|
|
c2f6c7d939 | ||
|
|
d831d68e73 | ||
|
|
84e4b776ac | ||
|
|
f9e1b2c6dc | ||
|
|
54fca5bebc | ||
|
|
c5ce417d79 | ||
|
|
6765142ebc | ||
|
|
ca898a6759 | ||
|
|
a2bb382652 | ||
|
|
02f966bc67 | ||
|
|
65acdc8c09 | ||
|
|
407ea77a9e | ||
|
|
3e4a9f54a7 | ||
|
|
6b77bd5f13 | ||
|
|
fb6de25e5f | ||
|
|
ffbe5107e2 | ||
|
|
40a5b2e3f3 | ||
|
|
c683884868 | ||
|
|
65961d8d2e | ||
|
|
7dc1f1e225 | ||
|
|
a47ab15aef | ||
|
|
c74efdaa9b | ||
|
|
39224c7bf7 | ||
|
|
96aa3d409d | ||
|
|
c63990f720 | ||
|
|
ad643bf76e | ||
|
|
c7ea4966fd | ||
|
|
8fd81be477 | ||
|
|
a1cb4ac544 | ||
|
|
f91854594c | ||
|
|
f661ea1d46 | ||
|
|
f50eea3eaf | ||
|
|
c15b57e690 | ||
|
|
5f7ef31345 | ||
|
|
447410a27a | ||
|
|
2aa9f9cca9 | ||
|
|
cba27d354d | ||
|
|
b398f42ada | ||
|
|
b6571d99de | ||
|
|
b2392c1943 | ||
|
|
048a673d31 | ||
|
|
c4df9c004b | ||
|
|
22b33a4f25 |
2
.gitignore
vendored
@@ -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
|
||||
*.*~
|
||||
|
||||
14
.travis.yml
@@ -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
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = client service platform
|
||||
|
||||
|
||||
43
README.md
@@ -1,2 +1,43 @@
|
||||
# Amnezia
|
||||
# Amnezia VPN
|
||||
## _The best client for self-hosted VPN_
|
||||
|
||||
[](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
|
||||
1801
client/3rd/QRCodeGenerator/QRCodeGenerator.cpp
Normal file
129
client/3rd/QRCodeGenerator/QRCodeGenerator.h
Normal 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_)
|
||||
5
client/3rd/QRCodeGenerator/QRCodeGenerator.pri
Normal file
@@ -0,0 +1,5 @@
|
||||
HEADERS += \
|
||||
3rd/QRCodeGenerator/QRCodeGenerator.h \
|
||||
|
||||
SOURCES += \
|
||||
3rd/QRCodeGenerator/QRCodeGenerator.cpp \
|
||||
@@ -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)
|
||||
|
||||
274
client/3rd/SingleApplication/singleapplication.cpp
Normal 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();
|
||||
}
|
||||
154
client/3rd/SingleApplication/singleapplication.h
Normal 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
|
||||
15
client/3rd/SingleApplication/singleapplication.pri
Normal 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
|
||||
}
|
||||
486
client/3rd/SingleApplication/singleapplication_p.cpp
Normal 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;
|
||||
}
|
||||
104
client/3rd/SingleApplication/singleapplication_p.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
48
client/configurators/cloak_configurator.cpp
Normal 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;
|
||||
}
|
||||
18
client/configurators/cloak_configurator.h
Normal 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
|
||||
304
client/configurators/openvpn_configurator.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
37
client/configurators/shadowsocks_configurator.cpp
Normal 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;
|
||||
}
|
||||
18
client/configurators/shadowsocks_configurator.h
Normal 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
|
||||
33
client/configurators/vpn_configurator.cpp
Normal 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 "";
|
||||
}
|
||||
}
|
||||
18
client/configurators/vpn_configurator.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
58
client/core/errorstrings.cpp
Normal 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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
68
client/core/scripts_registry.cpp
Normal 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;
|
||||
}
|
||||
39
client/core/scripts_registry.h
Normal 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
|
||||
16
client/core/server_defs.cpp
Normal 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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 136 KiB |
BIN
client/images/check.png
Normal file
|
After Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 55 KiB |
BIN
client/images/plus.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
client/images/reload.png
Normal file
|
After Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 846 B |
BIN
client/images/settings_grey.png
Normal file
|
After Width: | Height: | Size: 871 B |
BIN
client/images/uncheck.png
Normal file
|
After Width: | Height: | Size: 181 B |
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
109
client/protocols/openvpnovercloakprotocol.cpp
Normal 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();
|
||||
}
|
||||
30
client/protocols/openvpnovercloakprotocol.h
Normal 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
|
||||
@@ -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 = "";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
80
client/protocols/protocols_defs.cpp
Normal 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 {};
|
||||
}
|
||||
}
|
||||
134
client/protocols/protocols_defs.h
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
1
client/server_scripts/build_container.sh
Normal file
@@ -0,0 +1 @@
|
||||
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER
|
||||
1
client/server_scripts/check_connection.sh
Normal file
@@ -0,0 +1 @@
|
||||
uname -a
|
||||
7
client/server_scripts/install_docker.sh
Normal 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
|
||||
49
client/server_scripts/openvpn/Dockerfile
Normal 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 [ "" ]
|
||||
27
client/server_scripts/openvpn/configure_container.sh
Normal 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'
|
||||
|
||||
18
client/server_scripts/openvpn/run_container.sh
Normal 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'
|
||||
|
||||
26
client/server_scripts/openvpn/start.sh
Normal 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
|
||||
35
client/server_scripts/openvpn/template.ovpn
Normal 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>
|
||||
52
client/server_scripts/openvpn_cloak/Dockerfile
Normal 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 [ "" ]
|
||||
53
client/server_scripts/openvpn_cloak/configure_container.sh
Normal 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'
|
||||
17
client/server_scripts/openvpn_cloak/run_container.sh
Normal 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'
|
||||
28
client/server_scripts/openvpn_cloak/start.sh
Normal 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
|
||||
36
client/server_scripts/openvpn_cloak/template.ovpn
Normal 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>
|
||||
53
client/server_scripts/openvpn_shadowsocks/Dockerfile
Normal 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 [ "" ]
|
||||
@@ -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'
|
||||
17
client/server_scripts/openvpn_shadowsocks/run_container.sh
Normal 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'
|
||||
27
client/server_scripts/openvpn_shadowsocks/start.sh
Normal 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
|
||||
37
client/server_scripts/openvpn_shadowsocks/template.ovpn
Normal 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>
|
||||
3
client/server_scripts/prepare_host.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
CUR_USER=$(whoami);\
|
||||
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
||||
sudo chown $CUR_USER $DOCKERFILE_FOLDER
|
||||
3
client/server_scripts/remove_all_containers.sh
Normal 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
|
||||
@@ -1,2 +1,2 @@
|
||||
docker stop $CONTAINER_NAME
|
||||
docker rm -f $CONTAINER_NAME
|
||||
sudo docker stop $CONTAINER_NAME
|
||||
sudo docker rm -f $CONTAINER_NAME
|
||||
|
||||
@@ -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
|
||||
31
client/server_scripts/setup_host_firewall.sh
Normal 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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||