mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
* refactor: move business logic from servers model * refactor: move containersModel initialization * refactor: added protocol ui controller and removed settings class from protocols model * refactor: moved cli management to separate controller * refactor: moved app split to separate controller * refactor: moved site split to separate controller * refactor: moved allowed dns to separate controller * refactor: moved language logic to separate ui controller * refactor: removed Settings from devices model * refactor: moved configs and services api logit to separate core controller * refactor: added a layer with a repository between the storage and controllers * refactor: use child parent system instead of smart pointers for controllers and models initialization * refactor: moved install functions from server controller to install controller * refactor: install controller refactoring * chore: renamed exportController to exportUiController * refactor: separate export controller * refactor: removed VpnConfigurationsController * chore: renamed ServerController to SshSession * refactor: replaced ServerController to SshSession * chore: moved qml controllers to separate folder * chore: include fixes * chore: moved utils from core root to core/utils * chore: include fixes * chore: rename core/utils files to camelCase foramt * chore: include fixes * chore: moved some utils to api and selfhosted folders * chore: include fixes * chore: remove unused file * chore: moved serialization folder to core/utils * chore: include fixes * chore: moved some files from client root to core/utils * chore: include fixes * chore: moved ui utils to ui/utils folder * chore: include fixes * chore: move utils from root to ui/utils * chore: include fixes * chore: moved configurators to core/configurators * chore: include fixes * refactor: moved iap logic from ui controller to core * refactor: moved remaining core logic from ApiConfigsController to SubscriptionController * chore: rename apiNewsController to apiNewsUiController * refactor: moved core logic from news ui controller to core * chore: renamed apiConfigsController to subscriptionUiController * chore: include fixes * refactor: merge ApiSettingsController with SubscriptionUiController * chore: moved ui selfhosted controllers to separate folder * chore: include fixes * chore: rename connectionController to connectiomUiController * refactor: moved core logic from connectionUiController * chore: rename settingsController to settingsUiController * refactor: move core logic from settingsUiController * refactor: moved core controller signal/slot connections to separate class * fix: newsController fixes after refactoring * chore: rename model to camelCase * chore: include fixes * chore: remove unused code * chore: move selfhosted core to separate folder * chore: include fixes * chore: rename importController to importUiController * refactor: move core logic from importUiController * chore: minor fixes * chore: remove prem v1 migration * refactor: remove openvpn over cloak and openvpn over shadowsocks * refactor: removed protocolsForContainer function * refactor: add core models * refactor: replace json with c++ structs for server config * refactor: move getDnsPair to ServerConfigUtils * feat: add admin selfhosted config export test * feat: add multi import test * refactor: use coreController for tests * feat: add few simple tests * chore: qrepos in all core controllers * feat: add test for settings * refactor: remove repo dependency from configurators * chore: moved protocols to core folder * chore: include fixes * refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places * chore: include fixes * chore: build fixes * chore: build fixes * refactor: remove q repo and interface repo * feat: add test for ui servers model and controller * chore: renamed to camelCase * chore: include fixes * refactor: moved core logic from sites ui controller * fix: fixed api config processing * fix: fixed processed server index processing * refactor: protocol models now use c++ structs instead of json configs * refactor: servers model now use c++ struct instead of json config * fix: fixed default server index processing * fix: fix logs init * fix: fix secure settings load keys * chore: build fixes * fix: fixed clear settings * fix: fixed restore backup * fix: sshSession usage * fix: fixed export functions signatures * fix: return missing part from buildContainerWorker * fix: fixed server description on page home * refactor: add container config helpers functions * refactor: c++ structs instead of json * chore: add dns protocol config struct * refactor: move config utils functions to config structs * feat: add test for selfhosted server setup * refactor: separate resources.qrc * fix: fixed server rename * chore: return nameOverriddenByUser * fix: build fixes * fix: fixed models init * refactor: cleanup models usage * fix: fixed models init * chore: cleanup connections and functions signatures * chore: cleanup updateModel calls * feat: added cache to servers repo * chore: cleanup unused functions * chore: ssxray processing * chore: remove transportProtoWithDefault and portWithDefault functions * chore: removed proto types any and l2tp * refactor: moved some constants * fix: fixed native configs export * refactor: remove json from processConfigWith functions * fix: fixed processed server index usage * fix: qml warning fixes * chore: merge fixes * chore: update tests * fix: fixed xray config processing * fix: fixed split tunneling processing * chore: rename sites controllers and model * chore: rename fixes * chore: minor fixes * chore: remove ability to load backup from "file with connection settings" button * fix: fixed api device revoke * fix: remove full model update when renaming a user * fix: fixed premium/free server rename * fix: fixed selfhosted new server install * fix: fixed updateContainer function * fix: fixed revoke for external premium configs * feat: add native configs qr processing * chore: codestyle fixes * fix: fixed admin config create * chore: again remove ability to load backup from "file with connection settings" button * chore: minor fixes * fix: fixed variables initialization * fix: fixed qml imports * fix: minor fixes * fix: fix vpnConnection function calls * feat: add buckup error handling * fix: fixed admin config revok * fix: fixed selfhosted awg installation * fix: ad visability * feat: add empty check for primary dns * chore: minor fixes
763 lines
30 KiB
C++
763 lines
30 KiB
C++
#include "importController.h"
|
|
|
|
#include <QDataStream>
|
|
#include <QDebug>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonParseError>
|
|
#include <QMap>
|
|
#include <QRandomGenerator>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QRegularExpressionMatchIterator>
|
|
#include <QUrl>
|
|
#include <algorithm>
|
|
|
|
#include "core/utils/containerEnum.h"
|
|
#include "core/utils/containers/containerUtils.h"
|
|
#include "core/utils/protocolEnum.h"
|
|
#include "core/utils/api/apiEnums.h"
|
|
#include "core/utils/constants/apiKeys.h"
|
|
#include "core/utils/constants/apiConstants.h"
|
|
#include "core/utils/api/apiUtils.h"
|
|
#include "core/utils/serialization/serialization.h"
|
|
#include "core/utils/utilities.h"
|
|
#include "core/utils/protocolEnum.h"
|
|
#include "core/protocols/protocolUtils.h"
|
|
#include "core/utils/constants/configKeys.h"
|
|
#include "core/utils/constants/protocolConstants.h"
|
|
#include "core/utils/qrCodeUtils.h"
|
|
#include "core/models/serverConfig.h"
|
|
|
|
using namespace amnezia;
|
|
using namespace ProtocolUtils;
|
|
|
|
namespace
|
|
{
|
|
ConfigTypes checkConfigFormat(const QString &config)
|
|
{
|
|
const QString openVpnConfigPatternCli = "client";
|
|
const QString openVpnConfigPatternDriver1 = "dev tun";
|
|
const QString openVpnConfigPatternDriver2 = "dev tap";
|
|
|
|
const QString wireguardConfigPatternSectionInterface = "[Interface]";
|
|
const QString wireguardConfigPatternSectionPeer = "[Peer]";
|
|
|
|
const QString xrayConfigPatternInbound = "inbounds";
|
|
const QString xrayConfigPatternOutbound = "outbounds";
|
|
|
|
const QString amneziaConfigPattern = "containers";
|
|
const QString amneziaConfigPatternHostName = "hostName";
|
|
const QString amneziaConfigPatternUserName = "userName";
|
|
const QString amneziaConfigPatternPassword = "password";
|
|
const QString amneziaFreeConfigPattern = "api_key";
|
|
const QString amneziaPremiumConfigPattern = "auth_data";
|
|
const QString backupPattern = "Servers/serversList";
|
|
|
|
if (config.contains(backupPattern)) {
|
|
return ConfigTypes::Backup;
|
|
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
|
|
|| config.contains(amneziaPremiumConfigPattern)
|
|
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
|
&& config.contains(amneziaConfigPatternPassword))) {
|
|
return ConfigTypes::Amnezia;
|
|
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
|
return ConfigTypes::WireGuard;
|
|
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
|
return ConfigTypes::Xray;
|
|
} else if (config.contains(openVpnConfigPatternCli)
|
|
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
|
return ConfigTypes::OpenVpn;
|
|
}
|
|
return ConfigTypes::Invalid;
|
|
}
|
|
} // namespace
|
|
|
|
ImportController::ImportController(SecureServersRepository* serversRepository,
|
|
SecureAppSettingsRepository* appSettingsRepository,
|
|
QObject *parent)
|
|
: QObject(parent),
|
|
m_serversRepository(serversRepository),
|
|
m_appSettingsRepository(appSettingsRepository)
|
|
{
|
|
}
|
|
|
|
ImportController::ImportResult ImportController::extractConfigFromData(const QString &data, const QString &configFileName)
|
|
{
|
|
ImportResult result;
|
|
result.configFileName = configFileName;
|
|
result.maliciousWarningText.clear();
|
|
|
|
QString config = data;
|
|
QString prefix;
|
|
QString errormsg;
|
|
ConfigTypes configType = ConfigTypes::Invalid;
|
|
|
|
if (config.startsWith("vless://")) {
|
|
configType = ConfigTypes::Xray;
|
|
result.config = extractXrayConfig(
|
|
Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
|
configType, prefix);
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (config.startsWith("vmess://") && config.contains("@")) {
|
|
configType = ConfigTypes::Xray;
|
|
result.config = extractXrayConfig(
|
|
Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
|
configType, prefix);
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (config.startsWith("vmess://")) {
|
|
configType = ConfigTypes::Xray;
|
|
result.config = extractXrayConfig(
|
|
Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
|
configType, prefix);
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (config.startsWith("trojan://")) {
|
|
configType = ConfigTypes::Xray;
|
|
result.config = extractXrayConfig(
|
|
Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
|
configType, prefix);
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (config.startsWith("ss://") && !config.contains("plugin=")) {
|
|
configType = ConfigTypes::ShadowSocks;
|
|
result.config = extractXrayConfig(
|
|
Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
|
|
configType, prefix);
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (config.startsWith("ssd://")) {
|
|
QStringList tmp;
|
|
QList<std::pair<QString, QJsonObject>> servers = serialization::ssd::Deserialize(config, &prefix, &tmp);
|
|
configType = ConfigTypes::ShadowSocks;
|
|
// Took only first config from list
|
|
if (!servers.isEmpty()) {
|
|
result.config = extractXrayConfig(servers.first().first, configType);
|
|
}
|
|
if (!result.config.empty()) {
|
|
result.configType = configType;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
configType = checkConfigFormat(config);
|
|
if (configType == ConfigTypes::Invalid) {
|
|
config.replace("vpn://", "");
|
|
QByteArray ba = QByteArray::fromBase64(config.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
QByteArray baUncompressed = qUncompress(ba);
|
|
if (!baUncompressed.isEmpty()) {
|
|
ba = baUncompressed;
|
|
}
|
|
|
|
config = ba;
|
|
configType = checkConfigFormat(config);
|
|
}
|
|
|
|
result.configType = configType;
|
|
|
|
switch (configType) {
|
|
case ConfigTypes::OpenVpn: {
|
|
result.config = extractOpenVpnConfig(config);
|
|
if (!result.config.empty()) {
|
|
checkForMaliciousStrings(result.config, result.maliciousWarningText);
|
|
return result;
|
|
}
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
case ConfigTypes::Awg:
|
|
case ConfigTypes::WireGuard: {
|
|
result.config = extractWireGuardConfig(config, result.configType);
|
|
result.isNativeWireGuardConfig = (result.configType == ConfigTypes::WireGuard);
|
|
if (!result.config.empty()) {
|
|
return result;
|
|
}
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
case ConfigTypes::Xray: {
|
|
result.config = extractXrayConfig(config, configType);
|
|
if (!result.config.empty()) {
|
|
return result;
|
|
}
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
case ConfigTypes::Amnezia: {
|
|
result.config = QJsonDocument::fromJson(config.toUtf8()).object();
|
|
|
|
if (apiUtils::isServerFromApi(result.config)) {
|
|
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
|
|
apiConfig[apiDefs::key::vpnKey] = data;
|
|
result.config[apiDefs::key::apiConfig] = apiConfig;
|
|
}
|
|
|
|
processAmneziaConfig(result.config);
|
|
if (!result.config.empty()) {
|
|
checkForMaliciousStrings(result.config, result.maliciousWarningText);
|
|
return result;
|
|
}
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
case ConfigTypes::Backup: {
|
|
result.errorCode = ErrorCode::ImportBackupFileUseRestoreInstead;
|
|
return result;
|
|
}
|
|
case ConfigTypes::Invalid: {
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
result.configFileName.clear();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
|
|
ImportController::ImportResult ImportController::extractConfigFromQr(const QByteArray &data)
|
|
{
|
|
ImportResult result;
|
|
|
|
QString dataStr = QString::fromUtf8(data);
|
|
ConfigTypes configType = checkConfigFormat(dataStr);
|
|
if (configType != ConfigTypes::Invalid) {
|
|
return extractConfigFromData(dataStr, "");
|
|
}
|
|
|
|
QJsonObject dataObj = QJsonDocument::fromJson(data).object();
|
|
if (!dataObj.isEmpty()) {
|
|
result.config = dataObj;
|
|
result.configType = ConfigTypes::Amnezia;
|
|
return result;
|
|
}
|
|
|
|
QByteArray ba_uncompressed = qUncompress(data);
|
|
if (!ba_uncompressed.isEmpty()) {
|
|
result.config = QJsonDocument::fromJson(ba_uncompressed).object();
|
|
if (result.config.isEmpty()) {
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
result.configType = ConfigTypes::Amnezia;
|
|
return result;
|
|
}
|
|
|
|
QByteArray ba = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
QByteArray baUncompressed = qUncompress(ba);
|
|
|
|
if (!baUncompressed.isEmpty()) {
|
|
ba = baUncompressed;
|
|
}
|
|
|
|
if (!ba.isEmpty()) {
|
|
result.config = QJsonDocument::fromJson(ba).object();
|
|
if (result.config.isEmpty()) {
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
result.configType = ConfigTypes::Amnezia;
|
|
return result;
|
|
}
|
|
|
|
result.errorCode = ErrorCode::ImportInvalidConfigError;
|
|
return result;
|
|
}
|
|
|
|
void ImportController::startDecodingQr()
|
|
{
|
|
m_qrCodeChunks.clear();
|
|
m_totalQrCodeChunksCount = 0;
|
|
m_receivedQrCodeChunksCount = 0;
|
|
m_isQrCodeProcessed = true;
|
|
}
|
|
|
|
ImportController::QrParseResult ImportController::parseQrCodeChunk(const QString &code)
|
|
{
|
|
QrParseResult parseResult;
|
|
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
|
|
parseResult.chunksTotal = m_totalQrCodeChunksCount;
|
|
|
|
if (!m_isQrCodeProcessed) {
|
|
return parseResult;
|
|
}
|
|
|
|
QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
QDataStream s(&ba, QIODevice::ReadOnly);
|
|
qint16 magic;
|
|
s >> magic;
|
|
|
|
if (magic == qrCodeUtils::qrMagicCode) {
|
|
quint8 chunksCount;
|
|
s >> chunksCount;
|
|
if (m_totalQrCodeChunksCount != chunksCount) {
|
|
m_qrCodeChunks.clear();
|
|
}
|
|
|
|
m_totalQrCodeChunksCount = chunksCount;
|
|
|
|
quint8 chunkId;
|
|
s >> chunkId;
|
|
s >> m_qrCodeChunks[chunkId];
|
|
m_receivedQrCodeChunksCount = m_qrCodeChunks.size();
|
|
parseResult.chunksReceived = m_receivedQrCodeChunksCount;
|
|
parseResult.chunksTotal = m_totalQrCodeChunksCount;
|
|
|
|
if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) {
|
|
QByteArray data;
|
|
for (int i = 0; i < m_totalQrCodeChunksCount; ++i) {
|
|
data.append(m_qrCodeChunks.value(i));
|
|
}
|
|
|
|
ImportResult result = extractConfigFromQr(data);
|
|
if (result.errorCode == ErrorCode::NoError) {
|
|
parseResult.success = true;
|
|
parseResult.importResult = result;
|
|
m_isQrCodeProcessed = false;
|
|
} else {
|
|
m_qrCodeChunks.clear();
|
|
m_totalQrCodeChunksCount = 0;
|
|
m_receivedQrCodeChunksCount = 0;
|
|
}
|
|
}
|
|
} else {
|
|
ImportResult result = extractConfigFromQr(code.toUtf8());
|
|
if (result.errorCode != ErrorCode::NoError) {
|
|
result = extractConfigFromQr(ba);
|
|
}
|
|
if (result.errorCode == ErrorCode::NoError) {
|
|
parseResult.success = true;
|
|
parseResult.importResult = result;
|
|
m_isQrCodeProcessed = false;
|
|
}
|
|
}
|
|
|
|
return parseResult;
|
|
}
|
|
|
|
bool ImportController::isQrDecodingActive() const
|
|
{
|
|
return m_isQrCodeProcessed;
|
|
}
|
|
|
|
int ImportController::qrChunksReceived() const
|
|
{
|
|
return m_receivedQrCodeChunksCount;
|
|
}
|
|
|
|
int ImportController::qrChunksTotal() const
|
|
{
|
|
return m_totalQrCodeChunksCount;
|
|
}
|
|
|
|
void ImportController::importConfig(const QJsonObject &config)
|
|
{
|
|
ServerCredentials credentials;
|
|
credentials.hostName = config.value(configKey::hostName).toString();
|
|
credentials.port = config.value(configKey::port).toInt();
|
|
credentials.userName = config.value(configKey::userName).toString();
|
|
credentials.secretData = config.value(configKey::password).toString();
|
|
|
|
if (credentials.isValid() || config.contains(configKey::containers)) {
|
|
ServerConfig serverConfig = ServerConfig::fromJson(config);
|
|
m_serversRepository->addServer(serverConfig);
|
|
emit importFinished();
|
|
} else if (config.contains(configKey::configVersion)) {
|
|
quint16 crc = qChecksum(QJsonDocument(config).toJson());
|
|
if (m_serversRepository->hasServerWithCrc(crc)) {
|
|
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
|
} else {
|
|
QJsonObject configWithCrc = config;
|
|
configWithCrc.insert(configKey::crc, crc);
|
|
ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc);
|
|
m_serversRepository->addServer(serverConfig);
|
|
emit importFinished();
|
|
}
|
|
} else {
|
|
qDebug() << "Failed to import profile";
|
|
qDebug().noquote() << QJsonDocument(config).toJson();
|
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
|
}
|
|
}
|
|
|
|
QJsonObject ImportController::processNativeWireGuardConfig(const QJsonObject &config)
|
|
{
|
|
QJsonObject result = config;
|
|
auto containers = result.value(configKey::containers).toArray();
|
|
if (!containers.isEmpty()) {
|
|
auto container = containers.at(0).toObject();
|
|
auto serverProtocolConfig = container.value(ContainerUtils::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject();
|
|
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(configKey::lastConfig).toString().toUtf8()).object();
|
|
|
|
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
|
QString junkPacketMinSize = QString::number(10);
|
|
QString junkPacketMaxSize = QString::number(50);
|
|
clientProtocolConfig[configKey::junkPacketCount] = junkPacketCount;
|
|
clientProtocolConfig[configKey::junkPacketMinSize] = junkPacketMinSize;
|
|
clientProtocolConfig[configKey::junkPacketMaxSize] = junkPacketMaxSize;
|
|
clientProtocolConfig[configKey::initPacketJunkSize] = "0";
|
|
clientProtocolConfig[configKey::responsePacketJunkSize] = "0";
|
|
clientProtocolConfig[configKey::initPacketMagicHeader] = "1";
|
|
clientProtocolConfig[configKey::responsePacketMagicHeader] = "2";
|
|
clientProtocolConfig[configKey::underloadPacketMagicHeader] = "3";
|
|
clientProtocolConfig[configKey::transportPacketMagicHeader] = "4";
|
|
|
|
clientProtocolConfig[configKey::cookieReplyPacketJunkSize] = "0";
|
|
clientProtocolConfig[configKey::transportPacketJunkSize] = "0";
|
|
|
|
clientProtocolConfig[configKey::specialJunk1] = protocols::awg::defaultSpecialJunk1;
|
|
|
|
clientProtocolConfig[configKey::isObfuscationEnabled] = true;
|
|
|
|
serverProtocolConfig[configKey::lastConfig] = QString(QJsonDocument(clientProtocolConfig).toJson());
|
|
container[configKey::wireguard] = serverProtocolConfig;
|
|
containers.replace(0, container);
|
|
result[configKey::containers] = containers;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ConfigTypes ImportController::checkConfigFormat(const QString &config) const
|
|
{
|
|
return ::checkConfigFormat(config);
|
|
}
|
|
|
|
QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
|
{
|
|
QJsonObject openVpnConfig;
|
|
openVpnConfig[configKey::config] = data;
|
|
|
|
QJsonObject lastConfig;
|
|
lastConfig[configKey::lastConfig] = QString(QJsonDocument(openVpnConfig).toJson());
|
|
lastConfig[configKey::isThirdPartyConfig] = true;
|
|
|
|
QJsonObject containers;
|
|
containers.insert(configKey::container, QJsonValue(configKey::amneziaOpenvpn));
|
|
containers.insert(configKey::openvpn, QJsonValue(lastConfig));
|
|
|
|
QJsonArray arr;
|
|
arr.push_back(containers);
|
|
|
|
QString hostName;
|
|
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
|
|
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
|
if (hostNameMatch.hasMatch()) {
|
|
hostName = hostNameMatch.captured(1);
|
|
}
|
|
|
|
QJsonObject config;
|
|
config[configKey::containers] = arr;
|
|
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
|
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
|
|
|
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
|
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
|
if (dnsMatch.hasNext()) {
|
|
config[configKey::dns1] = dnsMatch.next().captured(1);
|
|
}
|
|
if (dnsMatch.hasNext()) {
|
|
config[configKey::dns2] = dnsMatch.next().captured(1);
|
|
}
|
|
|
|
config[configKey::hostName] = hostName;
|
|
|
|
return config;
|
|
}
|
|
|
|
QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const
|
|
{
|
|
QMap<QString, QString> configMap;
|
|
auto configByLines = data.split("\n");
|
|
for (const QString &line : configByLines) {
|
|
QString trimmedLine = line.trimmed();
|
|
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
|
continue;
|
|
} else {
|
|
QStringList parts = trimmedLine.split(" = ");
|
|
if (parts.count() == 2) {
|
|
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
|
|
}
|
|
}
|
|
}
|
|
|
|
QJsonObject lastConfig;
|
|
lastConfig[configKey::config] = data;
|
|
|
|
auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) };
|
|
QString hostName;
|
|
QString port;
|
|
if (!url.host().isEmpty()) {
|
|
hostName = url.host();
|
|
} else {
|
|
qDebug() << "Key parameter" << protocols::wireguard::Endpoint << "is missing or has an invalid format";
|
|
return QJsonObject();
|
|
}
|
|
|
|
if (url.port() != -1) {
|
|
port = QString::number(url.port());
|
|
} else {
|
|
port = protocols::wireguard::defaultPort;
|
|
}
|
|
|
|
lastConfig[configKey::hostName] = hostName;
|
|
lastConfig[configKey::port] = port.toInt();
|
|
|
|
if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
|
&& !configMap.value(protocols::wireguard::Address).isEmpty()
|
|
&& !configMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
|
lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey);
|
|
lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address);
|
|
|
|
if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
|
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey);
|
|
} else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
|
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey);
|
|
}
|
|
|
|
lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey);
|
|
} else {
|
|
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
|
return QJsonObject();
|
|
}
|
|
|
|
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
|
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
|
}
|
|
|
|
if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
|
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
|
|
}
|
|
|
|
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
|
|
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
|
|
|
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
|
|
|
QString protocolName = configKey::wireguard;
|
|
QString protocolVersion;
|
|
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
|
|
|
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
|
|
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
|
|
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
|
|
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
|
|
configKey::transportPacketMagicHeader };
|
|
|
|
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
|
|
configKey::transportPacketJunkSize,
|
|
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
|
|
configKey::specialJunk4, configKey::specialJunk5
|
|
};
|
|
|
|
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
|
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
|
if (hasAllRequiredFields) {
|
|
for (const QString &field : requiredJunkFields) {
|
|
lastConfig[field] = configMap.value(field);
|
|
}
|
|
|
|
for (const QString &field : optionalJunkFields) {
|
|
if (!configMap.value(field).isEmpty()) {
|
|
lastConfig[field] = configMap.value(field);
|
|
}
|
|
}
|
|
|
|
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
|
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
|
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
|
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
|
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
|
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
|
!configMap.value(configKey::specialJunk5).isEmpty();
|
|
|
|
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
|
protocolVersion = "2";
|
|
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
|
|
protocolVersion = "1.5";
|
|
}
|
|
protocolName = configKey::awg;
|
|
detectedType = ConfigTypes::Awg;
|
|
}
|
|
|
|
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
|
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
|
} else {
|
|
lastConfig[configKey::mtu] = (protocolName == configKey::awg)
|
|
? protocols::awg::defaultMtu
|
|
: protocols::wireguard::defaultMtu;
|
|
}
|
|
|
|
QJsonObject wireguardConfig;
|
|
wireguardConfig[configKey::lastConfig] = QString(QJsonDocument(lastConfig).toJson());
|
|
wireguardConfig[configKey::isThirdPartyConfig] = true;
|
|
wireguardConfig[configKey::port] = port;
|
|
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
|
|
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
|
|
wireguardConfig[configKey::protocolVersion] = protocolVersion;
|
|
}
|
|
|
|
QJsonObject containers;
|
|
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;
|
|
containers.insert(configKey::container, QJsonValue(containerName));
|
|
containers.insert(protocolName, QJsonValue(wireguardConfig));
|
|
|
|
QJsonArray arr;
|
|
arr.push_back(containers);
|
|
|
|
QJsonObject config;
|
|
config[configKey::containers] = arr;
|
|
config[configKey::defaultContainer] = containerName;
|
|
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
|
|
|
const static QRegularExpression dnsRegExp(
|
|
"DNS = "
|
|
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
|
QRegularExpressionMatch dnsMatch = dnsRegExp.match(data);
|
|
if (dnsMatch.hasMatch()) {
|
|
config[configKey::dns1] = dnsMatch.captured(1);
|
|
config[configKey::dns2] = dnsMatch.captured(2);
|
|
}
|
|
|
|
config[configKey::hostName] = hostName;
|
|
|
|
configType = detectedType;
|
|
return config;
|
|
}
|
|
|
|
QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description) const
|
|
{
|
|
QJsonParseError parserErr;
|
|
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
|
|
|
QJsonObject xrayVpnConfig;
|
|
xrayVpnConfig[configKey::config] = jsonConf.toJson().constData();
|
|
QJsonObject lastConfig;
|
|
lastConfig[configKey::lastConfig] = jsonConf.toJson().constData();
|
|
lastConfig[configKey::isThirdPartyConfig] = true;
|
|
|
|
QJsonObject containers;
|
|
if (configType == ConfigTypes::ShadowSocks) {
|
|
containers.insert(configKey::ssxray, QJsonValue(lastConfig));
|
|
containers.insert(configKey::container, QJsonValue(configKey::amneziaSsxray));
|
|
} else {
|
|
containers.insert(configKey::container, QJsonValue(configKey::amneziaXray));
|
|
containers.insert(configKey::xray, QJsonValue(lastConfig));
|
|
}
|
|
|
|
QJsonArray arr;
|
|
arr.push_back(containers);
|
|
|
|
QString hostName;
|
|
|
|
const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)");
|
|
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
|
if (hostNameMatch.hasMatch()) {
|
|
hostName = hostNameMatch.captured(1);
|
|
}
|
|
|
|
QJsonObject config;
|
|
config[configKey::containers] = arr;
|
|
config[configKey::defaultContainer] = (configType == ConfigTypes::ShadowSocks)
|
|
? configKey::amneziaSsxray
|
|
: configKey::amneziaXray;
|
|
if (description.isEmpty()) {
|
|
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
|
} else {
|
|
config[configKey::description] = description;
|
|
}
|
|
config[configKey::hostName] = hostName;
|
|
|
|
return config;
|
|
}
|
|
|
|
void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig, QString &warningText) const
|
|
{
|
|
const QJsonArray &containers = serverConfig.value(configKey::containers).toArray();
|
|
for (const QJsonValue &container : containers) {
|
|
auto containerConfig = container.toObject();
|
|
auto containerName = containerConfig[configKey::container].toString();
|
|
if (containerName == ContainerUtils::containerToString(DockerContainer::OpenVpn)) {
|
|
|
|
QString protocolConfig =
|
|
containerConfig[ProtocolUtils::protoToString(Proto::OpenVpn)].toObject()[configKey::lastConfig].toString();
|
|
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[configKey::config].toString();
|
|
|
|
// https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst
|
|
QStringList dangerousTags {
|
|
"up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify"
|
|
};
|
|
|
|
QStringList maliciousStrings;
|
|
QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts);
|
|
|
|
for (const QString &rawLine : lines) {
|
|
QString line = rawLine.trimmed();
|
|
|
|
QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty);
|
|
if (dangerousTags.contains(command, Qt::CaseInsensitive)) {
|
|
maliciousStrings << rawLine;
|
|
}
|
|
}
|
|
|
|
warningText = "This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious "
|
|
"scripts, so only add it if you fully trust the provider of this config. ";
|
|
|
|
if (!maliciousStrings.isEmpty()) {
|
|
warningText += "<br>In the imported configuration, potentially dangerous lines were found:";
|
|
for (const auto &string : maliciousStrings) {
|
|
warningText += QString("<br><i>%1</i>").arg(string);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImportController::processAmneziaConfig(QJsonObject &config) const
|
|
{
|
|
auto containers = config.value(configKey::containers).toArray();
|
|
for (auto i = 0; i < containers.size(); i++) {
|
|
auto container = containers.at(i).toObject();
|
|
auto dockerContainer = ContainerUtils::containerFromString(container.value(configKey::container).toString());
|
|
if (ContainerUtils::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) {
|
|
auto containerConfig = container.value(ContainerUtils::containerTypeToProtocolString(dockerContainer)).toObject();
|
|
auto protocolConfig = containerConfig.value(configKey::lastConfig).toString();
|
|
if (protocolConfig.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
|
|
jsonConfig[configKey::mtu] =
|
|
ContainerUtils::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
|
|
|
containerConfig[configKey::lastConfig] = QString(QJsonDocument(jsonConfig).toJson());
|
|
|
|
container[ContainerUtils::containerTypeToProtocolString(dockerContainer)] = containerConfig;
|
|
containers.replace(i, container);
|
|
config.insert(configKey::containers, containers);
|
|
}
|
|
}
|
|
}
|
|
|