diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index f32d525a..44afa713 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -167,7 +167,7 @@ bool AmneziaApplication::parseCommands() QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; m_parser.addOption(c_cleanup); - + m_parser.process(*this); if (m_parser.isSet(c_cleanup)) { @@ -179,9 +179,8 @@ bool AmneziaApplication::parseCommands() return true; } -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -void AmneziaApplication::startLocalServer() -{ +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) +void AmneziaApplication::startLocalServer() { const QString serverName("AmneziaVPNInstance"); QLocalServer::removeServer(serverName); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index ea5f6f52..aecc48af 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -6,7 +6,7 @@ #include #include #include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #include #else #include @@ -19,7 +19,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #define AMNEZIA_BASE_CLASS QGuiApplication #else #define AMNEZIA_BASE_CLASS QApplication @@ -37,7 +37,7 @@ public: void loadFonts(); bool parseCommands(); -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) void startLocalServer(); #endif diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index fafb7c2b..d02261da 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -7,7 +7,7 @@ #include #include #include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #include #else #include @@ -120,7 +120,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairisSitesSplitTunnelingEnabled()) { config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); #endif @@ -129,7 +129,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairrouteMode() == Settings::VpnAllExceptSites) { -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); // Prevent ipv6 leak config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index 308f5947..8e190103 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -8,7 +8,7 @@ #include #include #include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #include #else #include @@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr settings, const QShar QString SshConfigurator::convertOpenSShKey(const QString &key) { -#ifndef Q_OS_IOS +#if !defined(Q_OS_IOS) && !defined(MACOS_NE) QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); @@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key) #endif } +// DEAD CODE. void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) { -#ifndef Q_OS_IOS +#if !defined(Q_OS_IOS) && !defined(MACOS_NE) QProcess *p = new QProcess(); p->setProcessChannelMode(QProcess::SeparateChannels); @@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv() pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#elif defined(Q_OS_MACX) +#elif defined(Q_OS_MACX) && !defined(MACOS_NE) pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); #endif diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 52b148c0..35eec36c 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -275,7 +275,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #ifdef Q_OS_WINDOWS return true; -#elif defined(Q_OS_IOS) +#elif defined(Q_OS_IOS) || defined(MACOS_NE) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp new file mode 100644 index 00000000..02e704b0 --- /dev/null +++ b/client/core/controllers/apiController.cpp @@ -0,0 +1,509 @@ +#include "apiController.h" + +#include +#include + +#include +#include +#include +#include + +#include "QBlockCipher.h" +#include "QRsa.h" + +#include "amnezia_application.h" +#include "configurators/wireguard_configurator.h" +#include "core/enums/apiEnums.h" +#include "utilities.h" +#include "version.h" + +namespace +{ + namespace configKey + { + constexpr char cloak[] = "cloak"; + constexpr char awg[] = "awg"; + + constexpr char apiEdnpoint[] = "api_endpoint"; + constexpr char accessToken[] = "api_key"; + constexpr char certificate[] = "certificate"; + constexpr char publicKey[] = "public_key"; + constexpr char protocol[] = "protocol"; + + constexpr char uuid[] = "installation_uuid"; + constexpr char osVersion[] = "os_version"; + constexpr char appVersion[] = "app_version"; + + constexpr char userCountryCode[] = "user_country_code"; + constexpr char serverCountryCode[] = "server_country_code"; + constexpr char serviceType[] = "service_type"; + constexpr char serviceInfo[] = "service_info"; + + constexpr char aesKey[] = "aes_key"; + constexpr char aesIv[] = "aes_iv"; + constexpr char aesSalt[] = "aes_salt"; + + constexpr char apiPayload[] = "api_payload"; + constexpr char keyPayload[] = "key_payload"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; + } + + const int requestTimeoutMsecs = 12 * 1000; // 12 secs + + ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply) + { + if (!sslErrors.empty()) { + qDebug().noquote() << sslErrors; + return ErrorCode::ApiConfigSslError; + } else if (reply->error() == QNetworkReply::NoError) { + return ErrorCode::NoError; + } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + return ErrorCode::ApiConfigTimeoutError; + } else { + QString err = reply->errorString(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + return ErrorCode::ApiConfigDownloadError; + } + } + + bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "", + const QByteArray &iv = "", const QByteArray &salt = "") + { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + qDebug() << "Timeout occurred"; + return true; + } else if (responseBody.contains("html")) { + qDebug() << "The response contains an html tag"; + return true; + } else if (checkEncryption) { + try { + QSimpleCrypto::QBlockCipher blockCipher; + static_cast(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt)); + } catch (...) { + qDebug() << "Failed to decrypt the data"; + return true; + } + } + return false; + } +} + +ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent) + : QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment) +{ +} + +void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, + const QByteArray &apiResponseBody, QJsonObject &serverConfig) +{ + QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); + + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + if (ba.isEmpty()) { + emit errorOccurred(ErrorCode::ApiConfigEmptyError); + return; + } + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QString configStr = ba; + if (protocol == configKey::cloak) { + configStr.replace("", "\n"); + configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); + } else if (protocol == configKey::awg) { + configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); + if (containers.isEmpty()) { + return; // todo process error + } + auto container = containers.at(0).toObject(); + QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); + auto containerConfig = container.value(containerName).toObject(); + auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); + containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); + container[containerName] = containerConfig; + containers.replace(0, container); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); + } + + QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); + serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); + serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); + serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); + + if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); + serverConfig[config_key::description] = newServerConfig.value(config_key::description); + serverConfig[config_key::name] = newServerConfig.value(config_key::name); + } + + auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); + serverConfig[config_key::defaultContainer] = defaultContainer; + + QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); + map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); + auto apiConfig = QJsonObject::fromVariantMap(map); + + if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { + apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); + } + + serverConfig[configKey::apiConfig] = apiConfig; + + return; +} + +QStringList ApiController::getProxyUrls() +{ + QNetworkRequest request; + request.setTransferTimeout(requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QEventLoop wait; + QList sslErrors; + QNetworkReply *reply; + + QStringList proxyStorageUrl; + if (m_isDevEnvironment) { + proxyStorageUrl = QStringList { DEV_S3_ENDPOINT }; + } else { + proxyStorageUrl = QStringList { PROD_S3_ENDPOINT }; + } + + QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + + for (const auto &proxyStorageUrl : proxyStorageUrl) { + request.setUrl(proxyStorageUrl); + reply = amnApp->manager()->get(request); + + connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + if (reply->error() == QNetworkReply::NetworkError::NoError) { + break; + } + reply->deleteLater(); + } + + auto encryptedResponseBody = reply->readAll(); + reply->deleteLater(); + + EVP_PKEY *privateKey = nullptr; + QByteArray responseBody; + try { + if (!m_isDevEnvironment) { + QCryptographicHash hash(QCryptographicHash::Sha512); + hash.addData(key); + QByteArray hashResult = hash.result().toHex(); + + QByteArray key = QByteArray::fromHex(hashResult.left(64)); + QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32)); + + QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); + + QSimpleCrypto::QBlockCipher blockCipher; + responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv); + } else { + responseBody = encryptedResponseBody; + } + } catch (...) { + Utils::logException(); + qCritical() << "error loading private key from environment variables or decrypting payload"; + return {}; + } + + auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); + + QStringList endpoints; + for (const auto &endpoint : endpointsArray) { + endpoints.push_back(endpoint.toString()); + } + return endpoints; +} + +ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol) +{ + ApiController::ApiPayloadData apiPayload; + if (protocol == configKey::cloak) { + apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); + } else if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + apiPayload.wireGuardClientPubKey = connData.clientPubKey; + apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; + } + return apiPayload; +} + +QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData) +{ + QJsonObject obj; + if (protocol == configKey::cloak) { + obj[configKey::certificate] = apiPayloadData.certRequest.request; + } else if (protocol == configKey::awg) { + obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; + } + + obj[configKey::osVersion] = QSysInfo::productType(); + obj[configKey::appVersion] = QString(APP_VERSION); + + return obj; +} + +void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig) +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + if (serverConfig.value(config_key::configVersion).toInt()) { + QNetworkRequest request; + request.setTransferTimeout(requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8()); + QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString(); + request.setUrl(endpoint); + + QString protocol = serverConfig.value(configKey::protocol).toString(); + + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::uuid] = installationUuid; + + QByteArray requestBody = QJsonDocument(apiPayload).toJson(); + + QNetworkReply *reply = amnApp->manager()->post(request, requestBody); + + QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable { + if (reply->error() == QNetworkReply::NoError) { + auto apiResponseBody = reply->readAll(); + fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig); + emit finished(serverConfig, serverIndex); + } else { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + emit errorOccurred(ErrorCode::ApiConfigTimeoutError); + } else { + QString err = reply->errorString(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + emit errorOccurred(ErrorCode::ApiConfigDownloadError); + } + } + + reply->deleteLater(); + }); + + QObject::connect(reply, &QNetworkReply::errorOccurred, + [this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; }); + connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors) { + qDebug().noquote() << errors; + emit errorOccurred(ErrorCode::ApiConfigSslError); + }); + } +} + +ErrorCode ApiController::getServicesList(QByteArray &responseBody) +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + QNetworkRequest request; + request.setTransferTimeout(requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint)); + + QNetworkReply *reply; + reply = amnApp->manager()->get(request); + + QEventLoop wait; + QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + + QList sslErrors; + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + responseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) { + m_proxyUrls = getProxyUrls(); + std::random_device randomDevice; + std::mt19937 generator(randomDevice()); + std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator); + for (const QString &proxyUrl : m_proxyUrls) { + qDebug() << "Go to the next endpoint"; + request.setUrl(QString("%1v1/services").arg(proxyUrl)); + reply->deleteLater(); // delete the previous reply + reply = amnApp->manager()->get(request); + + QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + responseBody = reply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) { + break; + } + } + } + + auto errorCode = checkErrors(sslErrors, reply); + reply->deleteLater(); + + if (errorCode == ErrorCode::NoError) { + if (!responseBody.contains("services")) { + return ErrorCode::ApiServicesMissingError; + } + } + + return errorCode; +} + +ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, + QJsonObject &serverConfig) +{ +#if defined(Q_OS_IOS) || defined(MACOS_NE) + IosController::Instance()->requestInetAccess(); + QThread::msleep(10); +#endif + + QNetworkRequest request; + request.setTransferTimeout(requestTimeoutMsecs); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint)); + + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = userCountryCode; + if (!serverCountryCode.isEmpty()) { + apiPayload[configKey::serverCountryCode] = serverCountryCode; + } + apiPayload[configKey::serviceType] = serviceType; + apiPayload[configKey::uuid] = installationUuid; + if (!authData.isEmpty()) { + apiPayload[configKey::authData] = authData; + } + + QSimpleCrypto::QBlockCipher blockCipher; + QByteArray key = blockCipher.generatePrivateSalt(32); + QByteArray iv = blockCipher.generatePrivateSalt(32); + QByteArray salt = blockCipher.generatePrivateSalt(8); + + QJsonObject keyPayload; + keyPayload[configKey::aesKey] = QString(key.toBase64()); + keyPayload[configKey::aesIv] = QString(iv.toBase64()); + keyPayload[configKey::aesSalt] = QString(salt.toBase64()); + + QByteArray encryptedKeyPayload; + QByteArray encryptedApiPayload; + try { + QSimpleCrypto::QRsa rsa; + + EVP_PKEY *publicKey = nullptr; + try { + QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; + QSimpleCrypto::QRsa rsa; + publicKey = rsa.getPublicKeyFromByteArray(rsaKey); + } catch (...) { + Utils::logException(); + qCritical() << "error loading public key from environment variables"; + return ErrorCode::ApiMissingAgwPublicKey; + } + + encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING); + EVP_PKEY_free(publicKey); + + encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); + } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); + qCritical() << "error when encrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; + } + + QJsonObject requestBody; + requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); + requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); + + QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + + QEventLoop wait; + connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + + QList sslErrors; + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + auto encryptedResponseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { + m_proxyUrls = getProxyUrls(); + std::random_device randomDevice; + std::mt19937 generator(randomDevice()); + std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator); + for (const QString &proxyUrl : m_proxyUrls) { + qDebug() << "Go to the next endpoint"; + request.setUrl(QString("%1v1/config").arg(proxyUrl)); + reply->deleteLater(); // delete the previous reply + reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); + + QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); + wait.exec(); + + encryptedResponseBody = reply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { + break; + } + } + } + + auto errorCode = checkErrors(sslErrors, reply); + reply->deleteLater(); + if (errorCode) { + return errorCode; + } + + try { + auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); + fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); + } catch (...) { // todo change error handling in QSimpleCrypto? + Utils::logException(); + qCritical() << "error when decrypting the request body"; + return ErrorCode::ApiConfigDecryptionError; + } + + return errorCode; +} diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h new file mode 100644 index 00000000..90d49cb0 --- /dev/null +++ b/client/core/controllers/apiController.h @@ -0,0 +1,50 @@ +#ifndef APICONTROLLER_H +#define APICONTROLLER_H + +#include + +#include "configurators/openvpn_configurator.h" + +#if defined(Q_OS_IOS) || defined(MACOS_NE) + #include "platforms/ios/ios_controller.h" +#endif + +class ApiController : public QObject +{ + Q_OBJECT + +public: + explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr); + +public slots: + void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig); + + ErrorCode getServicesList(QByteArray &responseBody); + ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); + +signals: + void errorOccurred(ErrorCode errorCode); + void finished(const QJsonObject &config, const int serverIndex); + +private: + struct ApiPayloadData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + }; + + ApiPayloadData generateApiPayloadData(const QString &protocol); + QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData); + void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody, + QJsonObject &serverConfig); + QStringList getProxyUrls(); + + QString m_gatewayEndpoint; + QStringList m_proxyUrls; + bool m_isDevEnvironment = false; +}; + +#endif // APICONTROLLER_H diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index a5825f0d..284ad51f 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -22,7 +22,7 @@ #include #include #endif -#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) +#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #include #include #include @@ -378,7 +378,7 @@ QString NetworkUtilities::getGatewayAndIface() close(sock); return gateway_address; #endif -#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) +#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) QString gateway; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY}; int afinet_type[] = {AF_INET, AF_INET6}; diff --git a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 4ae7bded..12bf89be 100644 --- a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -1,10 +1,10 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "macos/gobridge/wireguard.h" + #include "wireguard-go-version.h" -#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include @@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex); bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]); void write_msg_to_log(const char* tag, const char* msg); + +// init function definition in C +void hev_socks5_tunnel_quit(void); +// Updated function definition in C +int hev_socks5_tunnel_main(const char* configFile, int fd); diff --git a/client/macos/networkextension/wireguard-go-version.h.in b/client/macos/networkextension/wireguard-go-version.h.in new file mode 100644 index 00000000..860bc3c3 --- /dev/null +++ b/client/macos/networkextension/wireguard-go-version.h.in @@ -0,0 +1,3 @@ +#ifndef WIREGUARD_GO_VERSION +#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@" +#endif // WIREGUARD_GO_VERSION \ No newline at end of file diff --git a/client/main.cpp b/client/main.cpp index aca9e62b..558553c5 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -11,11 +11,11 @@ #include "Windows.h" #endif -#if defined(Q_OS_IOS) +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) bool isAnotherInstanceRunning() { QLocalSocket socket; @@ -45,7 +45,7 @@ int main(int argc, char *argv[]) AmneziaApplication app(argc, argv); -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) if (isAnotherInstanceRunning()) { QTimer::singleShot(1000, &app, [&]() { app.quit(); }); return app.exec(); diff --git a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift index 3e0a4a07..bfd1165f 100644 --- a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift +++ b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift @@ -73,7 +73,7 @@ extension PacketTunnelProvider { startHandler = completionHandler ovpnAdapter?.connect(using: packetFlow) } - + func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } let bytesin = ovpnAdapter?.transportStatistics.bytesIn diff --git a/client/platforms/ios/PacketTunnelProvider+WireGuard.swift b/client/platforms/ios/PacketTunnelProvider+WireGuard.swift index 18200c7f..5d6e66de 100644 --- a/client/platforms/ios/PacketTunnelProvider+WireGuard.swift +++ b/client/platforms/ios/PacketTunnelProvider+WireGuard.swift @@ -112,9 +112,19 @@ extension PacketTunnelProvider { } } + let lastHandshakeString = settingsDictionary["last_handshake_time_sec"] + let lastHandshake: Int64 + + if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) { + lastHandshake = handshakeValue + } else { + lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec` + } + let response: [String: Any] = [ "rx_bytes": settingsDictionary["rx_bytes"] ?? "0", - "tx_bytes": settingsDictionary["tx_bytes"] ?? "0" + "tx_bytes": settingsDictionary["tx_bytes"] ?? "0", + "last_handshake_time_sec": lastHandshake ] completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) diff --git a/client/platforms/ios/QRCodeReaderBase.mm b/client/platforms/ios/QRCodeReaderBase.mm index af879e2f..963c35a8 100644 --- a/client/platforms/ios/QRCodeReaderBase.mm +++ b/client/platforms/ios/QRCodeReaderBase.mm @@ -1,3 +1,4 @@ +#if !MACOS_NE #include "QRCodeReaderBase.h" #import @@ -108,3 +109,19 @@ void QRCodeReader::startReading() { void QRCodeReader::stopReading() { [m_qrCodeReader stopReading]; } +#else +#include "QRCodeReaderBase.h" + +QRCodeReader::QRCodeReader() +{ + +} + +QRect QRCodeReader::cameraSize() { + return QRect(); +} + +void QRCodeReader::startReading() {} +void QRCodeReader::stopReading() {} +void QRCodeReader::setCameraSize(QRect) {} +#endif diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index c2c1d2d3..1668f4c3 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,5 +1,6 @@ +#if !MACOS_NE #import - +#endif @interface QIOSApplicationDelegate @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index bd7ad6b1..64ee9425 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -5,7 +5,7 @@ @implementation QIOSApplicationDelegate (AmneziaVPNDelegate) - +#if !MACOS_NE - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum]; @@ -57,5 +57,5 @@ } return NO; } - +#endif @end diff --git a/client/platforms/ios/ScreenProtection.swift b/client/platforms/ios/ScreenProtection.swift index 200cf0cb..98758f30 100644 --- a/client/platforms/ios/ScreenProtection.swift +++ b/client/platforms/ios/ScreenProtection.swift @@ -1,3 +1,13 @@ +#if MACOS_NE +public func toggleScreenshots(_ isEnabled: Bool) { + +} + +class ScreenProtection { + + +} +#else import UIKit public func toggleScreenshots(_ isEnabled: Bool) { @@ -90,3 +100,4 @@ struct ProtectionPair { textField.removeFromSuperview() } } +#endif diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 85580769..7e815bde 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -46,6 +46,7 @@ public: void disconnectVpn(); void vpnStatusDidChange(void *pNotification); + void vpnConfigurationDidChange(void *pNotification); void getBackendLogs(std::function &&callback); diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 85fb50b7..9d7525ce 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -27,6 +27,7 @@ const char* MessageKey::isOnDemand = "is-on-demand"; const char* MessageKey::SplitTunnelType = "SplitTunnelType"; const char* MessageKey::SplitTunnelSites = "SplitTunnelSites"; +#if !MACOS_NE static UIViewController* getViewController() { NSArray *windows = [[UIApplication sharedApplication]windows]; for (UIWindow *window in windows) { @@ -36,6 +37,7 @@ static UIViewController* getViewController() { } return nil; } +#endif Vpn::ConnectionState iosStatusToState(NEVPNStatus status) { switch (status) { @@ -249,6 +251,19 @@ void IosController::checkStatus() sendVpnExtensionMessage(message, [&](NSDictionary* response){ uint64_t txBytes = [response[@"tx_bytes"] intValue]; uint64_t rxBytes = [response[@"rx_bytes"] intValue]; + + uint64_t last_handshake_time_sec = 0; + if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) { + last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue]; + } else { + qDebug() << "Key last_handshake_time_sec is missing or null"; + } + + if (last_handshake_time_sec < 0) { + disconnectVpn(); + qDebug() << "Invalid handshake time, disconnecting VPN."; + } + emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes); m_rxBytes = rxBytes; m_txBytes = txBytes; @@ -789,14 +804,14 @@ bool IosController::shareText(const QStringList& filesToSend) { NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()]; [sharingItems addObject:logFileUrl]; } - +#if !MACOS_NE UIViewController *qtController = getViewController(); if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; - +#endif __block bool isAccepted = false; - +#if !MACOS_NE [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { isAccepted = completed; emit finished(); @@ -808,7 +823,7 @@ bool IosController::shareText(const QStringList& filesToSend) { popController.sourceView = qtController.view; popController.sourceRect = CGRectMake(100, 100, 100, 100); } - +#endif QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); @@ -817,6 +832,7 @@ bool IosController::shareText(const QStringList& filesToSend) { } QString IosController::openFile() { +#if !MACOS_NE UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init]; @@ -826,9 +842,9 @@ QString IosController::openFile() { if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; - +#endif __block QString filePath; - +#if !MACOS_NE documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { if (path) { filePath = QString::fromUtf8(path.UTF8String); @@ -837,7 +853,7 @@ QString IosController::openFile() { } emit finished(); }; - +#endif QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); diff --git a/client/platforms/ios/ios_controller_wrapper.h b/client/platforms/ios/ios_controller_wrapper.h index f0333d77..ab325154 100644 --- a/client/platforms/ios/ios_controller_wrapper.h +++ b/client/platforms/ios/ios_controller_wrapper.h @@ -1,7 +1,11 @@ #import #import #import + +#if !MACOS_NE #include +#endif + #include class IosController; @@ -17,9 +21,10 @@ class IosController; @end typedef void (^DocumentPickerClosedCallback)(NSString *path); - +#if !MACOS_NE @interface DocumentPickerDelegate : NSObject @property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback; @end +#endif diff --git a/client/platforms/ios/ios_controller_wrapper.mm b/client/platforms/ios/ios_controller_wrapper.mm index 1f8c938f..38eb2d22 100644 --- a/client/platforms/ios/ios_controller_wrapper.mm +++ b/client/platforms/ios/ios_controller_wrapper.mm @@ -26,7 +26,8 @@ @end -@implementation DocumentPickerDelegate +#if !MACOS_NE +@implementation DocumentPickerDelegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { for (NSURL *url in urls) { @@ -42,4 +43,5 @@ } } -@end \ No newline at end of file +@end +#endif diff --git a/client/platforms/ios/iosnotificationhandler.mm b/client/platforms/ios/iosnotificationhandler.mm index efa48385..773c6297 100644 --- a/client/platforms/ios/iosnotificationhandler.mm +++ b/client/platforms/ios/iosnotificationhandler.mm @@ -6,6 +6,8 @@ #import #import + +#if !MACOS_NE #import @interface IOSNotificationDelegate @@ -87,3 +89,86 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt } }]; } +#else + +// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS +@interface IOSNotificationDelegate + : NSObject { + IOSNotificationHandler* m_iosNotificationHandler; +} +@end + +@implementation IOSNotificationDelegate + +- (id)initWithObject:(IOSNotificationHandler*)notification { + self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific + if (self) { + m_iosNotificationHandler = notification; + } + return self; +} + +- (void)userNotificationCenter:(UNUserNotificationCenter*)center + willPresentNotification:(UNNotification*)notification + withCompletionHandler: + (void (^)(UNNotificationPresentationOptions options))completionHandler { + Q_UNUSED(center) + completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner); +} + +- (void)userNotificationCenter:(UNUserNotificationCenter*)center + didReceiveNotificationResponse:(UNNotificationResponse*)response + withCompletionHandler:(void (^)())completionHandler { + Q_UNUSED(center) + Q_UNUSED(response) + completionHandler(); +} +@end + +IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) { + + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | + UNAuthorizationOptionBadge) + completionHandler:^(BOOL granted, NSError* _Nullable error) { + Q_UNUSED(granted); + if (!error) { + m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this]; + } + }]; +} + +IOSNotificationHandler::~IOSNotificationHandler() { } + +void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title, + const QString& message, int timerMsec) { + Q_UNUSED(type); + + if (!m_delegate) { + return; + } + + UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; + content.title = title.toNSString(); + content.body = message.toNSString(); + content.sound = [UNNotificationSound defaultSound]; + + int timerSec = timerMsec / 1000; + UNTimeIntervalNotificationTrigger* trigger = + [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO]; + + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn" + content:content + trigger:trigger]; + + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = (id)m_delegate; + + [center addNotificationRequest:request + withCompletionHandler:^(NSError* _Nullable error) { + if (error) { + NSLog(@"Local Notification failed"); + } + }]; +} +#endif diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 865edae4..881d1633 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -177,7 +177,7 @@ namespace amnezia constexpr char defaultPort[] = "51820"; -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) constexpr char defaultMtu[] = "1280"; #else constexpr char defaultMtu[] = "1376"; @@ -197,7 +197,7 @@ namespace amnezia namespace awg { constexpr char defaultPort[] = "55424"; -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) constexpr char defaultMtu[] = "1280"; #else constexpr char defaultMtu[] = "1376"; diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 056089b8..4b3edca5 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -4,7 +4,7 @@ #include "core/errorstrings.h" #include "vpnprotocol.h" -#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) +#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) #include "openvpnovercloakprotocol.h" #include "openvpnprotocol.h" #include "shadowsocksvpnprotocol.h" @@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & #if defined(Q_OS_WINDOWS) case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); #endif -#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) +#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 9fc60493..c85367da 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -1,6 +1,6 @@ #include "connectionController.h" -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #include #else #include @@ -32,8 +32,9 @@ ConnectionController::ConnectionController(const QSharedPointer &s void ConnectionController::openConnection() { -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) { +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) + if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) + { emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); return; } diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 1bba0e8a..3abff8ee 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -18,7 +18,7 @@ #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include #endif @@ -570,7 +570,7 @@ void ImportController::startDecodingQr() m_totalQrCodeChunksCount = 0; m_receivedQrCodeChunksCount = 0; - #if defined Q_OS_IOS + #if defined(Q_OS_IOS) || defined(MACOS_NE) m_isQrCodeProcessed = true; #endif #if defined Q_OS_ANDROID diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 12dc5e01..2ded5f55 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -2,7 +2,7 @@ #include "utils/converter.h" #include "core/errorstrings.h" -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #include #else #include @@ -11,7 +11,7 @@ #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #endif -#if defined Q_OS_MAC +#if defined Q_OS_MAC && !defined(MACOS_NE) #include "ui/macos_util.h" #endif @@ -114,7 +114,7 @@ void PageController::showOnStartup() } else { #if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) emit hideMainWindow(); -#elif defined Q_OS_MACX +#elif defined Q_OS_MACX and !defined MACOS_NE setDockIconVisible(false); #endif } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f4e3d83d..29311f1e 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -10,7 +10,7 @@ #include "platforms/android/android_controller.h" #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include #endif @@ -76,7 +76,7 @@ bool SettingsController::isLoggingEnabled() void SettingsController::toggleLogging(bool enable) { m_settings->setSaveLogs(enable); -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) AmneziaVPN::toggleLogging(enable); #endif if (enable == true) { @@ -131,8 +131,12 @@ void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::restoreAppConfig(const QString &fileName) { - QByteArray data; - SystemController::readFile(fileName, data); + QFile file(fileName); + + file.open(QIODevice::ReadOnly); + + QByteArray data = file.readAll(); + restoreAppConfigFromData(data); } @@ -169,7 +173,7 @@ void SettingsController::clearSettings() emit changeSettingsFinished(tr("All settings have been reset to default values")); -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) AmneziaVPN::clearSettings(); #endif } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 52ca1294..f8b47350 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -14,7 +14,7 @@ #include "platforms/android/android_controller.h" #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include "platforms/ios/ios_controller.h" #include #endif diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 7cde28b4..85616b13 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -4,7 +4,7 @@ #include "core/controllers/serverController.h" #include "core/networkUtilities.h" -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include #endif @@ -760,7 +760,7 @@ void ServersModel::removeApiConfig(const int serverIndex) { auto serverConfig = getServerConfig(serverIndex); -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) QString vpncName = QString("%1 (%2) %3") .arg(serverConfig[config_key::description].toString()) .arg(serverConfig[config_key::hostName].toString()) diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index 5efb45c4..3dea619c 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -5,15 +5,16 @@ #include #include "notificationhandler.h" -#if defined(Q_OS_IOS) +#if defined(Q_OS_IOS) || defined(MACOS_NE) # include "platforms/ios/iosnotificationhandler.h" #else # include "systemtray_notificationhandler.h" #endif + // static NotificationHandler* NotificationHandler::create(QObject* parent) { -#if defined(Q_OS_IOS) +#if defined(Q_OS_IOS) || defined(MACOS_NE) return new IOSNotificationHandler(parent); #else diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 38a1da52..b20b13ff 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -25,29 +25,32 @@ PageType { } } - ListView { - id: listView + defaultActiveFocusItem: focusItem - anchors.fill: parent + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.height - property bool isFocusable: true + ColumnLayout { + id: content - ScrollBar.vertical: ScrollBarType {} + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - model: variants + spacing: 0 - clip: true + Item { + id: focusItem + KeyNavigation.tab: textKey.textField + } - reuseItems: true - - header: ColumnLayout { - width: listView.width HeaderType { - id: moreButton - property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() - + Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 @@ -57,7 +60,7 @@ PageType { actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonFunction: function() { - moreActionsDrawer.openTriggered() + moreActionsDrawer.open() } DrawerType2 { @@ -68,7 +71,7 @@ PageType { anchors.fill: parent expandedHeight: root.height * 0.5 - expandedStateContent: ColumnLayout { + expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -128,8 +131,6 @@ PageType { } ParagraphTextType { - objectName: "insertKeyLabel" - Layout.fillWidth: true Layout.topMargin: 32 Layout.rightMargin: 16 @@ -153,6 +154,8 @@ PageType { textField.text = "" textField.paste() } + + KeyNavigation.tab: continueButton } BasicButtonType { @@ -163,12 +166,13 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - visible: textKey.textField.text !== "" + visible: textKey.textFieldText !== "" text: qsTr("Continue") + Keys.onTabPressed: lastItemTabClicked(focusItem) clickedFunc: function() { - if (ImportController.extractConfigFromData(textKey.textField.text)) { + if (ImportController.extractConfigFromData(textKey.textFieldText)) { PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -184,157 +188,143 @@ PageType { color: AmneziaStyle.color.charcoalGray text: qsTr("Other connection options") } - } - - delegate: ColumnLayout { - width: listView.width CardWithIconsType { + id: apiInstalling + Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 Layout.bottomMargin: 16 - visible: isVisible - - headerText: title - bodyText: description + headerText: qsTr("VPN by Amnezia") + bodyText: qsTr("Connect to classic paid and free VPN services from Amnezia") rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: imageSource + leftImageSource: "qrc:/images/controls/amnezia.svg" - onClicked: { handler() } + onClicked: function() { + PageController.showBusyIndicator(true) + var result = InstallController.fillAvailableServices() + PageController.showBusyIndicator(false) + if (result) { + PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) + } + } } - } - footer: ColumnLayout { - width: listView.width + CardWithIconsType { + id: manualInstalling - BasicButtonType { - id: siteLink2 - Layout.topMargin: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 Layout.bottomMargin: 16 - Layout.alignment: Qt.AlignHCenter - implicitHeight: 32 - visible: Qt.platform.os !== "ios" + headerText: qsTr("Self-hosted VPN") + bodyText: qsTr("Configure Amnezia VPN on your own server") - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.goldenApricot + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/server.svg" - text: qsTr("Site Amnezia") + onClicked: { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + } + } - rightImageSource: "qrc:/images/controls/external-link.svg" + CardWithIconsType { + id: backupRestore - clickedFunc: function() { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + visible: PageController.isStartPageVisible() + + headerText: qsTr("Restore from backup") + + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/archive-restore.svg" + + onClicked: { + var filePath = SystemController.getFileName(qsTr("Open backup file"), + qsTr("Backup files (*.backup)")) + if (filePath !== "") { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } + } + } + + CardWithIconsType { + id: openFile + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + headerText: qsTr("File with connection settings") + + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/folder-search-2.svg" + + onClicked: { + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : + "Config files (*.vpn *.ovpn *.conf *.json)" + var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) + if (fileName !== "") { + if (ImportController.extractConfigFromFile(fileName)) { + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + } + } + + CardWithIconsType { + id: scanQr + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + visible: (Qt.platform.os === "android" || Qt.platform.os === "ios") && SettingsController.isCameraPresent() + + headerText: qsTr("QR code") + + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/scan-line.svg" + + onClicked: { + ImportController.startDecodingQr() + if (Qt.platform.os === "ios") { + PageController.goToPage(PageEnum.PageSetupWizardQrReader) + } + } + } + + CardWithIconsType { + id: siteLink + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + + visible: PageController.isStartPageVisible() + + headerText: qsTr("I have nothing") + + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/help-circle.svg" + + onClicked: { Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) } } } } - - property list variants: [ - amneziaVpn, - selfHostVpn, - backupRestore, - fileOpen, - qrScan, - siteLink - ] - - QtObject { - id: amneziaVpn - - property string title: qsTr("VPN by Amnezia") - property string description: qsTr("Connect to classic paid and free VPN services from Amnezia") - property string imageSource: "qrc:/images/controls/amnezia.svg" - property bool isVisible: true - property var handler: function() { - PageController.showBusyIndicator(true) - var result = ApiConfigsController.fillAvailableServices() - PageController.showBusyIndicator(false) - if (result) { - PageController.goToPage(PageEnum.PageSetupWizardApiServicesList) - } - } - } - - QtObject { - id: selfHostVpn - - property string title: qsTr("Self-hosted VPN") - property string description: qsTr("Configure Amnezia VPN on your own server") - property string imageSource: "qrc:/images/controls/server.svg" - property bool isVisible: true - property var handler: function() { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) - } - } - - QtObject { - id: backupRestore - - property string title: qsTr("Restore from backup") - property string description: qsTr("") - property string imageSource: "qrc:/images/controls/archive-restore.svg" - property bool isVisible: PageController.isStartPageVisible() - property var handler: function() { - var filePath = SystemController.getFileName(qsTr("Open backup file"), - qsTr("Backup files (*.backup)")) - if (filePath !== "") { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(filePath) - PageController.showBusyIndicator(false) - } - } - } - - QtObject { - id: fileOpen - - property string title: qsTr("File with connection settings") - property string description: qsTr("") - property string imageSource: "qrc:/images/controls/folder-search-2.svg" - property bool isVisible: true - property var handler: function() { - var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" : - "Config files (*.vpn *.ovpn *.conf *.json)" - var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) - if (fileName !== "") { - if (ImportController.extractConfigFromFile(fileName)) { - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } - } - } - - QtObject { - id: qrScan - - property string title: qsTr("QR code") - property string description: qsTr("") - property string imageSource: "qrc:/images/controls/scan-line.svg" - property bool isVisible: SettingsController.isCameraPresent() - property var handler: function() { - ImportController.startDecodingQr() - if (Qt.platform.os === "ios") { - PageController.goToPage(PageEnum.PageSetupWizardQrReader) - } - } - } - - QtObject { - id: siteLink - - property string title: qsTr("I have nothing") - property string description: qsTr("") - property string imageSource: "qrc:/images/controls/help-circle.svg" - property bool isVisible: PageController.isStartPageVisible() && Qt.platform.os !== "ios" - property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - } } diff --git a/client/utilities.cpp b/client/utilities.cpp index 61944e51..51a9885a 100755 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -190,7 +190,7 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag) CloseHandle(hSnapshot); return false; -#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) +#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(MACOS_NE) return false; #else QProcess process; diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 042c51c7..7e22d73f 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -22,7 +22,7 @@ #include "platforms/android/android_controller.h" #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) #include "platforms/ios/ios_controller.h" #endif @@ -33,7 +33,7 @@ VpnConnection::VpnConnection(std::shared_ptr settings, QObject *parent : QObject(parent), m_settings(settings), m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); @@ -101,7 +101,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) } #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); } else { @@ -215,10 +215,11 @@ ErrorCode VpnConnection::lastError() const void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration) { - qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2") + qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") .arg(serverIndex) - .arg(ContainerProps::containerToString(container)); -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + .arg(ContainerProps::containerToString(container)) + << m_settings->routeMode(); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); } @@ -249,7 +250,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede appendSplitTunnelingConfig(); -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { emit connectionStateChanged(Vpn::ConnectionState::Error); @@ -261,7 +262,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede createAndroidConnections(); m_vpnProtocol.reset(androidVpnProtocol); -#elif defined Q_OS_IOS +#elif defined Q_OS_IOS || defined(MACOS_NE) Proto proto = ContainerProps::defaultProtocol(container); IosController::Instance()->connectVpn(proto, m_vpnConfiguration); connect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); @@ -340,26 +341,26 @@ void VpnConnection::appendSplitTunnelingConfig() } } - Settings::RouteMode sitesRouteMode = Settings::RouteMode::VpnAllSites; + Settings::RouteMode routeMode = Settings::RouteMode::VpnAllSites; QJsonArray sitesJsonArray; if (m_settings->isSitesSplitTunnelingEnabled()) { - sitesRouteMode = m_settings->routeMode(); + routeMode = m_settings->routeMode(); if (allowSiteBasedSplitTunneling) { - auto sites = m_settings->getVpnIps(sitesRouteMode); + auto sites = m_settings->getVpnIps(routeMode); for (const auto &site : sites) { sitesJsonArray.append(site); } // Allow traffic to Amnezia DNS - if (sitesRouteMode == Settings::VpnOnlyForwardSites) { + if (routeMode == Settings::VpnOnlyForwardSites) { sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString()); sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString()); } } } - m_vpnConfiguration.insert(config_key::splitTunnelType, sitesRouteMode); + m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps; @@ -375,13 +376,6 @@ void VpnConnection::appendSplitTunnelingConfig() m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode); m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray); - - qDebug() << QString("Site split tunneling is %1, route mode is %2") - .arg(m_settings->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled") - .arg(sitesRouteMode); - qDebug() << QString("App split tunneling is %1, route mode is %2") - .arg(m_settings->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled") - .arg(appsRouteMode); } #ifdef Q_OS_ANDROID @@ -443,7 +437,7 @@ void VpnConnection::disconnectFromVpn() } #endif -#ifdef Q_OS_IOS +#if defined(Q_OS_IOS) || defined(MACOS_NE) IosController::Instance()->disconnectVpn(); disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); #endif