285 lines
9 KiB
C++
285 lines
9 KiB
C++
#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", "");
|
|
}
|
|
|
|
#ifdef Q_OS_MAC
|
|
config.replace("block-outside-dns", "");
|
|
#endif
|
|
|
|
return config;
|
|
}
|
|
|
|
QString OpenVpnConfigurator::convertOpenSShKey(const QString &key)
|
|
{
|
|
QProcess p;
|
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
QTemporaryFile tmp;
|
|
#ifdef QT_DEBUG
|
|
tmp.setAutoRemove(false);
|
|
#endif
|
|
tmp.open();
|
|
tmp.write(key.toUtf8());
|
|
tmp.close();
|
|
|
|
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
|
|
|
|
#ifdef Q_OS_WIN
|
|
p.setProcessEnvironment(prepareEnv());
|
|
p.setProgram("cmd.exe");
|
|
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
|
|
#else
|
|
p.setProgram("ssh-keygen");
|
|
p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName());
|
|
#endif
|
|
|
|
p.start();
|
|
p.waitForFinished();
|
|
|
|
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
|
|
|
|
tmp.open();
|
|
|
|
return tmp.readAll();
|
|
}
|
|
|
|
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
|
|
const ServerCredentials &credentials, QString clientId)
|
|
{
|
|
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
|
|
"easyrsa import-req %2/%3.req %3\"")
|
|
.arg(amnezia::containerToString(container))
|
|
.arg(amnezia::protocols::openvpn::clientsDirPath)
|
|
.arg(clientId);
|
|
|
|
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
|
|
"easyrsa sign-req client %2\"")
|
|
.arg(amnezia::containerToString(container))
|
|
.arg(clientId);
|
|
|
|
QStringList scriptList {script_import, script_sign};
|
|
QString script = ServerController::replaceVars(scriptList.join("\n"), ServerController::genVarsForScript(credentials, container));
|
|
|
|
return ServerController::runScript(ServerController::sshParams(credentials), script);
|
|
}
|