Compare commits

...
127 Commits
Author SHA1 Message Date
pokamest 1bb2ef9e30 ui fixes 2021-05-10 20:51:38 +03:00
pokamest df2a6dc278 cloak for macos fixed 2021-05-10 05:25:20 -07:00
pokamest 835f767c3f import/export fixes 2021-05-10 14:19:36 +03:00
pokamest e3fb239de9 Config export 2021-05-10 02:33:31 +03:00
pokamest de67f244da Multiprotocol support 2021-05-07 23:28:37 +03:00
pokamest d424bb24cf refactoring
Protocol to DockerContainer
2021-04-26 23:19:19 +03:00
pokamest 615bba69e5 refactoring 2021-04-26 22:54:31 +03:00
pokamest 7bba7a9eab cygwin grep added 2021-04-20 02:11:14 +03:00
pokamest a5e9cea22f Release 1.6 WIP 2021-04-20 02:09:47 +03:00
pokamest f9affb083b Macos route delete fix 2021-04-19 14:34:47 +03:00
pokamest 85b6b06cc9 - no dockerhub
- trafic masking
2021-04-04 23:12:36 +03:00
pokamest 059c6404ab gitignore updated 2021-04-04 23:09:31 +03:00
pokamest 0d989a7fae meta version updated 2021-04-04 23:08:01 +03:00
pokamest 7dfc002316 cloak exe added 2021-04-04 23:06:54 +03:00
pokamest 8d8d392e84 cygwin updated to x64 2021-04-04 23:04:31 +03:00
pokamest c6e75d5f86 server script fixes 2021-03-25 23:26:59 +03:00
pokamest 99bfd56ef4 minor release 1.5.3 2021-03-19 15:03:44 +03:00
pokamest c2f6c7d939 route delete fixed (Windows) 2021-03-18 22:13:05 +03:00
pokamest d831d68e73 ShadowSocks protocol fixes:
- remote for OpenVPN is set to real ip address
- remote ip will be added as alias in docker container
- ss-local graceful shutdown
- crash fixes
2021-03-18 18:45:08 +03:00
pokamest 84e4b776ac openvpn target host is 10.8.0.1 via ss route 2021-03-17 03:47:33 +03:00
pokamest f9e1b2c6dc QR code lib added
ShadowSocks export
ui stylesheet fixes
ip:port regexp fixed
dns settings reset bug fixed
2021-03-17 03:45:38 +03:00
pokamest 54fca5bebc cygpcre-1.dll added 2021-03-16 22:15:27 +03:00
pokamest c5ce417d79 macos close button fix 2021-03-14 12:52:19 -07:00
pokamest 6765142ebc ssh key auth fix 2021-03-14 12:51:52 -07:00
pokamest ca898a6759 Ssh key auth support added
yum/apt install support
2021-03-14 21:19:11 +03:00
pokamest a2bb382652 ShadowSocks password - sha256 2021-03-13 14:16:24 +03:00
pokamest 02f966bc67 Win7 fix
Connection import fix
2021-03-13 13:56:52 +03:00
pokamest 65acdc8c09 readme added 2021-03-09 18:48:59 +03:00
pokamest 407ea77a9e ssh connection strict check disabled 2021-03-09 18:45:41 +03:00
pokamest 3e4a9f54a7 cygwin binaries added 2021-03-09 18:44:45 +03:00
pokamest 6b77bd5f13 Server install fix 2021-03-08 18:17:50 +03:00
pokamest fb6de25e5f Migrate to cygwin sh 2021-03-06 15:07:43 +03:00
pokamest ffbe5107e2 Secondary instance fix 2021-03-06 14:59:55 +03:00
pokamest 40a5b2e3f3 sites list ui fix 2021-02-25 23:11:46 +03:00
pokamest c683884868 sites list reimplement 2021-02-25 21:16:00 +03:00
pokamest 65961d8d2e vpnconnection.cpp crash fix
ss server sript fix
2021-02-25 18:05:42 +03:00
pokamest 7dc1f1e225 Utils ip address regexp 2021-02-25 18:03:24 +03:00
pokamest a47ab15aef mainwindow.ui fix 2021-02-25 01:06:02 +03:00
pokamest c74efdaa9b ui fix for macos 2021-02-24 13:41:32 -08:00
pokamest 39224c7bf7 SingleApplication 2021-02-24 13:38:23 -08:00
pokamest 96aa3d409d SingleApplication 2021-02-24 23:40:57 +03:00
pokamest c63990f720 Auto start
Auto connect
Dns settings
ui fixes
2021-02-24 21:58:32 +03:00
pokamest ad643bf76e new icon 2021-02-22 18:07:39 +03:00
pokamest c7ea4966fd minor fixes:
-build_windows.bat
-win build fix
-qdebug fix
2021-02-22 16:31:43 +03:00
pokamest 8fd81be477 ShadowSocks fixes for MacOS 2021-02-21 09:44:53 -08:00
pokamest a1cb4ac544 Custom routing done
ShadowSocks enabled by default
2021-02-18 15:00:41 +03:00
pokamest f91854594c Merge branch 'dev' into service_refact 2021-02-11 19:17:16 +03:00
pokamest f661ea1d46 Merge branch 'macos_build_fix' into dev 2021-02-10 10:44:37 -08:00
pokamest f50eea3eaf macos signing fixes 2021-02-10 06:57:26 -08:00
pokamest c15b57e690 windows travis fix 2021-02-10 00:07:12 +03:00
pokamest 5f7ef31345 win cert updated 2021-02-09 00:33:26 +03:00
pokamest 447410a27a Macos build fix (#6)
macos deploy fixes
2021-02-08 23:57:35 +03:00
pokamest 2aa9f9cca9 macos build fix 2021-02-08 12:42:48 -08:00
pokamest cba27d354d macos deploy fixes 2021-02-08 21:10:34 +03:00
pokamest b398f42ada ipc process fix 2021-02-03 20:05:50 +03:00
pokamest b6571d99de Qt ro refact 2021-02-03 15:42:36 +03:00
pokamest b2392c1943 Qt Remote objects done 2021-02-02 22:51:31 +03:00
pokamest 048a673d31 Qt remote objects IPC 2021-02-02 01:47:40 +03:00
pokamest c4df9c004b Merge branch 'dev' into service_refact 2021-01-30 15:03:01 +03:00
pokamest b9d4c53eb6 travis macos fix 2021-01-28 14:18:28 +03:00
pokamest 5fdeea0581 travis fix 2021-01-28 13:26:43 +03:00
pokamest e1cd764050 travis fix 2021-01-28 13:11:10 +03:00
pokamest 1e54379cc0 macos travis fix 2021-01-28 12:54:09 +03:00
pokamest ba3f69d206 travis fix 2021-01-27 01:44:32 +03:00
pokamest fcfd4181c7 merge fix 2021-01-27 01:32:36 +03:00
pokamest 9f6ec6d3e2 Merge branch 'master' into gitfix 2021-01-27 00:50:55 +03:00
pokamest 2e50aae603 macos deploy 2021-01-26 22:15:49 +03:00
pokamest 5a16a07b57 travis release 2021-01-26 22:00:58 +03:00
pokamest 953eca6695 Bug fixes:
- auto install tap
- share connectionState
- service crash fix
2021-01-26 15:01:15 +03:00
pokamest a50cdd5bc2 travis fix 2021-01-23 16:45:11 +03:00
pokamest 10eb65e545 travis fix 2021-01-23 16:28:06 +03:00
pokamest 5354344ba7 travis fix 2021-01-23 16:02:21 +03:00
pokamest c000a93eef travis fix 2021-01-23 15:13:46 +03:00
pokamest ef6bc5ae4a travis fix 2021-01-23 15:08:01 +03:00
pokamest 97c4564649 travis fix 2021-01-23 15:03:56 +03:00
pokamest 5bf31a8b50 travis fix 2021-01-23 01:09:27 +03:00
pokamest 18e0d7fb2c travis fix 2021-01-23 00:57:12 +03:00
pokamest ac454d9d78 Server scripts fix 2021-01-22 16:06:43 +03:00
pokamest 806677510f icon changed 2021-01-21 19:16:10 +03:00
pokamest 68e0ba9923 server scripts fix 2021-01-21 19:14:07 +03:00
pokamest 22b33a4f25 remote_obj 2021-01-20 23:07:23 +03:00
pokamest 77f830acd5 travis fix 2021-01-19 09:48:50 +03:00
pokamest eb3b090257 travis fix 2021-01-19 01:58:55 +03:00
pokamest 2e1c219093 travis fix 2021-01-19 01:54:42 +03:00
pokamest e38f69caf0 travis fix 2021-01-19 01:54:27 +03:00
pokamest 795f286923 travis fix 2021-01-19 01:45:47 +03:00
pokamest d62e5b51da travis fix 2021-01-19 01:44:17 +03:00
pokamest 96b267b1f7 travis fix 2021-01-19 01:31:59 +03:00
pokamest 568388367e travis fix 2021-01-19 01:18:23 +03:00
pokamest 5ca46edf33 travis fix 2021-01-19 01:15:05 +03:00
pokamest af41abd346 travis fix 2021-01-19 01:09:41 +03:00
pokamest 5edb61641c travis fix 2021-01-19 00:27:35 +03:00
pokamest af3b1caa87 travis fix 2021-01-18 22:30:32 +03:00
pokamest cc4a7b32b0 travis fix 2021-01-18 22:07:31 +03:00
pokamest dfd61f58d4 travis fix 2021-01-18 21:58:23 +03:00
pokamest 84a3c1f725 travis fix 2021-01-18 19:00:18 +03:00
pokamest 2419fa1a6e travis fix 2021-01-18 18:45:38 +03:00
pokamest 3b8ecd9e9d travis windows fix 2021-01-18 01:41:20 +03:00
pokamest 94ee4b76db travis fix 2021-01-18 01:18:22 +03:00
pokamest d43a1cd753 travis fix 2021-01-18 01:14:04 +03:00
pokamest 8bc6852b39 travis fix 2021-01-17 16:27:33 +03:00
pokamest 3850be08f1 travis fix 2021-01-17 16:22:05 +03:00
pokamest d72d1d56b1 travis fix 2021-01-17 16:15:52 +03:00
pokamest 2a4926843c travis fix 2021-01-17 14:14:26 +03:00
pokamest 1b357055a2 travis fix 2021-01-17 13:53:05 +03:00
pokamest 2d3d64e462 travis fix 2021-01-17 13:17:37 +03:00
pokamest aa648d4e12 travis scripts 2021-01-17 13:11:39 +03:00
pokamest bfa0ac4c34 Macos doc icon 2021-01-16 21:04:16 +03:00
pokamest c37ddd83d5 ui fix 2021-01-16 15:08:27 +03:00
pokamest 96bbc1cdbc ss fix 2021-01-15 23:37:34 +03:00
pokamest ec9ae0ef4f shadowsocks impl 2021-01-15 23:36:35 +03:00
pokamest 95cbb07cbb Merge branch 'dev' into ss/impl 2021-01-12 12:57:58 +03:00
pokamest 7b95d6a5fc ss files 2021-01-11 23:19:30 +03:00
driftingsun bea19a9461 Fix issue with easyrsa 2021-01-11 16:41:17 +03:00
pokamest ba686556d2 Merge branch 'dev' into ss/impl 2021-01-10 20:52:04 +03:00
pokamest 15dc6eaaf8 ss files 2021-01-10 20:37:57 +03:00
driftingsun 3369a387ce Fix crash on fatal error, remove moc files from Windows installation 2021-01-10 18:47:29 +03:00
michael dd46d8cf99 Fix some warnings, fix installation scripts (macOS) 2021-01-10 16:50:38 +03:00
pokamest 0b8c8835c4 new server page fix 2021-01-09 20:05:16 +03:00
pokamest 441811e277 Refactoring
Ui improvements
Bug fixes
2021-01-09 19:55:16 +03:00
pokamest 3645735053 easyrsa scripts for MacOS 2021-01-08 18:10:52 +03:00
pokamest 416d597847 MacOS deploy script fix 2021-01-08 18:08:20 +03:00
pokamest 861c71e3a8 OpenVpn runtime error codes handling 2021-01-08 16:51:58 +03:00
pokamest f45fb442de Scripts fix for macos 2021-01-08 15:43:45 +03:00
pokamest 82241701a9 deploy fix 2021-01-08 00:34:15 +03:00
pokamest bfdbe27a8d Openvpn scripts fixes
some refactoring
2021-01-07 20:53:42 +03:00
pokamest c7dafe9c00 Crash fix in management server 2021-01-07 19:10:24 +03:00
196 changed files with 13743 additions and 3456 deletions

No files matched your search

+2
View File
@@ -3,6 +3,7 @@
macOSPackage/
AmneziaVPN.dmg
AmneziaVPN.exe
AmneziaVPN_*.exe
deploy/build/*
winbuild.bat
@@ -35,6 +36,7 @@ CMakeLists.txt.user*
.DS_Store
._.DS_Store
._*
*.dmg
# tmp files
*.*~
+32 -18
View File
@@ -11,16 +11,30 @@ jobs:
- name: MacOS
os: osx
osx_image: xcode12.2
env:
- QT_VERSION=5.15.1
script:
- |
if [ ! -f $HOME/Qt/5.14.2/clang_64/bin/qmake ]; then \
if [ ! -f $HOME/Qt/$QT_VERSION/clang_64/bin/qmake ]; then \
brew install p7zip && \
pip3 install aqtinstall requests py7zr && \
python3 -m aqt install --outputdir $HOME/Qt 5.14.2 mac desktop clang_64 -m qtbase && \
python3 -m aqt install --outputdir $HOME/Qt $QT_VERSION mac desktop clang_64 -m qtbase && \
python3 -m aqt tool --outputdir $HOME/Qt mac tools_ifw 4.0.1 qt.tools.ifw.40;
fi
- bash deploy/build_macos.sh
- bash deploy/build_macos.sh
deploy:
provider: releases
token: $GH_TOKEN
skip_cleanup: true
file:
- "AmneziaVPN_unsigned.dmg"
on:
tags: true
branch: master
- name: Windows
os: windows
@@ -44,24 +58,24 @@ jobs:
- echo -e "\r\n" >> winbuild.bat
- echo 'call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsamd64_x86.bat"' >> winbuild.bat
- echo -e "\r\n" >> winbuild.bat
- echo -e "deploy\\\build_windows.bat" >> winbuild.bat
- cat winbuild.bat
- echo -e "set WIN_CERT_PW=$WIN_CERT_PW" >> winbuild.bat
- echo -e "\r\n" >> winbuild.bat
- echo -e "call deploy\\\build_windows.bat" >> winbuild.bat
- cmd //c winbuild.bat
deploy:
provider: releases
token: $GH_TOKEN
skip_cleanup: true
file:
- "AmneziaVPN.exe"
on:
tags: true
branch: master
deploy:
provider: releases
api_key: $GH_TOKEN
file:
- "AmneziaVPN.exe"
- "AmneziaVPN.dmg"
skip_cleanup: true
on:
tags: true
branch: master
before_cache:
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew cleanup; fi
# Cache only .git files under "/usr/local/Homebrew" so "brew update" does not take 5min every build
@@ -71,4 +85,4 @@ cache:
directories:
- $HOME/Qt
- /C/Qt
- $HOME/Library/Caches/Homebrew
- $HOME/Library/Caches/Homebrew
+1
View File
@@ -1,2 +1,3 @@
TEMPLATE = subdirs
SUBDIRS = client service platform
+42 -1
View File
@@ -1,2 +1,43 @@
# Amnezia
# Amnezia VPN
## _The best client for self-hosted VPN_
[![Build Status](https://travis-ci.com/amnezia-vpn/desktop-client.svg?branch=master)](https://travis-ci.com/amnezia-vpn/desktop-client)
Amnezia is a VPN client with the key feature of deploying your own VPN server on you virtual server.
## Features
- Very easy to use - enter your ip address, ssh login and password, and Amnezia client will automatically install VPN docker containers to your server and connect to VPN.
- OpenVPN and OpenVPN over ShadowSocks protocols support.
- Custom VPN routing mode support - add any sites to client to enable VPN only for them.
- Windows and MacOS support.
- Unsecure sharing connection profile for family use.
## Tech
AmneziaVPN uses a number of open source projects to work:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [ShadowSocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [EasyRSA](https://github.com/OpenVPN/easy-rsa) - part of OpenVPN
- [CygWin](https://www.cygwin.com/) - only for Windiws, used for launching EasyRSA scripts
- [QtSsh](https://github.com/jaredtao/QtSsh) - forked form Qt Creator
- and more...
## Development
Want to contribute? Welcome!
Use Qt Creator for fast developing.
### Building sources and deployment
Easiest way to build your own executables - is to fork project and configure [Travis CI](https://travis-ci.com/)
Or you can build sources manually using Qt Creator. Qt >= 14.2 supported.
Look to the `build_macos.sh` and `build_windows.bat` scripts in `deploy` folder for details.
## License
GPL v.3
## Contacts
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - main support channel
[https://amnezia.org](https://amnezia.org) - our website
File diff suppressed because it is too large. Load diff
@@ -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_)
@@ -0,0 +1,5 @@
HEADERS += \
3rd/QRCodeGenerator/QRCodeGenerator.h \
SOURCES += \
3rd/QRCodeGenerator/QRCodeGenerator.cpp \
+6 -4
View File
@@ -823,11 +823,13 @@ void SshConnectionPrivate::createPrivateKey()
if (m_connParams.privateKeyFile.isEmpty())
throw SshClientException(SshKeyFileError, tr("No private key file given."));
QFile keyFile(m_connParams.privateKeyFile);
if (!keyFile.open(QIODevice::ReadOnly)) {
throw SshClientException(SshKeyFileError,
tr("Private key file error: %1").arg(keyFile.errorString()));
if (keyFile.open(QIODevice::ReadOnly)) {
m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
else {
m_sendFacility.createAuthenticationKey(m_connParams.privateKeyFile.toUtf8());
}
m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
@@ -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();
}
@@ -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
@@ -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
}
@@ -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;
}
@@ -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
+32 -12
View File
@@ -1,4 +1,4 @@
QT += widgets core gui network xml
QT += widgets core gui network xml remoteobjects
TARGET = AmneziaVPN
TEMPLATE = app
@@ -8,48 +8,65 @@ DEFINES += QT_DEPRECATED_WARNINGS
include("3rd/QtSsh/src/ssh/ssh.pri")
include("3rd/QtSsh/src/botan/botan.pri")
include("3rd/SingleApplication/singleapplication.pri")
include("3rd/QRCodeGenerator/QRCodeGenerator.pri")
HEADERS += \
communicator.h \
../ipc/ipc.h \
configurators/cloak_configurator.h \
configurators/shadowsocks_configurator.h \
configurators/vpn_configurator.h \
core/defs.h \
core/errorstrings.h \
core/openvpnconfigurator.h \
core/ipcclient.h \
configurators/openvpn_configurator.h \
core/scripts_registry.h \
core/server_defs.h \
core/servercontroller.h \
debug.h \
defines.h \
localclient.h \
managementserver.h \
message.h \
protocols/openvpnovercloakprotocol.h \
protocols/protocols_defs.h \
protocols/shadowsocksvpnprotocol.h \
runguard.h \
settings.h \
ui/Controls/SlidingStackedWidget.h \
ui/mainwindow.h \
ui/qautostart.h \
ui/server_widget.h \
utils.h \
vpnconnection.h \
protocols/vpnprotocol.h \
protocols/openvpnprotocol.h \
SOURCES += \
communicator.cpp \
core/openvpnconfigurator.cpp \
configurators/cloak_configurator.cpp \
configurators/shadowsocks_configurator.cpp \
configurators/vpn_configurator.cpp \
core/errorstrings.cpp \
core/ipcclient.cpp \
configurators/openvpn_configurator.cpp \
core/scripts_registry.cpp \
core/server_defs.cpp \
core/servercontroller.cpp \
debug.cpp \
localclient.cpp \
main.cpp \
managementserver.cpp \
message.cpp \
protocols/openvpnovercloakprotocol.cpp \
protocols/protocols_defs.cpp \
protocols/shadowsocksvpnprotocol.cpp \
runguard.cpp \
settings.cpp \
ui/Controls/SlidingStackedWidget.cpp \
ui/mainwindow.cpp \
ui/qautostart.cpp \
ui/server_widget.cpp \
utils.cpp \
vpnconnection.cpp \
protocols/vpnprotocol.cpp \
protocols/openvpnprotocol.cpp \
FORMS += ui/mainwindow.ui
FORMS += ui/mainwindow.ui \
ui/server_widget.ui
RESOURCES += \
resources.qrc
@@ -100,3 +117,6 @@ macx {
LIBS += -framework Cocoa -framework ApplicationServices -framework CoreServices -framework Foundation -framework AppKit
}
REPC_REPLICA += ../ipc/ipcinterface.rep
-79
View File
@@ -1,79 +0,0 @@
#include "communicator.h"
#include "defines.h"
#include "localclient.h"
#include "utils.h"
Communicator::Communicator(QObject* parent) : QObject(parent),
m_localClient(nullptr)
{
connectToServer();
}
Communicator::~Communicator()
{
}
void Communicator::connectToServer()
{
if (m_localClient) {
delete m_localClient;
}
m_localClient = new LocalClient(this);
connect(m_localClient, &LocalClient::connected, this, &Communicator::onConnected);
connect(m_localClient, &LocalClient::lineAvailable, this, &Communicator::onLineAvailable);
m_localClient->connectToServer(Utils::serverName());
}
void Communicator::onConnected()
{
qDebug().noquote() << QString("Connected to local server '%1'").arg(m_localClient->serverName());
Message message(Message::State::Initialize, QStringList({"Client"}));
sendMessage(message);
}
void Communicator::onLineAvailable(const QString& line)
{
Message message(line);
if (!message.isValid()) {
qDebug() << "Message is not valid";
return;
}
emit messageReceived(message);
}
bool Communicator::isConnected() const
{
if (!m_localClient) {
return false;
}
return m_localClient->connectedState();
}
QString Communicator::readData()
{
return QString();
}
bool Communicator::writeData(const QString& data)
{
return m_localClient->write(data.toUtf8());
}
void Communicator::sendMessage(const Message& message)
{
if (!isConnected()) {
return;
}
const QString data = message.toString();
bool status = writeData(data + "\n");
qDebug().noquote() << QString("Send message '%1',%2 status '%2'").
arg(static_cast<int>(message.state())).
arg(data).
arg(Utils::toString(status));
}
-41
View File
@@ -1,41 +0,0 @@
#ifndef COMMUNICATOR_H
#define COMMUNICATOR_H
#include <QObject>
#include <QStringList>
#include "message.h"
class LocalClient;
class Communicator : public QObject
{
Q_OBJECT
public:
explicit Communicator(QObject* parent = nullptr);
~Communicator();
bool isConnected() const;
void sendMessage(const Message& message);
signals:
void messageReceived(const Message& message);
void comminicatorConnected();
void comminicatorDisconnected();
protected slots:
void onConnected();
void onLineAvailable(const QString& line);
protected:
QString readData();
bool writeData(const QString& data);
void connectToServer();
LocalClient* m_localClient;
};
#endif // COMMUNICATOR_H
@@ -0,0 +1,47 @@
#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);
QString textCfg = ServerController::replaceVars(QJsonDocument(config).toJson(),
ServerController::genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef CLOAK_CONFIGURATOR_H
#define CLOAK_CONFIGURATOR_H
#include <QObject>
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class CloakConfigurator
{
public:
static QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // CLOAK_CONFIGURATOR_H
@@ -0,0 +1,281 @@
#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);
config.replace("$OPENVPN_TA_KEY", connData.taKey);
#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", "");
}
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
@@ -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;
}
@@ -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
View File
@@ -0,0 +1,33 @@
#include "vpn_configurator.h"
#include "openvpn_configurator.h"
#include "cloak_configurator.h"
#include "shadowsocks_configurator.h"
//#include "wireguard_configurator.h"
#include <QFile>
#include <QJsonObject>
#include <QJsonDocument>
#include "protocols/protocols_defs.h"
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode)
{
switch (proto) {
case Protocol::OpenVpn:
return OpenVpnConfigurator::genOpenVpnConfig(credentials, container, containerConfig, errorCode);
case Protocol::ShadowSocks:
return ShadowSocksConfigurator::genShadowSocksConfig(credentials, container, containerConfig, errorCode);
case Protocol::Cloak:
return CloakConfigurator::genCloakConfig(credentials, container, containerConfig, errorCode);
// case Protocol::WireGuard:
// return WireGuardConfigurator::genWireGuardConfig(credentials, container, containerConfig, errorCode);
default:
return "";
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef VPN_CONFIGURATOR_H
#define VPN_CONFIGURATOR_H
#include <QObject>
#include "core/defs.h"
#include "settings.h"
#include "core/servercontroller.h"
class VpnConfigurator
{
public:
static QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Protocol proto, ErrorCode *errorCode = nullptr);
};
#endif // VPN_CONFIGURATOR_H
+24 -15
View File
@@ -1,29 +1,19 @@
#ifndef DEFS_H
#define DEFS_H
#include <QMetaEnum>
#include <QObject>
namespace amnezia {
enum class Protocol {
Any,
OpenVpn,
ShadowSocks,
WireGuard
};
enum class DockerContainer {
OpenVpn,
ShadowSocks,
WireGuard
};
struct ServerCredentials
{
QString hostName;
QString userName;
QString password;
int port = 22;
bool isValid() { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
};
enum ErrorCode
@@ -34,8 +24,10 @@ enum ErrorCode
InternalError,
NotImplementedError,
// Server errorz
// Server errors
ServerCheckFailed,
ServerPortAlreadyAllocatedError,
ServerContainerMissingError,
// Ssh connection errors
SshSocketError, SshTimeoutError, SshProtocolError,
@@ -45,6 +37,7 @@ enum ErrorCode
// Ssh remote process errors
SshRemoteProcessCreationError,
FailedToStartRemoteProcessError, RemoteProcessCrashError,
SshSftpError,
// Local errors
FailedToSaveConfigData,
@@ -55,13 +48,29 @@ enum ErrorCode
// Distro errors
OpenVpnExecutableMissing,
EasyRsaExecutableMissing,
ShadowSocksExecutableMissing,
CloakExecutableMissing,
AmneziaServiceConnectionFailed,
// VPN errors
OpenVpnAdaptersInUseError,
OpenVpnUnknownError
OpenVpnUnknownError,
// 3rd party utils errors
OpenVpnExecutableCrashed,
ShadowSocksExecutableCrashed,
CloakExecutableCrashed
};
namespace config {
// config keys
const char key_openvpn_config_data[] = "openvpn_config_data";
const char key_openvpn_config_path[] = "openvpn_config_path";
const char key_shadowsocks_config_data[] = "shadowsocks_config_data";
const char key_cloak_config_data[] = "cloak_config_data";
}
} // namespace amnezia
#endif // DEFS_H
+58
View File
@@ -0,0 +1,58 @@
#include "errorstrings.h"
using namespace amnezia;
QString errorString(ErrorCode code){
switch (code) {
// General error codes
case(NoError): return QObject::tr("No error");
case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): return QObject::tr("Function not implemented");
// Server errors
case(ServerCheckFailed): return QObject::tr("Server check failed");
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
// Ssh connection errors
case(SshSocketError): return QObject::tr("Ssh connection error");
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
case(SshProtocolError): return QObject::tr("Ssh protocol error");
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
case(SshKeyFileError): return QObject::tr("Ssh key file error");
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
case(SshInternalError): return QObject::tr("Ssh internal error");
// Ssh remote process errors
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
// Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case(InternalError):
default:
return QObject::tr("Internal error");
}
}
QDebug operator<<(QDebug debug, const ErrorCode &e)
{
QDebugStateSaver saver(debug);
debug.nospace() << "ErrorCode::" << int(e) << "(" << errorString(e) << ")";
return debug;
}
+5 -43
View File
@@ -1,52 +1,14 @@
#ifndef ERRORSTRINGS_H
#define ERRORSTRINGS_H
#include <QDebug>
#include "defs.h"
using namespace amnezia;
static QString errorString(ErrorCode code){
switch (code) {
// General error codes
case(NoError): return QObject::tr("No error");
case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): return QObject::tr("Function not implemented");
case(ServerCheckFailed): return QObject::tr("Server check failed");
// Ssh connection errors
case(SshSocketError): return QObject::tr("Ssh connection error");
case(SshTimeoutError): return QObject::tr("Ssh connection timeout");
case(SshProtocolError): return QObject::tr("Ssh protocol error");
case(SshHostKeyError): return QObject::tr("Ssh server ket check failed");
case(SshKeyFileError): return QObject::tr("Ssh key file error");
case(SshAuthenticationError): return QObject::tr("Ssh authentication error");
case(SshClosedByServerError): return QObject::tr("Ssh session closed");
case(SshInternalError): return QObject::tr("Ssh internal error");
// Ssh remote process errors
case(SshRemoteProcessCreationError): return QObject::tr("Failed to create remote process on server");
case(FailedToStartRemoteProcessError): return QObject::tr("Failed to start remote process on server");
case(RemoteProcessCrashError): return QObject::tr("Remote process on server crashed");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
case (EasyRsaError): return QObject::tr("EasyRSA runtime error");
// Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (EasyRsaExecutableMissing): return QObject::tr("EasyRsa executable missing");
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case(InternalError):
default:
return QObject::tr("Internal error");
}
}
QString errorString(ErrorCode code);
QDebug operator<<(QDebug debug, const ErrorCode &e);
#endif // ERRORSTRINGS_H
+79
View File
@@ -0,0 +1,79 @@
#include "ipcclient.h"
#include <QRemoteObjectNode>
IpcClient &IpcClient::Instance()
{
static IpcClient s;
return s;
}
bool IpcClient::init()
{
Instance().m_localSocket->waitForConnected();
if (!Instance().m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
return Instance().m_ipcClient->isReplicaValid();
}
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
{
if (! Instance().m_ipcClient || ! Instance().m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance().m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(1000);
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance().m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
pd->ipcProcess.reset(pd->replicaNode->acquire<IpcProcessInterfaceReplica>());
if (!pd->ipcProcess) {
qWarning() << "Acquire IpcProcessInterfaceReplica failed";
}
else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "IpcProcessInterfaceReplica replica is not connected!";
}
connect(pd->ipcProcess.data(), &IpcProcessInterfaceReplica::destroyed, pd->ipcProcess.data(), [pd](){
pd->replicaNode->deleteLater();
});
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto proccessReplica = QSharedPointer<IpcProcessInterfaceReplica>(pd->ipcProcess);
return proccessReplica;
}
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
m_localSocket.reset(new QLocalSocket(this));
connect(m_localSocket.data(), &QLocalSocket::connected, &m_ClientNode, [this]() {
m_ClientNode.addClientSideConnection(m_localSocket.data());
m_ipcClient.reset(m_ClientNode.acquire<IpcInterfaceReplica>());
m_ipcClient->waitForSource(1000);
if (!m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
});
m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef IPCCLIENT_H
#define IPCCLIENT_H
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipcinterface_replica.h"
class IpcClient : public QObject
{
Q_OBJECT
public:
static IpcClient &Instance();
static bool init();
static QSharedPointer<IpcInterfaceReplica> Interface() { return Instance().m_ipcClient; }
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
signals:
private:
explicit IpcClient(QObject *parent = nullptr);
QRemoteObjectNode m_ClientNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QSharedPointer<QLocalSocket> m_localSocket;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<IpcProcessInterfaceReplica>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<IpcProcessInterfaceReplica> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
};
#endif // IPCCLIENT_H
-228
View File
@@ -1,228 +0,0 @@
#include "openvpnconfigurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
#include <QRandomGenerator>
#include <QTemporaryDir>
#include <QDebug>
QString OpenVpnConfigurator::getRandomString(int len)
{
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString;
for(int i=0; i<len; ++i) {
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
QChar nextChar = possibleCharacters.at(index);
randomString.append(nextChar);
}
return randomString;
}
QString OpenVpnConfigurator::getEasyRsaShPath()
{
#ifdef Q_OS_WIN
// easyrsa sh path should looks like
// "/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa"
QString easyRsaShPath = QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\easyrsa";
easyRsaShPath.replace("C:\\", "");
easyRsaShPath.replace("\\", "/");
easyRsaShPath.prepend("/");
//return "\"" + easyRsaShPath + "\"";
return "\"/Program Files (x86)/AmneziaVPN/easyrsa/easyrsa\"";
#else
return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa";
#endif
}
QProcessEnvironment OpenVpnConfigurator::prepareEnv()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnvVar = env.value("PATH");
#ifdef Q_OS_WIN
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\easyrsa\\bin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\i386;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn\\x64;");
#else
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
return env;
}
ErrorCode OpenVpnConfigurator::initPKI(const QString &path)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " init-pki"));
#else
p.setProgram(getEasyRsaShPath());
p.setArguments(QStringList() << "init-pki");
#endif
p.setWorkingDirectory(path);
// QObject::connect(&p, &QProcess::channelReadyRead, [&](){
// qDebug().noquote() << p.readAll();
// });
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
ErrorCode OpenVpnConfigurator::genReq(const QString &path, const QString &clientId)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"sh.exe %1\"").arg(getEasyRsaShPath() + " gen-req " + clientId + " nopass"));
#else
p.setArguments(QStringList() << "gen-req" << clientId << "nopass");
p.setProgram(getEasyRsaShPath());
#endif
p.setWorkingDirectory(path);
QObject::connect(&p, &QProcess::channelReadyRead, [&](){
QString data = p.readAll();
//qDebug().noquote() << data;
if (data.contains("Common Name (eg: your user, host, or server name)")) {
p.write("\n");
}
});
p.start();
p.waitForFinished();
if (p.exitCode() == 0) return ErrorCode::NoError;
else return ErrorCode::EasyRsaError;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
{
OpenVpnConfigurator::ConnectionData connData;
connData.clientId = getRandomString(32);
QTemporaryDir dir;
// if (dir.isValid()) {
// // dir.path() returns the unique directory path
// }
QString path = dir.path();
initPKI(path);
ErrorCode errorCode = genReq(path, connData.clientId);
Q_UNUSED(errorCode)
QFile req(path + "/pki/reqs/" + connData.clientId + ".req");
req.open(QIODevice::ReadOnly);
connData.request = req.readAll();
QFile key(path + "/pki/private/" + connData.clientId + ".key");
key.open(QIODevice::ReadOnly);
connData.privKey = key.readAll();
// qDebug().noquote() << connData.request;
// qDebug().noquote() << connData.privKey;
return connData;
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing;
return connData;
}
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
DockerContainer container;
if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn;
else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks;
else {
if (errorCode) *errorCode = ErrorCode::InternalError;
return connData;
}
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
e = ServerController::signCert(container, credentials, connData.clientId);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::caCertPath(), &e);
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
}
ServerController::setupServerFirewall(credentials);
return connData;
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
Protocol proto, ErrorCode *errorCode)
{
QFile configTemplFile;
if (proto == Protocol::OpenVpn)
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
else if (proto == Protocol::ShadowSocks) {
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
}
configTemplFile.open(QIODevice::ReadOnly);
QString config = configTemplFile.readAll();
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
if (proto == Protocol::OpenVpn)
config.replace("$PROTO", "udp");
else if (proto == Protocol::ShadowSocks) {
config.replace("$PROTO", "tcp");
config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort()));
}
config.replace("$REMOTE_HOST", connData.host);
config.replace("$REMOTE_PORT", "1194");
config.replace("$CA_CERT", connData.caCert);
config.replace("$CLIENT_CERT", connData.clientCert);
config.replace("$PRIV_KEY", connData.privKey);
config.replace("$TA_KEY", connData.taKey);
return config;
}
+68
View File
@@ -0,0 +1,68 @@
#include "scripts_registry.h"
#include <QObject>
#include <QDebug>
#include <QFile>
QString amnezia::scriptFolder(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn: return QLatin1String("openvpn");
case DockerContainer::OpenVpnOverCloak: return QLatin1String("openvpn_cloak");
case DockerContainer::OpenVpnOverShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
default: return "";
}
}
QString amnezia::scriptName(SharedScriptType type)
{
switch (type) {
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
}
}
QString amnezia::scriptName(ProtocolScriptType type)
{
switch (type) {
case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile");
case ProtocolScriptType::run_container: return QLatin1String("run_container.sh");
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
}
}
QString amnezia::scriptData(amnezia::SharedScriptType type)
{
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
QFile file(fileName);
if (! file.open(QIODevice::ReadOnly)) {
qDebug() << "Error opening script" << fileName;
return "";
}
QByteArray ba = file.readAll();
if (ba.isEmpty()) {
qDebug() << "Error, script is empty" << fileName;
}
return ba;
}
QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container)
{
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type));
QFile file(fileName);
if (! file.open(QIODevice::ReadOnly)) {
qDebug() << "Error opening script" << fileName;
return "";
}
QByteArray data = file.readAll();
data.replace("\r", "");
return data;
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef SCRIPTS_REGISTRY_H
#define SCRIPTS_REGISTRY_H
#include <QLatin1String>
#include "core/defs.h"
#include "protocols/protocols_defs.h"
namespace amnezia {
enum SharedScriptType {
// General scripts
prepare_host,
install_docker,
build_container,
remove_container,
remove_all_containers,
setup_host_firewall,
check_connection
};
enum ProtocolScriptType {
// Protocol scripts
dockerfile,
run_container,
configure_container,
container_startup,
openvpn_template
};
QString scriptFolder(DockerContainer container);
QString scriptName(SharedScriptType type);
QString scriptName(ProtocolScriptType type);
QString scriptData(SharedScriptType type);
QString scriptData(ProtocolScriptType type, DockerContainer container);
}
#endif // SCRIPTS_REGISTRY_H
+16
View File
@@ -0,0 +1,16 @@
#include "server_defs.h"
//QString amnezia::containerToString(amnezia::DockerContainer container)
//{
// switch (container) {
// case(DockerContainer::OpenVpn): return "amnezia-openvpn";
// case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak";
// case(DockerContainer::OpenVpnOverShadowSocks): return "amnezia-shadowsocks";
// default: return "";
// }
//}
QString amnezia::server::getDockerfileFolder(amnezia::DockerContainer container)
{
return "/opt/amnezia/" + containerToString(container);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef SERVER_DEFS_H
#define SERVER_DEFS_H
#include <QObject>
#include "protocols/protocols_defs.h"
namespace amnezia {
namespace server {
//QString getContainerName(amnezia::DockerContainer container);
QString getDockerfileFolder(amnezia::DockerContainer container);
}
}
#endif // SERVER_DEFS_H
+475 -178
View File
@@ -1,5 +1,6 @@
#include "servercontroller.h"
#include <QCryptographicHash>
#include <QFile>
#include <QEventLoop>
#include <QLoggingCategory>
@@ -7,28 +8,31 @@
#include <QTimer>
#include <QJsonObject>
#include <QJsonDocument>
#include <QApplication>
#include <QTemporaryFile>
#include "sftpchannel.h"
#include "sshconnectionmanager.h"
#include "protocols/protocols_defs.h"
#include "server_defs.h"
#include "scripts_registry.h"
#include "utils.h"
using namespace QSsh;
QString ServerController::getContainerName(DockerContainer container)
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdOut,
const std::function<void(const QString &, QSharedPointer<SshRemoteProcess>)> &cbReadStdErr)
{
switch (container) {
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
default: return "";
}
}
ErrorCode ServerController::runScript(DockerContainer container,
const SshConnectionParameters &sshParams, QString script)
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
SshConnection *client = connectToHost(sshParams);
if (client->state() != SshConnection::State::Connected) {
if (client->state() == SshConnection::State::Connecting) {
qDebug() << "ServerController::runScript aborted, connectToHost in progress";
return ErrorCode::SshTimeoutError;
}
else if (client->state() != SshConnection::State::Connected) {
qDebug() << "ServerController::runScript connectToHost error: " << fromSshConnectionErrorCode(client->errorState());
return fromSshConnectionErrorCode(client->errorState());
}
@@ -36,17 +40,36 @@ ErrorCode ServerController::runScript(DockerContainer container,
qDebug() << "Run script";
QString totalLine;
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
for (int i = 0; i < lines.count(); i++) {
QString line = lines.at(i);
line.replace("$CONTAINER_NAME", getContainerName(container));
QString currentLine = lines.at(i);
QString nextLine;
if (i + 1 < lines.count()) nextLine = lines.at(i+1);
if (line.startsWith("#")) {
if (totalLine.isEmpty()) {
totalLine = currentLine;
}
else {
totalLine = totalLine + "\n" + currentLine;
}
QString lineToExec;
if (currentLine.endsWith("\\")) {
continue;
}
else {
lineToExec = totalLine;
totalLine.clear();
}
// Run collected line
if (totalLine.startsWith("#")) {
continue;
}
qDebug().noquote() << "EXEC" << line;
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(line.toUtf8());
qDebug().noquote() << "EXEC" << lineToExec;
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
@@ -66,18 +89,20 @@ ErrorCode ServerController::runScript(DockerContainer container,
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, &wait, [proc, cbReadStdOut](){
QString s = proc->readAllStandardOutput();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << s;
qDebug().noquote() << "stdout" << s;
}
if (cbReadStdOut) cbReadStdOut(s, proc);
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
QString s = proc->readAllStandardError();
if (s != "." && !s.isEmpty()) {
qDebug().noquote() << s;
qDebug().noquote() << "stderr" << s;
}
if (cbReadStdErr) cbReadStdErr(s, proc);
});
proc->start();
@@ -95,63 +120,45 @@ ErrorCode ServerController::runScript(DockerContainer container,
}
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
const ServerCredentials &credentials, QString &file, const QString &path)
const ServerCredentials &credentials, const QString &file, const QString &path)
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
if (e) return e;
QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
arg(getContainerName(container)).arg(file).arg(path);
QString stdOut;
auto cbReadStd = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> ) {
stdOut += data + "\n";
};
qDebug().noquote() << script;
e = runScript(sshParams(credentials),
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
if (e) return e;
if (stdOut.contains("Error: No such container:")) {
return ErrorCode::ServerContainerMissingError;
}
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(script.toUtf8());
runScript(sshParams(credentials),
replaceVars(QString("sudo shred %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
if (!proc) {
qCritical() << "Failed to create SshRemoteProcess, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
}
runScript(sshParams(credentials),
replaceVars(QString("sudo rm %1").arg(tmpFileName),
genVarsForScript(credentials, container)));
QEventLoop wait;
int exitStatus = -1;
// QObject::connect(proc.data(), &SshRemoteProcess::started, &wait, [](){
// qDebug() << "Command started";
// });
QObject::connect(proc.data(), &SshRemoteProcess::closed, &wait, [&](int status){
//qDebug() << "Remote process exited with status" << status;
exitStatus = status;
wait.quit();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
qDebug().noquote() << proc->readAllStandardOutput();
});
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
qDebug().noquote() << proc->readAllStandardError();
});
proc->start();
//wait.exec();
if (exitStatus < 0) {
wait.exec();
}
return fromSshProcessExitStatus(exitStatus);
return e;
}
QString ServerController::getTextFileFromContainer(DockerContainer container,
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
{
QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\"").
arg(getContainerName(container)).arg(path);
if (errorCode) *errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"cat \'%2\'\"").
arg(amnezia::containerToString(container)).arg(path);
qDebug().noquote() << "Copy file from container\n" << script;
@@ -195,28 +202,12 @@ QString ServerController::getTextFileFromContainer(DockerContainer container,
return proc->readAllStandardOutput();
}
ErrorCode ServerController::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
{
QString script_import = QString("docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
"easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
.arg(getContainerName(container)).arg(clientId);
QString script_sign = QString("docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
"easyrsa sign-req client %2\"")
.arg(getContainerName(container)).arg(clientId);
QStringList script {script_import, script_sign};
return runScript(container, sshParams(credentials), script.join("\n"));
}
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
{
QString caCert = ServerController::getTextFileFromContainer(container,
credentials, ServerController::caCertPath());
credentials, amnezia::protocols::openvpn::caCertPath);
QString taKey = ServerController::getTextFileFromContainer(container,
credentials, ServerController::taKeyPath());
credentials, amnezia::protocols::openvpn::taKeyPath);
if (!caCert.isEmpty() && !taKey.isEmpty()) {
return ErrorCode::NoError;
@@ -226,6 +217,68 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const
}
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath)
{
SshConnection *client = connectToHost(sshParams(credentials));
if (client->state() != SshConnection::State::Connected) {
return fromSshConnectionErrorCode(client->errorState());
}
bool err = false;
QEventLoop wait;
QTimer timer;
timer.setSingleShot(true);
timer.start(3000);
QSharedPointer<SftpChannel> sftp = client->createSftpChannel();
sftp->initialize();
QObject::connect(sftp.data(), &SftpChannel::initialized, &wait, [&](){
timer.stop();
wait.quit();
});
QObject::connect(&timer, &QTimer::timeout, &wait, [&](){
err= true;
wait.quit();
});
wait.exec();
if (!sftp) {
qCritical() << "Failed to create SftpChannel, breaking.";
return ErrorCode::SshRemoteProcessCreationError;
}
QTemporaryFile localFile;
localFile.open();
localFile.write(data);
localFile.close();
auto job = sftp->uploadFile(localFile.fileName(), remotePath, QSsh::SftpOverwriteMode::SftpOverwriteExisting);
QObject::connect(sftp.data(), &SftpChannel::finished, &wait, [&](QSsh::SftpJobId j, const QString &error){
if (job == j) {
qDebug() << "Sftp finished with status" << error;
wait.quit();
}
});
QObject::connect(sftp.data(), &SftpChannel::channelError, &wait, [&](const QString &reason){
qDebug() << "Sftp finished with error" << reason;
err= true;
wait.quit();
});
wait.exec();
if (err) {
return ErrorCode::SshSftpError;
}
else {
return ErrorCode::NoError;
}
}
ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
{
switch (error) {
@@ -256,127 +309,357 @@ 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;
// e = runScript(sshParams(credentials),
// replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
// genVarsForScript(credentials)));
ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData);
// if (e) return e;
e = prepareHostWorker(credentials, container, config);
if (e) return e;
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
}
removeContainer(credentials, container);
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 = buildContainerWorker(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";
e = runContainerWorker(credentials, container, config);
if (e) return e;
qDebug().noquote() << configData;
configData.replace("\"", "\\\"");
qDebug().noquote() << configData;
e = configureContainerWorker(credentials, container, config);
if (e) return e;
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
return startupContainerWorker(credentials, container, config);
// Start ss
QString script = QString("docker exec -i %1 sh -c \"ss-server -c %2 &\"").
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
return e;
// if (container == DockerContainer::OpenVpn) {
// return setupOpenVpnServer(credentials, config);
// }
// else if (container == DockerContainer::OpenVpnOverShadowSocks) {
// return setupShadowSocksServer(credentials, config);
// }
// else if (container == DockerContainer::OpenVpnOverCloak) {
// return setupOpenVpnOverCloakServer(credentials, config);
// }
// return ErrorCode::NoError;
}
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::setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config)
//{
// return ErrorCode::NotImplementedError;
//}
//ErrorCode ServerController::setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config)
//{
// ErrorCode e = ErrorCode::NoError;
// DockerContainer container = DockerContainer::OpenVpnOverCloak;
// e = prepareHostWorker(credentials, container, config);
// if (e) return e;
// removeContainer(credentials, container);
// e = buildContainerWorker(credentials, container, config);
// if (e) return e;
// e = runContainerWorker(credentials, container, config);
// if (e) return e;
// e = configureContainerWorker(credentials, container, config);
// if (e) return e;
// return startupContainerWorker(credentials, container, config);
//}
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)
{
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
genVarsForScript(credentials, container, config)));
}
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)));
}
//ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config)
//{
// return ErrorCode::NotImplementedError;
//// // 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;
//// 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> proc) {
//// stdOut += data + "\n";
//// };
//// ErrorCode e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), scriptData, cbReadStdOut, cbReadStdErr);
//// if (e) return e;
//// // Create ss config
//// QJsonObject ssConfig;
//// ssConfig.insert("server", "0.0.0.0");
//// ssConfig.insert("server_port", amnezia::protocols::shadowsocks::ssRemotePort());
//// ssConfig.insert("local_port", amnezia::protocols::shadowsocks::ssContainerPort());
//// ssConfig.insert("password", QString(QCryptographicHash::hash(credentials.password.toUtf8(), QCryptographicHash::Sha256).toHex()));
//// ssConfig.insert("timeout", 60);
//// ssConfig.insert("method", amnezia::protocols::shadowsocks::ssEncryption());
//// QString configData = QJsonDocument(ssConfig).toJson();
//// QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
//// configData.replace("\"", "\\\"");
//// //qDebug().noquote() << configData;
//// uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
//// // Start ss
//// QString script = QString("sudo docker exec -d %1 sh -c \"ss-server -c %2\"").
//// arg(amnezia::containerToString(DockerContainer::ShadowSocks)).arg(sSConfigPath);
//// e = runScript(genVarsForScript(credentials, DockerContainer::ShadowSocks), sshParams(credentials), script);
// // return e;
//}
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) }});
// 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", config.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 +710,25 @@ 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);
return runScript(sshParams(credentials),
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
genVarsForScript(credentials, DockerContainer::OpenVpnOverCloak)));
}
QString script = file.readAll();
return runScript(DockerContainer::OpenVpn, sshParams(credentials), script);
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);
}
return s;
}
+36 -20
View File
@@ -1,10 +1,13 @@
#ifndef SERVERCONTROLLER_H
#define SERVERCONTROLLER_H
#include <QJsonObject>
#include <QObject>
#include "sshconnection.h"
#include "sshremoteprocess.h"
#include "defs.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
@@ -12,46 +15,59 @@ 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 setupOpenVpnServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
//static ErrorCode setupOpenVpnOverCloakServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
// static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials, const QJsonObject &config = QJsonObject());
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());
};
+1 -1
View File
@@ -4,6 +4,6 @@
#define APPLICATION_NAME "AmneziaVPN"
#define SERVICE_NAME "AmneziaVPN-service"
#define ORGANIZATION_NAME "AmneziaVPN.ORG"
#define APP_VERSION "1.0.0.0"
#define APP_VERSION "1.6.0.0"
#endif // DEFINES_H
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

-62
View File
@@ -1,62 +0,0 @@
#include <QDebug>
#include <QtNetwork>
#include "localclient.h"
LocalClient::LocalClient(QObject *parent) : QObject(parent),
m_socket(new QLocalSocket(this))
{
m_in.setDevice(m_socket);
m_in.setVersion(QDataStream::Qt_5_10);
connect(m_socket, &QLocalSocket::readyRead, this, &LocalClient::onReadyRead);
connect(m_socket, &QLocalSocket::connected, this, &LocalClient::onConnected);
connect(m_socket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &LocalClient::displayError);
}
void LocalClient::connectToServer(const QString& name)
{
m_blockSize = 0;
m_socket->abort();
m_socket->connectToServer(name);
}
QString LocalClient::serverName() const
{
return m_socket->serverName();
}
void LocalClient::onConnected()
{
emit connected();
}
bool LocalClient::connectedState() const
{
return (m_socket->state() == QLocalSocket::ConnectedState);
}
quint64 LocalClient::write(const QByteArray& data)
{
return m_socket->write(data);
}
void LocalClient::onReadyRead()
{
if (m_socket->canReadLine()) {
char buf[1024];
qint64 lineLength = m_socket->readLine(buf, sizeof(buf));
if (lineLength != -1) {
QString line = buf;
line = line.simplified();
qDebug().noquote() << QString("Read line: '%1'").arg(line);
emit lineAvailable(line);
}
}
}
void LocalClient::displayError(QLocalSocket::LocalSocketError socketError)
{
Q_UNUSED(socketError)
qDebug().noquote() << QString("The following error occurred: %1.").arg(m_socket->errorString());
}
-34
View File
@@ -1,34 +0,0 @@
#ifndef LOCALCLIENT_H
#define LOCALCLIENT_H
#include <QDataStream>
#include <QLocalSocket>
class LocalClient : public QObject
{
Q_OBJECT
public:
explicit LocalClient(QObject *parent = nullptr);
QString serverName() const;
bool connectedState() const;
quint64 write(const QByteArray& data);
void connectToServer(const QString& name);
signals:
void connected();
void lineAvailable(const QString& line);
private slots:
void displayError(QLocalSocket::LocalSocketError socketError);
void onConnected();
void onReadyRead();
private:
QLocalSocket* m_socket;
QDataStream m_in;
quint32 m_blockSize;
};
#endif // LOCALCLIENT_H
+33 -8
View File
@@ -3,13 +3,19 @@
#include <QCommandLineParser>
#include <QMessageBox>
#include <QTranslator>
#include <QTimer>
#include <QLoggingCategory>
#include "debug.h"
#include "defines.h"
#include "runguard.h"
#include "singleapplication.h"
#include "ui/mainwindow.h"
#ifdef Q_OS_WIN
#include "Windows.h"
#endif
static void loadTranslator()
{
QTranslator* translator = new QTranslator;
@@ -20,17 +26,27 @@ static void loadTranslator()
int main(int argc, char *argv[])
{
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
RunGuard::instance(APPLICATION_NAME).activate();
QApplication app(argc, argv);
loadTranslator();
#ifdef Q_OS_WIN
AllowSetForegroundWindow(ASFW_ANY);
#endif
if (!RunGuard::instance().tryToRun()) {
qDebug() << "Tried to run second instance. Exiting...";
QMessageBox::information(NULL, QObject::tr("Notification"), QObject::tr("AmneziaVPN is already running."));
return 0;
SingleApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification);
if (!app.isPrimary()) {
QTimer::singleShot(1000, &app, [&](){
app.quit();
});
return app.exec();
}
#ifdef Q_OS_WIN
AllowSetForegroundWindow(0);
#endif
loadTranslator();
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf");
@@ -65,5 +81,14 @@ int main(int argc, char *argv[])
MainWindow mainWindow;
mainWindow.show();
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();
}
+7 -8
View File
@@ -22,7 +22,7 @@ bool ManagementServer::isOpen() const
void ManagementServer::stop()
{
m_tcpServer->close();
if (m_tcpServer) m_tcpServer->close();
}
void ManagementServer::onAcceptError(QAbstractSocket::SocketError socketError)
@@ -37,7 +37,9 @@ qint64 ManagementServer::writeCommand(const QString& message)
}
const QString command = message + "\n";
return m_socket->write(command.toStdString().c_str());
qint64 bytesWritten = m_socket->write(command.toStdString().c_str());
m_socket->flush();
return bytesWritten;
}
void ManagementServer::onNewConnection()
@@ -45,7 +47,7 @@ void ManagementServer::onNewConnection()
qDebug() << "New incoming connection";
m_socket = QPointer<QTcpSocket>(m_tcpServer->nextPendingConnection());
m_tcpServer->close();
if (m_tcpServer) m_tcpServer->close();
QObject::connect(m_socket.data(), SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
QObject::connect(m_socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
@@ -54,8 +56,7 @@ void ManagementServer::onNewConnection()
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
{
Q_UNUSED(socketError);
Q_UNUSED(socketError)
qDebug().noquote() << QString("Mananement server error: %1").arg(m_socket->errorString());
}
@@ -79,9 +80,7 @@ void ManagementServer::onReadyRead()
bool ManagementServer::start(const QString& host, unsigned int port)
{
if (m_tcpServer) {
m_tcpServer->deleteLater();
}
if (m_tcpServer) m_tcpServer->close();
m_tcpServer = QSharedPointer<QTcpServer>(new QTcpServer(this), [](QTcpServer *s){
if (s) s->deleteLater();
-106
View File
@@ -1,106 +0,0 @@
#include "message.h"
Message::Message(State state, const QStringList& args) :
m_valid(true),
m_state(state),
m_args(args)
{
}
bool Message::isValid() const
{
return m_valid;
}
QString Message::textState() const
{
switch (m_state) {
case State::Unknown: return "Unknown";
case State::Initialize: return "Initialize";
case State::StartRequest: return "StartRequest";
case State::Started: return "Started";
case State::FinishRequest: return "FinishRequest";
case State::Finished: return "Finished";
case State::RoutesAddRequest: return "RoutesAddRequest";
case State::RouteDeleteRequest: return "RouteDeleteRequest";
case State::ClearSavedRoutesRequest: return "ClearSavedRoutesRequest";
case State::FlushDnsRequest: return "FlushDnsRequest";
case State::InstallDriverRequest: return "InstallDriverRequest";
default:
;
}
return QString();
}
QString Message::rawData() const
{
return m_rawData;
}
Message::State Message::state() const
{
return m_state;
}
QString Message::toString() const
{
if (!isValid()) {
return QString();
}
return QString("%1%2%3")
.arg(textState())
.arg(m_dataSeparator)
.arg(argsToString());
}
QString Message::argAtIndex(int index) const
{
if ((index + 1) > args().size()) {
return QString();
}
return args().at(index);
}
QStringList Message::args() const
{
return m_args;
}
QString Message::argsToString() const
{
return m_args.join(m_argSeparator);
}
Message::Message(const QString& data)
{
m_rawData = data;
m_valid = false;
if (data.isEmpty()) {
return;
}
QStringList dataList = data.split(m_dataSeparator);
if ((dataList.size() != 2)) {
return;
}
bool stateFound = false;
for (int i = static_cast<int>(State::Unknown); i <= static_cast<int>(State::InstallDriverRequest); i++ ) {
m_state = static_cast<State>(i);
if (textState() == dataList.at(0)) {
stateFound = true;
break;
}
}
if (!stateFound) {
return;
}
m_args = dataList.at(1).split(m_argSeparator);
m_valid = true;
}
-34
View File
@@ -1,34 +0,0 @@
#ifndef MESSAGE_H
#define MESSAGE_H
#include <QStringList>
class Message {
public:
enum class State {Unknown, Initialize, StartRequest, Started, FinishRequest, Finished,
RoutesAddRequest, RouteDeleteRequest, ClearSavedRoutesRequest, FlushDnsRequest, InstallDriverRequest};
Message(State state, const QStringList& args);
Message(const QString& data);
QString argAtIndex(int index) const;
QString argsToString() const;
QString toString() const;
QStringList args() const;
State state() const;
bool isValid() const;
QString rawData() const;
protected:
QString textState() const;
const QString m_argSeparator = ",";
const QString m_dataSeparator = "|";
bool m_valid;
State m_state;
QStringList m_args;
QString m_rawData;
};
#endif // MESSAGE_H
+4 -4
View File
@@ -3,11 +3,11 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
IDI_ICON1 ICON "../images/app.ico"
#define VER_FILEVERSION 1,1,1,1
#define VER_FILEVERSION_STR "1.1.1.1\0"
#define VER_FILEVERSION 1,6,0,0
#define VER_FILEVERSION_STR "1.6.0.0\0"
#define VER_PRODUCTVERSION 1,1,1,1
#define VER_PRODUCTVERSION_STR "1.1.1.1\0"
#define VER_PRODUCTVERSION 1,6,0,0
#define VER_PRODUCTVERSION_STR "1.6.0.0\0"
#define VER_COMPANYNAME_STR "AmneziaVPN"
#define VER_FILEDESCRIPTION_STR "AmneziaVPN"
@@ -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" << 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();
}
@@ -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
+145 -126
View File
@@ -4,84 +4,88 @@
#include <QRegularExpression>
#include <QTcpSocket>
#include "communicator.h"
#include "debug.h"
#include "openvpnprotocol.h"
#include "utils.h"
OpenVpnProtocol::OpenVpnProtocol(const QString& args, QObject* parent) :
VpnProtocol(args, parent),
m_requestFromUserToStop(false)
OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
setConfigFile(args);
connect(m_communicator, &Communicator::messageReceived, this, &OpenVpnProtocol::onMessageReceived);
readOpenVpnConfiguration(configuration);
connect(&m_managementServer, &ManagementServer::readyRead, this, &OpenVpnProtocol::onReadyReadDataFromManagementServer);
}
OpenVpnProtocol::~OpenVpnProtocol()
{
qDebug() << "OpenVpnProtocol::stop()";
qDebug() << "OpenVpnProtocol::~OpenVpnProtocol()";
OpenVpnProtocol::stop();
}
void OpenVpnProtocol::onMessageReceived(const Message& message)
{
if (!message.isValid()) {
qWarning().noquote() << QString("Message received: '%1', but it is not valid").arg(message.toString());
return;
}
switch (message.state()) {
case Message::State::Started:
qDebug() << "OpenVPN process started";
break;
case Message::State::Finished:
qDebug().noquote() << QString("OpenVPN process finished with status %1").arg(message.argAtIndex(1));
onOpenVpnProcessFinished(message.argAtIndex(1).toInt());
break;
default:
qDebug().noquote() << QString("Message received: '%1'").arg(message.toString());
;
}
QThread::msleep(200);
}
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
// TODO: need refactoring
// sendTermSignal() will evet return true while server connected
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == VpnProtocol::ConnectionState::Preparing) ||
(m_connectionState == VpnProtocol::ConnectionState::Connecting) ||
(m_connectionState == VpnProtocol::ConnectionState::Connected) ||
(m_connectionState == VpnProtocol::ConnectionState::TunnelReconnecting)) {
(m_connectionState == VpnProtocol::ConnectionState::Reconnecting)) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
m_managementServer.stop();
qApp->processEvents();
setConnectionState(VpnProtocol::ConnectionState::Disconnecting);
}
}
void OpenVpnProtocol::killOpenVpnProcess()
ErrorCode OpenVpnProtocol::checkAndSetupTapDriver()
{
// send command to kill openvpn process (if any).
if (!IpcClient::Interface()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
QRemoteObjectPendingReply<QStringList> resultCheck = IpcClient::Interface()->getTapList();
resultCheck.waitForFinished();
if (resultCheck.returnValue().isEmpty()){
QRemoteObjectPendingReply<bool> resultInstall = IpcClient::Interface()->checkAndInstallDriver();
resultInstall.waitForFinished();
if (!resultInstall.returnValue()) return ErrorCode::OpenVpnUnknownError;
}
return ErrorCode::NoError;
}
bool OpenVpnProtocol::setConfigFile(const QString& configFileNamePath)
void OpenVpnProtocol::killOpenVpnProcess()
{
m_configFileName = configFileNamePath;
QFileInfo file(m_configFileName);
if (file.fileName().isEmpty()) {
m_configFileName = Utils::defaultVpnConfigFileName();
if (m_openVpnProcess){
m_openVpnProcess->close();
}
}
if (m_configFileName.isEmpty()) {
return false;
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{
if (configuration.contains(config::key_openvpn_config_data)) {
m_configFile.open();
m_configFile.write(configuration.value(config::key_openvpn_config_data).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName;
}
else if (configuration.contains(config::key_openvpn_config_path)) {
m_configFileName = configuration.value(config::key_openvpn_config_path).toString();
QFileInfo file(m_configFileName);
qDebug().noquote() << QString("Set config file: '%1'").arg(configPath());
if (file.fileName().isEmpty()) {
m_configFileName = Utils::defaultVpnConfigFileName();
}
return false;
qDebug().noquote() << QString("Set config file: '%1'").arg(configPath());
}
}
bool OpenVpnProtocol::openVpnProcessIsRunning() const
@@ -99,17 +103,18 @@ QString OpenVpnProtocol::configPath() const
return m_configFileName;
}
void OpenVpnProtocol::writeCommand(const QString& command)
void OpenVpnProtocol::sendManagementCommand(const QString& command)
{
QIODevice *device = dynamic_cast<QIODevice*>(m_managementServer.socket().data());
if (device) {
QTextStream stream(device);
stream << command << endl;
stream << command << Qt::endl;
}
}
void OpenVpnProtocol::updateRouteGateway(QString line)
{
// TODO: fix for macos
line = line.split("ROUTE_GATEWAY", QString::SkipEmptyParts).at(1);
if (!line.contains("/")) return;
m_routeGateway = line.split("/", QString::SkipEmptyParts).first();
@@ -128,17 +133,9 @@ QString OpenVpnProtocol::openVpnExecPath() const
ErrorCode OpenVpnProtocol::start()
{
qDebug() << "Start OpenVPN connection";
m_requestFromUserToStop = false;
m_openVpnStateSigTermHandlerTimer.stop();
//qDebug() << "Start OpenVPN connection";
OpenVpnProtocol::stop();
if (communicator() && !communicator()->isConnected()) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return lastError();
}
if (!QFileInfo::exists(openVpnExecPath())) {
setLastError(ErrorCode::OpenVpnExecutableMissing);
return lastError();
@@ -152,39 +149,55 @@ ErrorCode OpenVpnProtocol::start()
QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log";
Utils::createEmptyFile(vpnLogFileNamePath);
QStringList args({openVpnExecPath(),
"--config" , configPath(),
"--management", m_managementHost, QString::number(m_managementPort),
"--management-client",
"--log-append", vpnLogFileNamePath
});
if (!m_managementServer.start(m_managementHost, m_managementPort)) {
setLastError(ErrorCode::OpenVpnManagementServerError);
return lastError();
}
setConnectionState(ConnectionState::Connecting);
m_communicator->sendMessage(Message(Message::State::StartRequest, args));
startTimeoutTimer();
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
if (!m_openVpnProcess) {
//qWarning() << "IpcProcess replica is not created!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->waitForSource(1000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(openVpnExecPath());
QStringList arguments({"--config" , configPath(),
"--management", m_managementHost, QString::number(m_managementPort),
"--management-client",
"--log-append", vpnLogFileNamePath
});
m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred, [&](QProcess::ProcessError error) {
qDebug() << "IpcProcessInterfaceReplica errorOccurred" << error;
});
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged, [&](QProcess::ProcessState newState) {
qDebug() << "IpcProcessInterfaceReplica stateChanged" << newState;
});
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this, [&]() {
setConnectionState(ConnectionState::Disconnected);
});
m_openVpnProcess->start();
//startTimeoutTimer();
return ErrorCode::NoError;
}
void OpenVpnProtocol::openVpnStateSigTermHandlerTimerEvent()
{
bool processStatus = openVpnProcessIsRunning();
if (processStatus) {
killOpenVpnProcess();
}
onOpenVpnProcessFinished(0);
}
void OpenVpnProtocol::openVpnStateSigTermHandler()
{
m_openVpnStateSigTermHandlerTimer.start(5000);
}
bool OpenVpnProtocol::sendTermSignal()
{
return m_managementServer.writeCommand("signal SIGTERM");
@@ -220,14 +233,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
if (line.contains("CONNECTED,SUCCESS")) {
sendByteCount();
stopTimeoutTimer();
updateVpnGateway();
setConnectionState(VpnProtocol::ConnectionState::Connected);
continue;
} else if (line.contains("EXITING,SIGTER")) {
openVpnStateSigTermHandler();
//openVpnStateSigTermHandler();
setConnectionState(VpnProtocol::ConnectionState::Disconnecting);
continue;
} else if (line.contains("RECONNECTING")) {
setConnectionState(VpnProtocol::ConnectionState::TunnelReconnecting);
setConnectionState(VpnProtocol::ConnectionState::Reconnecting);
continue;
}
}
@@ -236,6 +249,10 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
updateRouteGateway(line);
}
if (line.contains("PUSH: Received control message")) {
updateVpnGateway(line);
}
if (line.contains("FATAL")) {
if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) {
emit protocolError(ErrorCode::OpenVpnAdaptersInUseError);
@@ -262,59 +279,61 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
}
}
void OpenVpnProtocol::onOpenVpnProcessFinished(int exitCode)
void OpenVpnProtocol::updateVpnGateway(const QString &line)
{
m_openVpnStateSigTermHandlerTimer.stop();
if (m_connectionState == VpnProtocol::ConnectionState::Disconnected) {
qDebug() << "Already in disconnected state";
return;
}
// line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
qDebug().noquote() << QString("Process finished with code: %1").arg(exitCode);
QStringList params = line.split(",");
for (const QString &l : params) {
if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) {
m_vpnAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2);
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
}
void OpenVpnProtocol::updateVpnGateway()
{
QProcess ipconfig;
ipconfig.start("ipconfig", QStringList() << "/all");
ipconfig.waitForStarted();
ipconfig.waitForFinished();
QString d = ipconfig.readAll();
d.replace("\r", "");
//qDebug().noquote() << d;
QStringList adapters = d.split(":\n");
bool isTapV9Present = false;
QString tapV9;
for (int i = 0; i < adapters.size(); ++i) {
if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
isTapV9Present = true;
tapV9 = adapters.at(i);
break;
qDebug() << QString("Set vpn address %1, gw %2").arg(m_vpnAddress).arg(vpnGateway());
}
}
}
if (!isTapV9Present) {
m_vpnGateway = "";
}
QStringList lines = tapV9.split("\n");
for (int i = 0; i < lines.size(); ++i) {
if (!lines.at(i).contains("DHCP")) continue;
// QProcess ipconfig;
// ipconfig.start("ipconfig", QStringList() << "/all");
// ipconfig.waitForStarted();
// ipconfig.waitForFinished();
QRegularExpression re("(: )([\\d\\.]+)($)");
QRegularExpressionMatch match = re.match(lines.at(i));
// QString d = ipconfig.readAll();
// d.replace("\r", "");
// //qDebug().noquote() << d;
if (match.hasMatch()) {
qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
m_vpnGateway = match.captured(2);
return;
}
else continue;
}
// QStringList adapters = d.split(":\n");
m_vpnGateway = "";
// bool isTapV9Present = false;
// QString tapV9;
// for (int i = 0; i < adapters.size(); ++i) {
// if (adapters.at(i).contains("TAP-Windows Adapter V9")) {
// isTapV9Present = true;
// tapV9 = adapters.at(i);
// break;
// }
// }
// if (!isTapV9Present) {
// m_vpnGateway = "";
// }
// QStringList lines = tapV9.split("\n");
// for (int i = 0; i < lines.size(); ++i) {
// if (!lines.at(i).contains("DHCP")) continue;
// QRegularExpression re("(: )([\\d\\.]+)($)");
// QRegularExpressionMatch match = re.match(lines.at(i));
// if (match.hasMatch()) {
// qDebug().noquote() << "Current VPN Gateway IP Address: " << match.captured(0);
// m_vpnGateway = match.captured(2);
// return;
// }
// else continue;
// }
// m_vpnGateway = "";
}
+12 -13
View File
@@ -6,51 +6,50 @@
#include <QTimer>
#include "managementserver.h"
#include "message.h"
#include "vpnprotocol.h"
#include "core/ipcclient.h"
class OpenVpnProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit OpenVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
explicit OpenVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~OpenVpnProtocol() override;
ErrorCode start() override;
void stop() override;
ErrorCode checkAndSetupTapDriver();
protected slots:
void onMessageReceived(const Message& message);
void onOpenVpnProcessFinished(int exitCode);
void onReadyReadDataFromManagementServer();
protected:
private:
QString configPath() const;
QString openVpnExecPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();
bool setConfigFile(const QString& configFileNamePath);
void readOpenVpnConfiguration(const QJsonObject &configuration);
void disconnectFromManagementServer();
void killOpenVpnProcess();
void openVpnStateSigTermHandler();
void openVpnStateSigTermHandlerTimerEvent();
void sendByteCount();
void sendInitialData();
void writeCommand(const QString& command);
void sendManagementCommand(const QString& command);
const QString m_managementHost = "127.0.0.1";
const unsigned int m_managementPort = 57775;
ManagementServer m_managementServer;
QString m_configFileName;
QTimer m_openVpnStateSigTermHandlerTimer;
bool m_requestFromUserToStop;
QTemporaryFile m_configFile;
private:
void updateRouteGateway(QString line);
void updateVpnGateway();
void updateVpnGateway(const QString &line);
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
};
#endif // OPENVPNPROTOCOL_H
+80
View File
@@ -0,0 +1,80 @@
#include "protocols_defs.h"
QDebug operator<<(QDebug debug, const amnezia::Protocol &p)
{
QDebugStateSaver saver(debug);
debug.nospace() << protoToString(p);
return debug;
}
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
{
QDebugStateSaver saver(debug);
debug.nospace() << containerToString(c);
return debug;
}
amnezia::Protocol amnezia::protoFromString(QString proto){
if (proto == config_key::openvpn) return Protocol::OpenVpn;
if (proto == config_key::cloak) return Protocol::Cloak;
if (proto == config_key::shadowsocks) return Protocol::ShadowSocks;
if (proto == config_key::wireguard) return Protocol::WireGuard;
return Protocol::Any;
}
QString amnezia::protoToString(amnezia::Protocol proto){
switch (proto) {
case(Protocol::OpenVpn): return config_key::openvpn;
case(Protocol::Cloak): return config_key::cloak;
case(Protocol::ShadowSocks): return config_key::shadowsocks;
case(Protocol::WireGuard): return config_key::wireguard;
default: return "";
}
}
amnezia::DockerContainer amnezia::containerFromString(const QString &container){
if (container == config_key::amnezia_openvpn) return DockerContainer::OpenVpn;
if (container == config_key::amnezia_openvpn_cloak) return DockerContainer::OpenVpnOverCloak;
if (container == config_key::amnezia_shadowsocks) return DockerContainer::OpenVpnOverShadowSocks;
if (container == config_key::amnezia_wireguard) return DockerContainer::WireGuard;
return DockerContainer::None;
}
QString amnezia::containerToString(amnezia::DockerContainer container){
switch (container) {
case(DockerContainer::OpenVpn): return config_key::amnezia_openvpn;
case(DockerContainer::OpenVpnOverCloak): return config_key::amnezia_openvpn_cloak;
case(DockerContainer::OpenVpnOverShadowSocks): return config_key::amnezia_shadowsocks;
case(DockerContainer::WireGuard): return config_key::amnezia_wireguard;
default: return "none";
}
}
QVector<amnezia::Protocol> amnezia::allProtocols()
{
return QVector<amnezia::Protocol> {
Protocol::OpenVpn,
Protocol::ShadowSocks,
Protocol::Cloak,
Protocol::WireGuard
};
}
QVector<amnezia::Protocol> amnezia::protocolsForContainer(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn:
return { Protocol::OpenVpn };
case DockerContainer::OpenVpnOverShadowSocks:
return { Protocol::OpenVpn, Protocol::ShadowSocks };
case DockerContainer::OpenVpnOverCloak:
return { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak };
default:
return {};
}
}
+131
View File
@@ -0,0 +1,131 @@
#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 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 char ncpDisableString[] = "ncp-disable";
}
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
+63 -29
View File
@@ -1,52 +1,93 @@
#include "shadowsocksvpnprotocol.h"
#include "core/servercontroller.h"
#include "communicator.h"
#include "debug.h"
#include "utils.h"
#include "protocols/protocols_defs.h"
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QJsonObject>
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QString &args, QObject *parent):
OpenVpnProtocol(args, parent)
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QJsonObject &configuration, QObject *parent):
OpenVpnProtocol(configuration, parent)
{
m_shadowSocksConfig = args;
readShadowSocksConfiguration(configuration);
}
ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol()
{
qDebug() << "ShadowSocksVpnProtocol::~ShadowSocksVpnProtocol";
ShadowSocksVpnProtocol::stop();
QThread::msleep(200);
m_ssProcess.close();
}
ErrorCode ShadowSocksVpnProtocol::start()
{
qDebug() << "ShadowSocksVpnProtocol::start()";
QJsonObject config = QJsonDocument::fromJson(m_shadowSocksConfig.toUtf8()).object();
if (Utils::processIsRunning(Utils::executable("ss-local", false))) {
Utils::killProcessByName(Utils::executable("ss-local", false));
}
ssProcess.setProcessChannelMode(QProcess::MergedChannels);
#ifdef QT_DEBUG
m_shadowSocksCfgFile.setAutoRemove(false);
#endif
m_shadowSocksCfgFile.open();
m_shadowSocksCfgFile.write(QJsonDocument(m_shadowSocksConfig).toJson());
m_shadowSocksCfgFile.close();
ssProcess.setProgram(shadowSocksExecPath());
ssProcess.setArguments(QStringList() << "-s" << config.value("server").toString()
<< "-p" << QString::number(config.value("server_port").toInt())
<< "-l" << QString::number(config.value("local_port").toInt())
<< "-m" << config.value("method").toString()
<< "-k" << config.value("password").toString()
);
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName()
<< "--no-delay";
ssProcess.start();
ssProcess.waitForStarted();
qDebug().noquote() << "ShadowSocksVpnProtocol::start()"
<< shadowSocksExecPath() << args.join(" ");
if (ssProcess.state() == QProcess::ProcessState::Running) {
m_ssProcess.setProcessChannelMode(QProcess::MergedChannels);
m_ssProcess.setProgram(shadowSocksExecPath());
m_ssProcess.setArguments(args);
connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this](){
qDebug().noquote() << "ss-local:" << m_ssProcess.readAllStandardOutput();
});
connect(&m_ssProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::ConnectionState::Disconnected);
if (exitStatus != QProcess::NormalExit){
emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed);
stop();
}
if (exitCode !=0 ){
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
});
m_ssProcess.start();
m_ssProcess.waitForStarted();
if (m_ssProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
return OpenVpnProtocol::start();
}
else return ErrorCode::FailedToStartRemoteProcessError;
else return ErrorCode::ShadowSocksExecutableMissing;
}
void ShadowSocksVpnProtocol::stop()
{
OpenVpnProtocol::stop();
qDebug() << "ShadowSocksVpnProtocol::stop()";
ssProcess.kill();
m_ssProcess.terminate();
#ifdef Q_OS_WIN
Utils::signalCtrl(m_ssProcess.processId(), CTRL_C_EVENT);
#endif
}
QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
QString ShadowSocksVpnProtocol::shadowSocksExecPath()
{
#ifdef Q_OS_WIN
return Utils::executable(QString("ss/ss-local"), true);
@@ -55,14 +96,7 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() const
#endif
}
QString ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto)
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
{
QJsonObject ssConfig;
ssConfig.insert("server", credentials.hostName);
ssConfig.insert("server_port", ServerController::ssRemotePort());
ssConfig.insert("local_port", ServerController::ssContainerPort());
ssConfig.insert("password", credentials.password);
ssConfig.insert("timeout", 60);
ssConfig.insert("method", ServerController::ssEncryption());
return QJsonDocument(ssConfig).toJson();
m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data).toObject();
}
+11 -7
View File
@@ -3,25 +3,29 @@
#include "openvpnprotocol.h"
#include "QProcess"
#include "protocols/protocols_defs.h"
class ShadowSocksVpnProtocol : public OpenVpnProtocol
{
public:
ShadowSocksVpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
ShadowSocksVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~ShadowSocksVpnProtocol() override;
ErrorCode start() override;
void stop() override;
static QString genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocks);
protected:
void readShadowSocksConfiguration(const QJsonObject &configuration);
protected:
QString shadowSocksExecPath() const;
protected:
QString m_shadowSocksConfig;
QJsonObject m_shadowSocksConfig;
private:
QProcess ssProcess;
static QString shadowSocksExecPath();
private:
QProcess m_ssProcess;
QTemporaryFile m_shadowSocksCfgFile;
};
#endif // SHADOWSOCKSVPNPROTOCOL_H
+11 -22
View File
@@ -1,42 +1,26 @@
#include <QDebug>
#include <QTimer>
#include "communicator.h"
#include "vpnprotocol.h"
#include "core/errorstrings.h"
Communicator* VpnProtocol::m_communicator = nullptr;
VpnProtocol::VpnProtocol(const QString& args, QObject* parent)
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent)
: QObject(parent),
m_connectionState(ConnectionState::Unknown),
m_rawConfig(configuration),
m_timeoutTimer(new QTimer(this)),
m_receivedBytes(0),
m_sentBytes(0)
{
m_timeoutTimer->setSingleShot(true);
connect(m_timeoutTimer, &QTimer::timeout, this, &VpnProtocol::onTimeout);
Q_UNUSED(args)
}
void VpnProtocol::initializeCommunicator(QObject* parent)
{
if (!m_communicator) {
m_communicator = new Communicator(parent);
}
}
Communicator* VpnProtocol::communicator()
{
return m_communicator;
}
void VpnProtocol::setLastError(ErrorCode lastError)
{
m_lastError = lastError;
if (lastError){
setConnectionState(ConnectionState::Disconnected);
setConnectionState(ConnectionState::Error);
}
qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError);
}
@@ -79,9 +63,14 @@ void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes)
void VpnProtocol::setConnectionState(VpnProtocol::ConnectionState state)
{
qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state);
if (m_connectionState == state) {
return;
}
if (m_connectionState == ConnectionState::Disconnected && state == ConnectionState::Disconnecting) {
return;
}
m_connectionState = state;
if (m_connectionState == ConnectionState::Disconnected) {
@@ -113,7 +102,7 @@ QString VpnProtocol::textConnectionState(ConnectionState connectionState)
case ConnectionState::Connecting: return tr("Connecting...");
case ConnectionState::Connected: return tr("Connected");
case ConnectionState::Disconnecting: return tr("Disconnecting...");
case ConnectionState::TunnelReconnecting: return tr("Reconnecting...");
case ConnectionState::Reconnecting: return tr("Reconnecting...");
case ConnectionState::Error: return tr("Error");
default:
;
@@ -127,12 +116,12 @@ QString VpnProtocol::textConnectionState() const
return textConnectionState(m_connectionState);
}
bool VpnProtocol::onConnected() const
bool VpnProtocol::isConnected() const
{
return m_connectionState == ConnectionState::Connected;
}
bool VpnProtocol::onDisconnected() const
bool VpnProtocol::isDisconnected() const
{
return m_connectionState == ConnectionState::Disconnected;
}
+9 -10
View File
@@ -3,30 +3,29 @@
#include <QObject>
#include <QString>
#include <QJsonObject>
#include "core/defs.h"
using namespace amnezia;
class QTimer;
class Communicator;
class VpnProtocol : public QObject
{
Q_OBJECT
public:
explicit VpnProtocol(const QString& args = QString(), QObject* parent = nullptr);
explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~VpnProtocol() override = default;
enum class ConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, TunnelReconnecting, Error};
enum ConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, Reconnecting, Error};
Q_ENUM(ConnectionState)
static Communicator* communicator();
static QString textConnectionState(ConnectionState connectionState);
static void initializeCommunicator(QObject* parent = nullptr);
virtual bool onConnected() const;
virtual bool onDisconnected() const;
virtual bool isConnected() const;
virtual bool isDisconnected() const;
virtual ErrorCode start() = 0;
virtual void stop() = 0;
@@ -54,18 +53,18 @@ protected:
virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
virtual void setConnectionState(VpnProtocol::ConnectionState state);
static Communicator* m_communicator;
ConnectionState m_connectionState;
QString m_routeGateway;
QString m_vpnAddress;
QString m_vpnGateway;
QJsonObject m_rawConfig;
private:
QTimer* m_timeoutTimer;
ErrorCode m_lastError;
quint64 m_receivedBytes;
quint64 m_sentBytes;
};
#endif // VPNPROTOCOL_H
+26 -5
View File
@@ -34,11 +34,32 @@
<file>images/server_settings.png</file>
<file>images/share.png</file>
<file>server_scripts/remove_container.sh</file>
<file>server_scripts/setup_openvpn_server.sh</file>
<file>server_scripts/template_openvpn.ovpn</file>
<file>images/background_connected.png</file>
<file>server_scripts/setup_shadowsocks_server.sh</file>
<file>server_scripts/template_shadowsocks.ovpn</file>
<file>server_scripts/setup_firewall.sh</file>
<file>server_scripts/setup_host_firewall.sh</file>
<file>images/reload.png</file>
<file>server_scripts/openvpn_cloak/Dockerfile</file>
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
<file>server_scripts/openvpn_cloak/start.sh</file>
<file>server_scripts/openvpn_cloak/template.ovpn</file>
<file>server_scripts/install_docker.sh</file>
<file>server_scripts/build_container.sh</file>
<file>server_scripts/prepare_host.sh</file>
<file>images/check.png</file>
<file>images/uncheck.png</file>
<file>images/settings_grey.png</file>
<file>images/plus.png</file>
<file>server_scripts/check_connection.sh</file>
<file>server_scripts/remove_all_containers.sh</file>
<file>server_scripts/openvpn_cloak/run_container.sh</file>
<file>server_scripts/openvpn/configure_container.sh</file>
<file>server_scripts/openvpn/run_container.sh</file>
<file>server_scripts/openvpn/template.ovpn</file>
<file>server_scripts/openvpn/Dockerfile</file>
<file>server_scripts/openvpn/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/template.ovpn</file>
</qresource>
</RCC>
-88
View File
@@ -1,88 +0,0 @@
#include "runguard.h"
#include <QCryptographicHash>
namespace
{
QString generateKeyHash( const QString& key, const QString& salt )
{
QByteArray data;
data.append( key.toUtf8() );
data.append( salt.toUtf8() );
data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
return data;
}
}
RunGuard::RunGuard(const QString& key)
: key( key )
, memLockKey( generateKeyHash( key, "_memLockKey" ) )
, sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
, sharedMem( sharedmemKey )
, memLock( memLockKey, 1 )
{
qDebug() << "RunGuard::RunGuard key" << key;
}
RunGuard &RunGuard::instance(const QString& key)
{
static RunGuard s(key);
return s;
}
void RunGuard::activate()
{
memLock.acquire();
{
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
fix.attach();
}
memLock.release();
}
RunGuard::~RunGuard()
{
release();
}
bool RunGuard::isAnotherRunning() const
{
if ( sharedMem.isAttached() )
return false;
memLock.acquire();
const bool isRunning = sharedMem.attach();
if ( isRunning )
sharedMem.detach();
memLock.release();
return isRunning;
}
bool RunGuard::tryToRun()
{
if ( isAnotherRunning() ) // Extra check
return false;
memLock.acquire();
const bool result = sharedMem.create( sizeof( quint64 ) );
memLock.release();
if ( !result )
{
release();
return false;
}
return true;
}
void RunGuard::release()
{
memLock.acquire();
if ( sharedMem.isAttached() )
sharedMem.detach();
memLock.release();
}
-37
View File
@@ -1,37 +0,0 @@
#ifndef RUNGUARD_H
#define RUNGUARD_H
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
#include <QDebug>
/**
* @brief The RunGuard class - The application single instance (via shared memory)
*/
class RunGuard
{
public:
static RunGuard &instance(const QString& key = QString());
~RunGuard();
void activate();
bool isAnotherRunning() const;
bool tryToRun();
void release();
private:
RunGuard(const QString& key);
Q_DISABLE_COPY( RunGuard )
const QString key;
const QString memLockKey;
const QString sharedmemKey;
mutable QSharedMemory sharedMem;
mutable QSystemSemaphore memLock;
};
#endif // RUNGUARD_H
+1
View File
@@ -0,0 +1 @@
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER
@@ -0,0 +1 @@
uname -a
+6
View File
@@ -0,0 +1,6 @@
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm update -y -q
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else pm=$pm_yum; fi; sudo $pm install -y -q curl
pm_apt="/usr/bin/apt-get"; pm_yum="/usr/bin/yum"; if [[ -f "$pm_apt" ]]; then sudo export DEBIAN_FRONTEND=noninteractive; sudo $pm_apt install -y -q docker.io; else sudo $pm_yum install -y -q docker; fi
sudo systemctl start docker
+49
View File
@@ -0,0 +1,49 @@
FROM alpine:latest
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
RUN apk --update upgrade --no-cache
ENV EASYRSA_BATCH 1
ENV PATH="/usr/share/easy-rsa:${PATH}"
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]
@@ -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\
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/opt/amnezia/openvpn/server.conf'
@@ -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
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i tun0 -o eth0 -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $VPN_SUBNET_IP/$VPN_SUBNET_MASK_VAL -o eth0 -j MASQUERADE
# kill daemons in case of restart
killall -KILL openvpn
# start daemons if configured
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
tail -f /dev/null
@@ -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>
@@ -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 [ "" ]
@@ -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\
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/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'
@@ -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'
@@ -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
@@ -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>
@@ -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\
tls-auth /opt/amnezia/openvpn/ta.key 0" >>/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'
@@ -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'
@@ -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
@@ -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
View File
@@ -0,0 +1,3 @@
CUR_USER=$(whoami);\
sudo mkdir -p $DOCKERFILE_FOLDER;\
sudo chown $CUR_USER $DOCKERFILE_FOLDER
@@ -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
+2 -2
View File
@@ -1,2 +1,2 @@
docker stop $CONTAINER_NAME
docker rm -f $CONTAINER_NAME
sudo docker stop $CONTAINER_NAME
sudo docker rm -f $CONTAINER_NAME
-3
View File
@@ -1,3 +0,0 @@
sysctl -w net.ipv4.ip_forward=1
iptables -P FORWARD ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
@@ -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>
+198 -36
View File
@@ -1,74 +1,236 @@
#include "defines.h"
#include "settings.h"
#include <QDebug>
#include "protocols/protocols_defs.h"
const char Settings::cloudFlareNs1[] = "1.1.1.1";
const char Settings::cloudFlareNs2[] = "1.0.0.1";
Settings::Settings(QObject* parent) :
QObject(parent),
m_settings (ORGANIZATION_NAME, APPLICATION_NAME, this)
{
read();
// Import old settings
if (serversCount() == 0) {
QString user = m_settings.value("Server/userName").toString();
QString password = m_settings.value("Server/password").toString();
QString serverName = m_settings.value("Server/serverName").toString();
int port = m_settings.value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){
QJsonObject server;
server.insert(config_key::userName, user);
server.insert(config_key::password, password);
server.insert(config_key::hostName, serverName);
server.insert(config_key::port, port);
server.insert(config_key::description, tr("Server #1"));
addServer(server);
m_settings.remove("Server/userName");
m_settings.remove("Server/password");
m_settings.remove("Server/serverName");
m_settings.remove("Server/serverPort");
}
}
}
void Settings::read()
int Settings::serversCount() const
{
m_settings.beginGroup("Server");
m_userName = m_settings.value("userName", QString()).toString();
m_password = m_settings.value("password", QString()).toString();
m_serverName = m_settings.value("serverName", QString()).toString();
m_serverPort = m_settings.value("serverPort", 22).toInt();
m_settings.endGroup();
return serversArray().size();
}
void Settings::save()
QJsonObject Settings::server(int index) const
{
m_settings.beginGroup("Server");
m_settings.setValue("userName", m_userName);
m_settings.setValue("password", m_password);
m_settings.setValue("serverName", m_serverName);
m_settings.setValue("serverPort", m_serverPort);
m_settings.endGroup();
const QJsonArray &servers = serversArray();
if (index >= servers.size()) return QJsonObject();
return servers.at(index).toObject();
}
bool Settings::haveAuthData() const
void Settings::addServer(const QJsonObject &server)
{
return (!serverName().isEmpty() && !userName().isEmpty() && !password().isEmpty());
QJsonArray servers = serversArray();
servers.append(server);
setServersArray(servers);
}
void Settings::setUserName(const QString& login)
void Settings::removeServer(int index)
{
m_userName = login;
QJsonArray servers = serversArray();
if (index >= servers.size()) return;
servers.removeAt(index);
setServersArray(servers);
}
void Settings::setPassword(const QString& password)
bool Settings::editServer(int index, const QJsonObject &server)
{
m_password = password;
QJsonArray servers = serversArray();
if (index >= servers.size()) return false;
servers.replace(index, server);
setServersArray(servers);
return true;
}
void Settings::setServerName(const QString& serverName)
void Settings::setDefaultContainer(int serverIndex, DockerContainer container)
{
m_serverName = serverName;
QJsonObject s = server(serverIndex);
s.insert(config_key::defaultContainer, containerToString(container));
editServer(serverIndex, s);
}
void Settings::setServerPort(int serverPort)
DockerContainer Settings::defaultContainer(int serverIndex) const
{
m_serverPort = serverPort;
return containerFromString(defaultContainerName(serverIndex));
}
void Settings::setServerCredentials(const ServerCredentials &credentials)
QString Settings::defaultContainerName(int serverIndex) const
{
setServerName(credentials.hostName);
setServerPort(credentials.port);
setUserName(credentials.userName);
setPassword(credentials.password);
QString name = server(serverIndex).value(config_key::defaultContainer).toString();
if (name.isEmpty()) {
return containerToString(DockerContainer::None);
}
else return name;
}
ServerCredentials Settings::serverCredentials()
QMap<DockerContainer, QJsonObject> Settings::containers(int serverIndex) const
{
const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray();
QMap<DockerContainer, QJsonObject> containersMap;
for (const QJsonValue &val : containers) {
containersMap.insert(containerFromString(val.toObject().value(config_key::container).toString()), val.toObject());
}
return containersMap;
}
void Settings::setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers)
{
QJsonObject s = server(serverIndex);
QJsonArray c;
for (const QJsonObject &o: containers) {
c.append(o);
}
s.insert(config_key::containers, c);
editServer(serverIndex, s);
}
QJsonObject Settings::containerConfig(int serverIndex, DockerContainer container)
{
if (container == DockerContainer::None) return QJsonObject();
return containers(serverIndex).value(container);
}
void Settings::setContainerConfig(int serverIndex, DockerContainer container, const QJsonObject &config)
{
if (container == DockerContainer::None) {
qCritical() << "Settings::setContainerConfig trying to set config for container == DockerContainer::None";
return;
}
auto c = containers(serverIndex);
c[container] = config;
c[container][config_key::container] = containerToString(container);
setContainers(serverIndex, c);
}
void Settings::removeContainerConfig(int serverIndex, DockerContainer container)
{
if (container == DockerContainer::None) {
qCritical() << "Settings::removeContainerConfig trying to remove config for container == DockerContainer::None";
return;
}
auto c = containers(serverIndex);
c.remove(container);
setContainers(serverIndex, c);
}
QJsonObject Settings::protocolConfig(int serverIndex, DockerContainer container, Protocol proto)
{
const QJsonObject &c = containerConfig(serverIndex, container);
return c.value(protoToString(proto)).toObject();
}
void Settings::setProtocolConfig(int serverIndex, DockerContainer container, Protocol proto, const QJsonObject &config)
{
QJsonObject c = containerConfig(serverIndex, container);
c.insert(protoToString(proto), config);
setContainerConfig(serverIndex, container, c);
}
void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer container, Protocol proto)
{
if (proto == Protocol::Any) {
for (Protocol p: { Protocol::OpenVpn, Protocol::ShadowSocks, Protocol::Cloak, Protocol::WireGuard}) {
clearLastConnectionConfig(serverIndex, container, p);
}
return;
}
QJsonObject c = protocolConfig(serverIndex, container, proto);
c.remove(config_key::last_config);
setProtocolConfig(serverIndex, container, proto, c);
qDebug() << "Settings::clearLastConnectionConfig for" << serverIndex << container << proto;
}
bool Settings::haveAuthData(int serverIndex) const
{
if (serverIndex < 0) return false;
ServerCredentials cred = serverCredentials(serverIndex);
return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty());
}
QString Settings::nextAvailableServerName() const
{
int i = 0;
bool nameExist = false;
do {
i++;
nameExist = false;
for (const QJsonValue &server: serversArray()) {
if (server.toObject().value(config_key::description).toString() == tr("Server") + " " + QString::number(i)) {
nameExist = true;
break;
}
}
} while (nameExist);
return tr("Server") + " " + QString::number(i);
}
QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); }
QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); }
//void Settings::setServerCredentials(const ServerCredentials &credentials)
//{
// setServerName(credentials.hostName);
// setServerPort(credentials.port);
// setUserName(credentials.userName);
// setPassword(credentials.password);
//}
ServerCredentials Settings::defaultServerCredentials() const
{
return serverCredentials(defaultServerIndex());
}
ServerCredentials Settings::serverCredentials(int index) const
{
const QJsonObject &s = server(index);
ServerCredentials credentials;
credentials.hostName = serverName();
credentials.userName = userName();
credentials.password = password();
credentials.port = serverPort();
credentials.hostName = s.value(config_key::hostName).toString();
credentials.userName = s.value(config_key::userName).toString();
credentials.password = s.value(config_key::password).toString();
credentials.port = s.value(config_key::port).toInt();
return credentials;
}
+81 -22
View File
@@ -5,7 +5,12 @@
#include <QString>
#include <QSettings>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include "core/defs.h"
#include "protocols/protocols_defs.h"
using namespace amnezia;
@@ -18,40 +23,94 @@ class Settings : public QObject
public:
explicit Settings(QObject* parent = nullptr);
void read();
void save();
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);
// QString userName() const { return m_settings.value("Server/userName", QString()).toString(); }
// void setUserName(const QString& login) { m_settings.setValue("Server/userName", login); }
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();
// QString password() const { return m_settings.value("Server/password", QString()).toString(); }
// void setPassword(const QString& password) { m_settings.setValue("Server/password", password); }
// QString serverName() const { return m_settings.value("Server/serverName", QString()).toString(); }
// void setServerName(const QString& serverName) { m_settings.setValue("Server/serverName", serverName); }
bool haveAuthData() const;
// int serverPort() const { return m_settings.value("Server/serverPort", 22).toInt(); }
// void setServerPort(int serverPort = 22) { m_settings.setValue("Server/serverPort", serverPort); }
ServerCredentials defaultServerCredentials() const;
ServerCredentials serverCredentials(int index) const;
//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()); }
// 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()); }
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", QString()).toBool(); }
void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", 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
+36 -5
View File
@@ -1,5 +1,7 @@
#include "SlidingStackedWidget.h"
#include <QEventLoop>
SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
: QStackedWidget(parent)
{
@@ -28,6 +30,14 @@ SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
m_wrap = false;
m_pnow = QPoint(0,0);
m_active = false;
animnow = new QPropertyAnimation();
animnext = new QPropertyAnimation();
animgroup = new QParallelAnimationGroup;
animgroup->addAnimation(animnow);
animgroup->addAnimation(animnext);
}
SlidingStackedWidget::~SlidingStackedWidget() {
@@ -90,6 +100,21 @@ 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;
@@ -149,22 +174,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;
@@ -47,6 +47,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 +73,10 @@ protected:
bool m_active;
QList<QWidget*> blockedPageList;
QPropertyAnimation *animnow;
QPropertyAnimation *animnext;
QParallelAnimationGroup *animgroup;
};
#endif // SLIDINGSTACKEDWIDGET_H
+11 -3
View File
@@ -7,6 +7,9 @@
#include <QApplication>
#include <QPoint>
#include <QSize>
#include <QSysInfo>
#include <QOperatingSystemVersion>
#ifdef Q_OS_WIN
#include <windows.h>
@@ -31,9 +34,10 @@ CFramelessWindow::CFramelessWindow(QWidget *parent)
// setWindowFlag(Qt::WindowSystemMenuHint, true);
// setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead
setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
setResizeable(m_bResizeable);
if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) {
setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
setResizeable(m_bResizeable);
}
}
void CFramelessWindow::setResizeable(bool resizeable)
@@ -99,6 +103,10 @@ void CFramelessWindow::addIgnoreWidget(QWidget* widget)
bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) {
return QMainWindow::nativeEvent(eventType, message, result);
}
//Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
MSG* msg = *reinterpret_cast<MSG**>(message);
+1364 -156
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 196 files, more files were not shown because too many files have changed in this diff. Show more