From 1f08d78b43f6e3321e6ccb0c9bd97863ac0f04e4 Mon Sep 17 00:00:00 2001 From: Pokamest Nikak Date: Sun, 22 Sep 2024 22:52:59 +0100 Subject: [PATCH 01/27] wip --- client/ui/controllers/installController.cpp | 27 +--- client/utilities.cpp | 139 ++++++++++++++++---- client/utilities.h | 6 +- 3 files changed, 120 insertions(+), 52 deletions(-) mode change 100644 => 100755 client/ui/controllers/installController.cpp mode change 100644 => 100755 client/utilities.cpp mode change 100644 => 100755 client/utilities.h diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp old mode 100644 new mode 100755 index c6f17057..5a64d092 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -33,31 +33,6 @@ namespace constexpr char apiConfig[] = "api_config"; } - -#ifdef Q_OS_WINDOWS - QString getNextDriverLetter() - { - QProcess drivesProc; - drivesProc.start("wmic logicaldisk get caption"); - drivesProc.waitForFinished(); - QString drives = drivesProc.readAll(); - qDebug() << drives; - - QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; - QString letter; - for (int i = letters.size() - 1; i > 0; i--) { - letter = letters.at(i); - if (!drives.contains(letter + ":")) - break; - } - if (letter == "C:") { - // set err info - qDebug() << "Can't find free drive letter"; - return ""; - } - return letter; - } -#endif } InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -667,7 +642,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw QString hostname = serverCredentials.hostName; #ifdef Q_OS_WINDOWS - mountPath = getNextDriverLetter() + ":"; + mountPath = Utils::getNextDriverLetter() + ":"; // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") // .arg(labelTftpUserNameText()) // .arg(labelTftpPortText()) diff --git a/client/utilities.cpp b/client/utilities.cpp old mode 100644 new mode 100755 index 4047365f..bcae2ed5 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -10,7 +10,62 @@ #include #include "utilities.h" -#include "version.h" + +#ifdef Q_OS_WINDOWS +QString printErrorMessage(DWORD errorCode) { + LPVOID lpMsgBuf; + + DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + + DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + + FormatMessageW( + dwFlags, + NULL, + errorCode, + dwLanguageId, + (LPWSTR)&lpMsgBuf, + 0, + NULL + ); + + QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf); + LocalFree(lpMsgBuf); + return errorMsg.trimmed(); +} + +QString Utils::getNextDriverLetter() +{ + DWORD drivesBitmask = GetLogicalDrives(); + if (drivesBitmask == 0) { + DWORD error = GetLastError(); + qDebug() << "GetLogicalDrives failed. Error code:" << error; + return ""; + } + + QString letters = "FGHIJKLMNOPQRSTUVWXYZ"; + QString availableLetter; + + for (int i = letters.size() - 1; i >= 0; --i) { + QChar letterChar = letters.at(i); + int driveIndex = letterChar.toLatin1() - 'A'; + + if ((drivesBitmask & (1 << driveIndex)) == 0) { + availableLetter = letterChar; + break; + } + } + + if (availableLetter.isEmpty()) { + qDebug() << "Can't find free drive letter"; + return ""; + } + + return availableLetter; +} +#endif QString Utils::getRandomString(int len) { @@ -109,30 +164,34 @@ QString Utils::usrExecutable(const QString &baseName) bool Utils::processIsRunning(const QString &fileName, const bool fullFlag) { #ifdef Q_OS_WIN - QProcess process; - process.setReadChannel(QProcess::StandardOutput); - process.setProcessChannelMode(QProcess::MergedChannels); - process.start("wmic.exe", - QStringList() << "/OUTPUT:STDOUT" - << "PROCESS" - << "get" - << "Caption"); - process.waitForStarted(); - process.waitForFinished(); - QString processData(process.readAll()); - QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); - foreach (const QString &rawLine, processList) { - const QString line = rawLine.simplified(); - if (line.isEmpty()) { - continue; - } + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) { + qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot"; + return false; + } - if (line == fileName) { + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + if (!Process32FirstW(hSnapshot, &pe32)) { + CloseHandle(hSnapshot); + qWarning() << "Utils::processIsRunning error Process32FirstW"; + return false; + } + + do { + QString exeFile = QString::fromWCharArray(pe32.szExeFile); + + if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) { + CloseHandle(hSnapshot); return true; } - } + } while (Process32NextW(hSnapshot, &pe32)); + + CloseHandle(hSnapshot); return false; -#elif defined(Q_OS_IOS) + +#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) return false; #else QProcess process; @@ -150,12 +209,44 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag) #endif } -void Utils::killProcessByName(const QString &name) +bool Utils::killProcessByName(const QString &name) { qDebug().noquote() << "Kill process" << name; #ifdef Q_OS_WIN - QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F"); -#elif defined Q_OS_IOS + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + return false; + + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + bool success = false; + + if (Process32FirstW(hSnapshot, &pe32)) { + do { + QString exeFile = QString::fromWCharArray(pe32.szExeFile); + + if (exeFile.compare(name, Qt::CaseInsensitive) == 0) { + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID); + if (hProcess != NULL) { + if (TerminateProcess(hProcess, 0)) { + success = true; + } else { + DWORD error = GetLastError(); + qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error); + } + CloseHandle(hProcess); + } else { + DWORD error = GetLastError(); + qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error); + } + } + } while (Process32NextW(hSnapshot, &pe32)); + } + + CloseHandle(hSnapshot); + return success; +#elif defined Q_OS_IOS || defined(Q_OS_ANDROID) return; #else QProcess::execute(QString("pkill %1").arg(name)); diff --git a/client/utilities.h b/client/utilities.h old mode 100644 new mode 100755 index 9bf8c82a..b3e3b50b --- a/client/utilities.h +++ b/client/utilities.h @@ -7,7 +7,8 @@ #include #ifdef Q_OS_WIN - #include "Windows.h" +#include +#include #endif class Utils : public QObject @@ -27,7 +28,7 @@ public: static bool initializePath(const QString &path); static bool processIsRunning(const QString &fileName, const bool fullFlag = false); - static void killProcessByName(const QString &name); + static bool killProcessByName(const QString &name); static QString openVpnExecPath(); static QString wireguardExecPath(); @@ -36,6 +37,7 @@ public: #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); + static QString getNextDriverLetter(); #endif }; From 3aa8a46f6e36ea62a0d3f010ed7a05d72aa55188 Mon Sep 17 00:00:00 2001 From: Pokamest Nikak Date: Mon, 23 Sep 2024 01:19:46 +0300 Subject: [PATCH 02/27] wip --- client/ui/controllers/connectionController.cpp | 14 +++++++------- client/utilities.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index c7f95000..db8beed1 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -34,13 +34,13 @@ 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)) -// { -// emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); -// return; -// } -// #endif +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) + { + emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning); + return; + } +#endif int serverIndex = m_serversModel->getDefaultServerIndex(); QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); diff --git a/client/utilities.cpp b/client/utilities.cpp index bcae2ed5..ed91f5fc 100755 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -247,7 +247,7 @@ bool Utils::killProcessByName(const QString &name) CloseHandle(hSnapshot); return success; #elif defined Q_OS_IOS || defined(Q_OS_ANDROID) - return; + return false; #else QProcess::execute(QString("pkill %1").arg(name)); #endif From 1542adba82a0f7f4f48e381406799c86c90d7bec Mon Sep 17 00:00:00 2001 From: Pokamest Nikak Date: Mon, 23 Sep 2024 00:44:25 +0100 Subject: [PATCH 03/27] Switched to secure PRNG & some pw len increased --- client/ui/controllers/installController.cpp | 4 ++-- client/utilities.cpp | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index c6f17057..0126a5b0 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -135,10 +135,10 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; } else if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); + containerConfig.insert(config_key::password, Utils::getRandomString(16)); } else if (container == DockerContainer::Socks5Proxy) { containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); + containerConfig.insert(config_key::password, Utils::getRandomString(16)); } config.insert(config_key::container, ContainerProps::containerToString(container)); diff --git a/client/utilities.cpp b/client/utilities.cpp index 4047365f..cb50235a 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -14,14 +14,13 @@ QString Utils::getRandomString(int len) { - const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - + const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString randomString; + for (int i = 0; i < len; ++i) { - quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length(); - QChar nextChar = possibleCharacters.at(index); - randomString.append(nextChar); + randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length()))); } + return randomString; } From 2763da960f608dd2b6b27b7751f6dd63d1ba86c0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 2 Oct 2024 13:20:16 +0800 Subject: [PATCH 04/27] chore: added clear() after extractConfigFromData() on android --- client/amnezia_application.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2d06b443..4e25097d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -111,10 +111,11 @@ void AmneziaApplication::init() qFatal("Android controller initialization failed"); } - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { - m_pageController->goToPageHome(); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); + data.clear(); + emit m_pageController->goToPageViewConfig(); }); m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider); @@ -122,16 +123,16 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { - m_pageController->goToPageHome(); + connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { + emit m_pageController->goToPageHome(); m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); + emit m_pageController->goToPageViewConfig(); }); - connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { - m_pageController->goToPageHome(); + connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) { + emit m_pageController->goToPageHome(); m_pageController->goToPageSettingsBackup(); - m_settingsController->importBackupFromOutside(filePath); + emit m_settingsController->importBackupFromOutside(filePath); }); QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); }); From dce08b3eccec04007e30ec8a8f2187106ebc1472 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 6 Oct 2024 13:19:06 +0800 Subject: [PATCH 05/27] added processing of auth_data section when requesting api config - fixed saving api_config section when processing backend response --- client/core/controllers/apiController.cpp | 40 ++++++++++++------- client/core/controllers/apiController.h | 2 +- .../ui/controllers/connectionController.cpp | 3 ++ client/ui/controllers/importController.cpp | 3 +- client/ui/controllers/installController.cpp | 13 +++--- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 5cdaa7ae..47197ad9 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -40,6 +40,9 @@ namespace constexpr char apiPayload[] = "api_payload"; constexpr char keyPayload[] = "key_payload"; + + constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; } const QStringList proxyStorageUrl = { "" }; @@ -94,8 +97,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); } else if (protocol == configKey::awg) { configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = serverConfig.value(config_key::containers).toArray(); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); if (containers.isEmpty()) { return; // todo process error } @@ -114,25 +117,30 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); container[containerName] = containerConfig; containers.replace(0, container); - serverConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(serverConfig).toJson()); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); } - QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2); - serverConfig[config_key::containers] = apiConfig.value(config_key::containers); - serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName); + 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 (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) { - serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion); - serverConfig[config_key::description] = apiConfig.value(config_key::description); - serverConfig[config_key::name] = apiConfig.value(config_key::name); + 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 = apiConfig.value(config_key::defaultContainer).toString(); + 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); + serverConfig[configKey::apiConfig] = apiConfig; + return; } @@ -316,7 +324,8 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody) } ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig) + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, + QJsonObject &serverConfig) { #ifdef Q_OS_IOS IosController::Instance()->requestInetAccess(); @@ -339,6 +348,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co } apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::uuid] = installationUuid; + apiPayload[configKey::authData] = authData; QSimpleCrypto::QBlockCipher blockCipher; QByteArray key = blockCipher.generatePrivateSalt(32); diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h index 1f811498..bcb25f96 100644 --- a/client/core/controllers/apiController.h +++ b/client/core/controllers/apiController.h @@ -21,7 +21,7 @@ public slots: ErrorCode getServicesList(QByteArray &responseBody); ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType, - const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig); + const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig); signals: void errorOccurred(ErrorCode errorCode); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index c7f95000..a51556a1 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -51,6 +51,9 @@ void ConnectionController::openConnection() if (configVersion == ApiConfigSources::Telegram && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { emit updateApiConfigFromTelegram(); + } else if (configVersion == ApiConfigSources::AmneziaGateway + && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { + emit updateApiConfigFromGateway(); } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { qDebug() << "attempt to update api config by end_date event"; if (configVersion == ApiConfigSources::Telegram) { diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 261551ea..2b7681eb 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -39,11 +39,12 @@ namespace 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) + } else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) && config.contains(amneziaConfigPatternPassword))) { return ConfigTypes::Amnezia; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index c6f17057..628ea59d 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -32,6 +32,7 @@ namespace constexpr char availableCountries[] = "available_countries"; constexpr char apiConfig[] = "api_config"; + constexpr char authData[] = "auth_data"; } #ifdef Q_OS_WINDOWS @@ -826,7 +827,7 @@ bool InstallController::installServiceFromApi() ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig); + m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig); if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorCode); return false; @@ -853,19 +854,19 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); + auto authData = serverConfig.value(configKey::authData).toObject(); QJsonObject newServerConfig; - ErrorCode errorCode = - apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), - apiConfig.value(configKey::serviceType).toString(), - apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig); + ErrorCode errorCode = apiController.getConfigForService( + m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(), + apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, + authData, newServerConfig); if (errorCode != ErrorCode::NoError) { emit installationErrorOccurred(errorCode); return false; } QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); - newApiConfig.insert(configKey::serviceInfo, apiConfig.value(configKey::serviceInfo)); newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType)); newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); From 399a8c6d287504cfe26ea3a939041da30fed211e Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 11 Oct 2024 05:58:30 +0400 Subject: [PATCH 06/27] bugfix: fixed qml warnings when loading user list on PageShare (#1119) --- client/ui/models/clientManagementModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 7d3be2cb..7445d60f 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -77,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co { beginResetModel(); m_clientsTable = QJsonArray(); + endResetModel(); ErrorCode error = ErrorCode::NoError; @@ -90,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error); if (error != ErrorCode::NoError) { logger.error() << "Failed to get the clientsTable file from the server"; - endResetModel(); return error; } + beginResetModel(); m_clientsTable = QJsonDocument::fromJson(clientsTableString).array(); if (m_clientsTable.isEmpty()) { @@ -601,5 +602,6 @@ QHash ClientManagementModel::roleNames() const roles[LatestHandshakeRole] = "latestHandshake"; roles[DataReceivedRole] = "dataReceived"; roles[DataSentRole] = "dataSent"; + roles[AllowedIpsRole] = "allowedIps"; return roles; } From 694e781beb8e2d065dfb6d5a7ec1b24c1873727d Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 11 Oct 2024 05:58:53 +0400 Subject: [PATCH 07/27] bugfix: fixed path to log folder for wireguard on windows (#1137) --- client/platforms/windows/windowscommons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/platforms/windows/windowscommons.cpp b/client/platforms/windows/windowscommons.cpp index c0a14dda..4c0d8176 100644 --- a/client/platforms/windows/windowscommons.cpp +++ b/client/platforms/windows/windowscommons.cpp @@ -21,7 +21,7 @@ #include "platforms/windows/windowsutils.h" constexpr const char* VPN_NAME = "AmneziaVPN"; -constexpr const char* WIREGUARD_DIR = "WireGuard"; +constexpr const char* WIREGUARD_DIR = "AmneziaWG"; constexpr const char* DATA_DIR = "Data"; namespace { From 7b838e77a02fa01ef8a157c109201d0fdf4a4e96 Mon Sep 17 00:00:00 2001 From: Nethius Date: Sun, 13 Oct 2024 15:14:43 +0400 Subject: [PATCH 08/27] bugfix: removed the importErrorOccurred() signal overload, since qml does not know how to handle signal overloads (#1111) --- client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + client/ui/controllers/importController.cpp | 6 +++--- client/ui/controllers/importController.h | 1 - client/ui/controllers/installController.cpp | 2 +- client/ui/controllers/installController.h | 2 +- client/ui/qml/Pages2/PageSetupWizardViewConfig.qml | 2 +- client/ui/qml/Pages2/PageStart.qml | 4 ++++ 8 files changed, 12 insertions(+), 7 deletions(-) diff --git a/client/core/defs.h b/client/core/defs.h index ebc07f4b..802eca45 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -96,6 +96,7 @@ namespace amnezia // import and install errors ImportInvalidConfigError = 900, + ImportOpenConfigError = 901, // Android errors AndroidError = 1000, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 8c16d786..00e94995 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -50,6 +50,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; + case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr(""); break; // Android errors case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 261551ea..168e3564 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -84,7 +84,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName) return extractConfigFromData(data); } - emit importErrorOccurred(tr("Unable to open file"), false); + emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); return false; } @@ -188,12 +188,12 @@ bool ImportController::extractConfigFromData(QString data) if (!m_serversModel->getServersCount()) { emit restoreAppConfig(config.toUtf8()); } else { - emit importErrorOccurred(tr("Invalid configuration file"), false); + emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); } break; } case ConfigTypes::Invalid: { - emit importErrorOccurred(tr("Invalid configuration file"), false); + emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false); break; } } diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 61205253..05e320a5 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -54,7 +54,6 @@ public slots: signals: void importFinished(); - void importErrorOccurred(const QString &errorMessage, bool goToPageHome); void importErrorOccurred(ErrorCode errorCode, bool goToPageHome); void qrDecodingFinished(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index c6f17057..31aa1fb1 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -768,7 +768,7 @@ bool InstallController::checkSshConnection(QSharedPointer serv } else { if (output.contains(tr("Please login as the user"))) { output.replace("\n", ""); - emit installationErrorOccurred(output); + emit wrongInstallationUser(output); return false; } } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 7eea216a..d7ab3553 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -75,8 +75,8 @@ signals: void removeAllContainersFinished(const QString &finishedMessage); void removeProcessedContainerFinished(const QString &finishedMessage); - void installationErrorOccurred(const QString &errorMessage); void installationErrorOccurred(ErrorCode errorCode); + void wrongInstallationUser(const QString &message); void serverAlreadyExists(int serverIndex); diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 3aac1555..92048f36 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -37,7 +37,7 @@ PageType { Connections { target: ImportController - function onImportErrorOccurred(errorMessage, goToPageHome) { + function onImportErrorOccurred(error, goToPageHome) { if (goToPageHome) { PageController.goToStartPage() } else { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index bb6663fb..640c61ef 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -123,6 +123,10 @@ PageType { } } + function onWrongInstallationUser(message) { + onInstallationErrorOccurred(message) + } + function onUpdateContainerFinished(message) { PageController.showNotificationMessage(message) PageController.closePage() From 2c9067b0de3e4a80979cbc9343220873fc54ea45 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 18 Oct 2024 14:57:20 +0800 Subject: [PATCH 09/27] bugfix: added missing text in the errors --- client/core/errorstrings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 00e94995..c5c0363b 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -50,7 +50,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; - case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr(""); break; + case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break; // Android errors case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break; From 60de146f031116bcc273a1f88045c838bb63b876 Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 18 Oct 2024 13:47:53 +0400 Subject: [PATCH 10/27] chore/mozilla upstream (#1136) * cherry-pick commit 5a51e292d44ec0fb07867aff0401b4c2a8fca1e8 from mozila upstream * cherry-pick commit e8ecb857dcfb804b7766a54e725b442fc6c0e661 from mozila upstream * cherry-pick commit 16269ffa600905b09678014f64951748fb0ff8ad from mozila upstream --- client/daemon/daemon.cpp | 23 ++++--------------- client/daemon/daemon.h | 1 - client/daemon/daemonlocalserverconnection.cpp | 17 ++++++++++---- client/mozilla/localsocketcontroller.cpp | 4 ++-- client/platforms/linux/daemon/linuxdaemon.h | 1 - client/platforms/macos/daemon/macosdaemon.h | 1 - .../platforms/windows/daemon/windowsdaemon.h | 1 - .../windows/daemon/windowssplittunnel.cpp | 2 +- 8 files changed, 21 insertions(+), 29 deletions(-) diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3e237e9c..a234860b 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) { return false; } - if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { + if (!dnsutils()->restoreResolvers()) { return false; } @@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) { } bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { - if (!supportDnsUtils()) { - return true; - } - if ((config.m_hopType == InterfaceConfig::MultiHopExit) || (config.m_hopType == InterfaceConfig::SingleHop)) { QList resolvers; @@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) { } // Cleanup DNS - if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { - return false; - } - - if (!wgutils()->interfaceExists()) { - logger.warning() << "Wireguard interface does not exist."; - return false; + if (!dnsutils()->restoreResolvers()) { + logger.warning() << "Failed to restore DNS resolvers."; } // Cleanup peers and routing @@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) { } m_excludedAddrSet.clear(); - // Delete the interface - if (!wgutils()->deleteInterface()) { - return false; - } - m_connections.clear(); - return true; + // Delete the interface + return wgutils()->deleteInterface(); } QString Daemon::logs() { diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h index d3d8c34d..3d418d70 100644 --- a/client/daemon/daemon.h +++ b/client/daemon/daemon.h @@ -69,7 +69,6 @@ class Daemon : public QObject { virtual WireguardUtils* wgutils() const = 0; virtual bool supportIPUtils() const { return false; } virtual IPUtils* iputils() { return nullptr; } - virtual bool supportDnsUtils() const { return false; } virtual DnsUtils* dnsutils() { return nullptr; } static bool parseStringList(const QJsonObject& obj, const QString& name, diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 1a49b7e5..edbc4c9b 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { logger.debug() << "Command received:" << type; + // It is expected that sometimes the client will request backend logs + // before the first authentication. In these cases we just return empty + // logs. + if (type == "logs") { + QJsonObject obj; + obj.insert("type", "logs"); + obj.insert("logs", ""); + write(obj); + return; + } + if (type == "activate") { InterfaceConfig config; if (!Daemon::parseConfig(obj, config)) { @@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { if (type == "status") { QJsonObject obj = Daemon::instance()->getStatus(); obj.insert("type", "status"); - m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); - m_socket->write("\n"); + write(obj); return; } @@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { QJsonObject obj; obj.insert("type", "logs"); obj.insert("logs", Daemon::instance()->logs().replace("\n", "|")); - m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); - m_socket->write("\n"); + write(obj); return; } diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 4d040288..5e9f0f97 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -34,8 +34,8 @@ LocalSocketController::LocalSocketController() { m_socket = new QLocalSocket(this); connect(m_socket, &QLocalSocket::connected, this, &LocalSocketController::daemonConnected); - connect(m_socket, &QLocalSocket::disconnected, this, - &LocalSocketController::disconnected); + connect(m_socket, &QLocalSocket::disconnected, this, + [&] { errorOccurred(QLocalSocket::PeerClosedError); }); connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalSocketController::errorOccurred); connect(m_socket, &QLocalSocket::readyRead, this, diff --git a/client/platforms/linux/daemon/linuxdaemon.h b/client/platforms/linux/daemon/linuxdaemon.h index 7f5d27b7..dbac8cee 100644 --- a/client/platforms/linux/daemon/linuxdaemon.h +++ b/client/platforms/linux/daemon/linuxdaemon.h @@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon { protected: WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } IPUtils* iputils() override { return m_iputils; } diff --git a/client/platforms/macos/daemon/macosdaemon.h b/client/platforms/macos/daemon/macosdaemon.h index a48c326c..4181648e 100644 --- a/client/platforms/macos/daemon/macosdaemon.h +++ b/client/platforms/macos/daemon/macosdaemon.h @@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon { protected: WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } IPUtils* iputils() override { return m_iputils; } diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h index 9d051bae..7e38c41e 100644 --- a/client/platforms/windows/daemon/windowsdaemon.h +++ b/client/platforms/windows/daemon/windowsdaemon.h @@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon { protected: bool run(Op op, const InterfaceConfig& config) override; WireguardUtils* wgutils() const override { return m_wgutils; } - bool supportDnsUtils() const override { return true; } DnsUtils* dnsutils() override { return m_dnsutils; } private: diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 39941933..c4e893b2 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) { // device should contain : for e.g C: return ""; } - QByteArray buffer(2048, 0xFF); + QByteArray buffer(2048, 0xFFu); auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter), (wchar_t*)buffer.data(), buffer.size() / 2); From d63bf15011f074ef498af5d91c5c46092856ada4 Mon Sep 17 00:00:00 2001 From: albexk Date: Fri, 18 Oct 2024 12:52:24 +0300 Subject: [PATCH 11/27] Android qt 6.7.3 (#1143) * Up Qt to 6.7.3 * Bump version to 4.8.2.0 * Raise the minimum Android version to 8 (API 26) * Update version code to separate versions for new and old Androids * Fix mouse not working on TVs * Refactor logging * Bump version code --- .github/workflows/deploy.yml | 2 +- CMakeLists.txt | 4 +- client/android/gradle.properties | 2 +- .../protocolApi/src/main/kotlin/Protocol.kt | 4 - .../src/org/amnezia/vpn/AmneziaActivity.kt | 73 +++++++++++++++++-- .../src/org/amnezia/vpn/AmneziaVpnService.kt | 6 +- .../src/org/amnezia/vpn/AuthActivity.kt | 2 +- .../org/amnezia/vpn/ImportConfigActivity.kt | 8 +- .../org/amnezia/vpn/ServiceNotification.kt | 12 ++- .../utils/src/main/kotlin/LibraryLoader.kt | 2 +- client/android/utils/src/main/kotlin/Log.kt | 17 +---- .../utils/src/main/kotlin/net/NetworkState.kt | 20 ++--- .../vpn/protocol/wireguard/Wireguard.kt | 2 +- client/android/xray/src/main/kotlin/Xray.kt | 4 +- client/cmake/android.cmake | 2 +- 15 files changed, 97 insertions(+), 63 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d9138516..f9fb19a5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -301,7 +301,7 @@ jobs: env: ANDROID_BUILD_PLATFORM: android-34 - QT_VERSION: 6.7.2 + QT_VERSION: 6.7.3 QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} diff --git a/CMakeLists.txt b/CMakeLists.txt index fba4183c..ce5777e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.1.9 +project(${PROJECT} VERSION 4.8.2.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 65) +set(APP_ANDROID_VERSION_CODE 2067) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/android/gradle.properties b/client/android/gradle.properties index 5a27838c..ce651e1c 100644 --- a/client/android/gradle.properties +++ b/client/android/gradle.properties @@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false # For development copy and set local values for these parameters in local.properties #androidCompileSdkVersion=android-34 #androidBuildToolsVersion=34.0.0 -#qtMinSdkVersion=24 +#qtMinSdkVersion=26 #qtTargetSdkVersion=34 #androidNdkVersion=26.1.10909125 #qtTargetAbiList=x86_64 diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt index b5c382be..6e682aa4 100644 --- a/client/android/protocolApi/src/main/kotlin/Protocol.kt +++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt @@ -1,6 +1,5 @@ package org.amnezia.vpn.protocol -import android.annotation.SuppressLint import android.content.Context import android.net.IpPrefix import android.net.VpnService @@ -8,9 +7,6 @@ import android.net.VpnService.Builder import android.os.Build import android.system.OsConstants import androidx.annotation.RequiresApi -import java.io.File -import java.io.FileOutputStream -import java.util.zip.ZipFile import kotlinx.coroutines.flow.MutableStateFlow import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.net.InetNetwork diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index d5026425..b2c2ff71 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -21,6 +21,7 @@ import android.os.Looper import android.os.Message import android.os.Messenger import android.provider.Settings +import android.view.MotionEvent import android.view.WindowManager.LayoutParams import android.webkit.MimeTypeMap import android.widget.Toast @@ -158,7 +159,7 @@ class AmneziaActivity : QtActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "Create Amnezia activity: $intent") + Log.d(TAG, "Create Amnezia activity") loadLibs() window.apply { addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) @@ -200,7 +201,7 @@ class AmneziaActivity : QtActivity() { NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED ) ) { - Log.d( + Log.v( TAG, "Notification state changed: ${it?.action}, blocked = " + "${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}" ) @@ -214,7 +215,7 @@ class AmneziaActivity : QtActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.d(TAG, "onNewIntent: $intent") + Log.v(TAG, "onNewIntent: $intent") intent?.let(::processIntent) } @@ -403,7 +404,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun startVpn(vpnConfig: String) { getVpnProto(vpnConfig)?.let { proto -> - Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto") + Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto") if (isServiceConnected) { if (proto.serviceClass == vpnProto?.serviceClass) { vpnProto = proto @@ -516,7 +517,7 @@ class AmneziaActivity : QtActivity() { startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( onSuccess = { it?.data?.let { uri -> - Log.d(TAG, "Save file to $uri") + Log.v(TAG, "Save file to $uri") try { contentResolver.openOutputStream(uri)?.use { os -> os.bufferedWriter().use { it.write(data) } @@ -565,7 +566,7 @@ class AmneziaActivity : QtActivity() { startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( onAny = { val uri = it?.data?.toString() ?: "" - Log.d(TAG, "Open file: $uri") + Log.v(TAG, "Open file: $uri") mainScope.launch { qtInitialized.await() QtAndroidController.onFileOpened(uri) @@ -720,6 +721,66 @@ class AmneziaActivity : QtActivity() { } } + // workaround for a bug in Qt that causes the mouse click event not to be handled + // also disable right-click, as it causes the application to crash + private var lastButtonState = 0 + private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain( + downTime, + eventTime, + action, + pointerCount, + (0 until pointerCount).map { i -> + MotionEvent.PointerProperties().apply { + getPointerProperties(i, this) + } + }.toTypedArray(), + (0 until pointerCount).map { i -> + MotionEvent.PointerCoords().apply { + getPointerCoords(i, this) + } + }.toTypedArray(), + metaState, + MotionEvent.BUTTON_PRIMARY, + xPrecision, + yPrecision, + deviceId, + edgeFlags, + source, + flags + ) + + private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean { + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + lastButtonState = ev.buttonState + if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true + } + + MotionEvent.ACTION_UP -> { + when (lastButtonState) { + MotionEvent.BUTTON_SECONDARY -> return true + MotionEvent.BUTTON_PRIMARY -> { + val modEvent = ev.fixCopy() + return superDispatch(modEvent).apply { modEvent.recycle() } + } + } + } + } + return superDispatch(ev) + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { + return handleMouseEvent(ev) { super.dispatchTouchEvent(it) } + } + return super.dispatchTouchEvent(ev) + } + + override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean { + ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }} + return super.dispatchTrackballEvent(ev) + } + /** * Utils methods */ diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index 937127ee..8d108bc3 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -300,7 +300,7 @@ open class AmneziaVpnService : VpnService() { arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED ) { it?.action?.let { action -> - Log.d(TAG, "Broadcast request received: $action") + Log.v(TAG, "Broadcast request received: $action") when (action) { ACTION_CONNECT -> connect() ACTION_DISCONNECT -> disconnect() @@ -317,7 +317,7 @@ open class AmneziaVpnService : VpnService() { ) ) { val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false) - Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state") + Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state") if (state == false) { enableNotification() } else { @@ -450,7 +450,7 @@ open class AmneziaVpnService : VpnService() { serviceNotification.isNotificationEnabled() && getSystemService()?.isInteractive != false ) { - Log.d(TAG, "Launch traffic stats update") + Log.v(TAG, "Launch traffic stats update") trafficStats.reset() startTrafficStatsUpdateJob() } diff --git a/client/android/src/org/amnezia/vpn/AuthActivity.kt b/client/android/src/org/amnezia/vpn/AuthActivity.kt index 2593315c..46401548 100644 --- a/client/android/src/org/amnezia/vpn/AuthActivity.kt +++ b/client/android/src/org/amnezia/vpn/AuthActivity.kt @@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() { object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) - Log.d(TAG, "Authentication succeeded") + Log.v(TAG, "Authentication succeeded") QtAndroidController.onAuthResult(true) finish() } diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt index 9faa30d0..49823a36 100644 --- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt +++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt @@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "Create Import Config Activity: $intent") + Log.v(TAG, "Create Import Config Activity: $intent") intent?.let(::readConfig) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - Log.d(TAG, "onNewIntent: $intent") + Log.v(TAG, "onNewIntent: $intent") intent.let(::readConfig) } private fun readConfig(intent: Intent) { when (intent.action) { ACTION_SEND -> { - Log.d(TAG, "Process SEND action, type: ${intent.type}") + Log.v(TAG, "Process SEND action, type: ${intent.type}") when (intent.type) { "application/octet-stream" -> { intent.getUriCompat()?.let { uri -> @@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() { } ACTION_VIEW -> { - Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") + Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") when (intent.scheme) { "file", "content" -> { intent.data?.let { uri -> diff --git a/client/android/src/org/amnezia/vpn/ServiceNotification.kt b/client/android/src/org/amnezia/vpn/ServiceNotification.kt index f4707731..47e8f263 100644 --- a/client/android/src/org/amnezia/vpn/ServiceNotification.kt +++ b/client/android/src/org/amnezia/vpn/ServiceNotification.kt @@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) { fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification { val speedString = if (state == CONNECTED) zeroSpeed else null - Log.d(TAG, "Build notification: $serverName, $state") + Log.v(TAG, "Build notification: $serverName, $state") return notificationBuilder .setSmallIcon(R.drawable.ic_amnezia_round) @@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) { fun isNotificationEnabled(): Boolean { if (!context.isNotificationPermissionGranted()) return false if (!notificationManager.areNotificationsEnabled()) return false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) - ?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true - } - return true + return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let { + it.importance != NotificationManager.IMPORTANCE_NONE + } ?: true } @SuppressLint("MissingPermission") fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) { if (context.isNotificationPermissionGranted()) { - Log.d(TAG, "Update notification: $serverName, $state") + Log.v(TAG, "Update notification: $serverName, $state") notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state)) } } diff --git a/client/android/utils/src/main/kotlin/LibraryLoader.kt b/client/android/utils/src/main/kotlin/LibraryLoader.kt index f1c6465e..8def18d0 100644 --- a/client/android/utils/src/main/kotlin/LibraryLoader.kt +++ b/client/android/utils/src/main/kotlin/LibraryLoader.kt @@ -46,7 +46,7 @@ object LibraryLoader { System.loadLibrary(libraryName) return } catch (_: UnsatisfiedLinkError) { - Log.d(TAG, "Failed to load library, try to extract it from apk") + Log.w(TAG, "Failed to load library, try to extract it from apk") } var tempFile: File? = null try { diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt index a656b9ea..da11c200 100644 --- a/client/android/utils/src/main/kotlin/Log.kt +++ b/client/android/utils/src/main/kotlin/Log.kt @@ -1,8 +1,6 @@ package org.amnezia.vpn.util import android.content.Context -import android.icu.text.DateFormat -import android.icu.text.SimpleDateFormat import android.os.Build import android.os.Process import java.io.File @@ -12,8 +10,6 @@ import java.nio.channels.FileChannel import java.nio.channels.FileLock import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import java.util.Date -import java.util.Locale import java.util.concurrent.locks.ReentrantLock import org.amnezia.vpn.util.Log.Priority.D import org.amnezia.vpn.util.Log.Priority.E @@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024 * | | | create a report and/or terminate the process | */ object Log { - private val dateTimeFormat: Any = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) - else object : ThreadLocal() { - override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US) - } + private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) private lateinit var logDir: File private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) } @@ -143,12 +135,7 @@ object Log { } private fun formatLogMsg(tag: String, msg: String, priority: Priority): String { - val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter) - } else { - @Suppress("UNCHECKED_CAST") - (dateTimeFormat as ThreadLocal).get()?.format(Date()) - } + val date = LocalDateTime.now().format(dateTimeFormat) return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " + "$tag: $msg\n" } diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt index b71bf393..1cab5535 100644 --- a/client/android/utils/src/main/kotlin/net/NetworkState.kt +++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt @@ -42,18 +42,12 @@ class NetworkState( private val networkCallback: NetworkCallback by lazy(NONE) { object : NetworkCallback() { override fun onAvailable(network: Network) { - Log.d(TAG, "onAvailable: $network") + Log.v(TAG, "onAvailable: $network") } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - checkNetworkState(network, networkCapabilities) - } else { - handler.post { - checkNetworkState(network, networkCapabilities) - } - } + Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities") + checkNetworkState(network, networkCapabilities) } private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) { @@ -73,11 +67,11 @@ class NetworkState( } override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { - Log.d(TAG, "onBlockedStatusChanged: $network, $blocked") + Log.v(TAG, "onBlockedStatusChanged: $network, $blocked") } override fun onLost(network: Network) { - Log.d(TAG, "onLost: $network") + Log.v(TAG, "onLost: $network") } } } @@ -87,7 +81,7 @@ class NetworkState( Log.d(TAG, "Bind network listener") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + } else { val numberAttempts = 300 var attemptCount = 0 while(true) { @@ -108,8 +102,6 @@ class NetworkState( } } } - } else { - connectivityManager.requestNetwork(networkRequest, networkCallback) } isListenerBound = true } diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index ac11374b..e93834f4 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -66,7 +66,7 @@ open class Wireguard : Protocol() { try { delay(1000) var log = getLogcat(time) - Log.d(TAG, "First waiting log: $log") + Log.v(TAG, "First waiting log: $log") // check that there is a connection log, // to avoid infinite connection if (!log.contains("Attaching to interface")) { diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt index 6e37c9c2..08242525 100644 --- a/client/android/xray/src/main/kotlin/Xray.kt +++ b/client/android/xray/src/main/kotlin/Xray.kt @@ -130,8 +130,8 @@ class Xray : Protocol() { LibXray.initXray(assetsPath) val geoDir = File(assetsPath, "geo").absolutePath val configPath = File(context.cacheDir, "config.json") - Log.d(TAG, "xray.location.asset: $geoDir") - Log.d(TAG, "config: $configPath") + Log.v(TAG, "xray.location.asset: $geoDir") + Log.v(TAG, "config: $configPath") try { configPath.writeText(configJson) } catch (e: IOException) { diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index c96d9ab8..34ca5bff 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -1,6 +1,6 @@ message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") -set(APP_ANDROID_MIN_SDK 24) +set(APP_ANDROID_MIN_SDK 26) set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING "The minimum API level supported by the application or library" FORCE) From 74802f30ed9b838f0ad55d5d167c96f44dd3d459 Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 18 Oct 2024 13:57:38 +0400 Subject: [PATCH 12/27] feature/proxy storage bypass (#1179) * feature: added proxy storage bypass - added encryption error handling to apiController * chore: fixed include --- client/CMakeLists.txt | 2 ++ client/core/controllers/apiController.cpp | 37 +++++++++++++++++++---- client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + client/utilities.cpp | 19 ++++++++++++ client/utilities.h | 3 ++ 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2de5db48..2ec4082c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -26,9 +26,11 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}") add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}") +add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}") add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") +add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}") if(IOS) set(PACKAGES ${PACKAGES} Multimedia) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 5cdaa7ae..96c28a81 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -12,6 +12,7 @@ #include "configurators/wireguard_configurator.h" #include "core/enums/apiEnums.h" #include "version.h" +#include "utilities.h" namespace { @@ -42,8 +43,6 @@ namespace constexpr char keyPayload[] = "key_payload"; } - const QStringList proxyStorageUrl = { "" }; - ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply) { if (!sslErrors.empty()) { @@ -146,6 +145,15 @@ QStringList ApiController::getProxyUrls() 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); @@ -166,11 +174,23 @@ QStringList ApiController::getProxyUrls() EVP_PKEY *privateKey = nullptr; QByteArray responseBody; try { - QByteArray key = PROD_PROXY_STORAGE_KEY; - QSimpleCrypto::QRsa rsa; - privateKey = rsa.getPrivateKeyFromByteArray(key, ""); - responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING); + 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 {}; } @@ -361,6 +381,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co QSimpleCrypto::QRsa rsa; publicKey = rsa.getPublicKeyFromByteArray(rsaKey); } catch (...) { + Utils::logException(); qCritical() << "error loading public key from environment variables"; return ErrorCode::ApiMissingAgwPublicKey; } @@ -370,7 +391,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co 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; @@ -416,7 +439,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co 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/defs.h b/client/core/defs.h index 802eca45..d00d347b 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -108,6 +108,7 @@ namespace amnezia ApiConfigTimeoutError = 1103, ApiConfigSslError = 1104, ApiMissingAgwPublicKey = 1105, + ApiConfigDecryptionError = 1106, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index c5c0363b..49534606 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -62,6 +62,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break; case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break; case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; + case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/utilities.cpp b/client/utilities.cpp index 4047365f..731781b5 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -244,3 +244,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) } #endif + +void Utils::logException(const std::exception &e) +{ + qCritical() << e.what(); + try { + std::rethrow_if_nested(e); + } catch (const std::exception &nested) { + logException(nested); + } catch (...) {} +} + +void Utils::logException(const std::exception_ptr &eptr) +{ + try { + if (eptr) std::rethrow_exception(eptr); + } catch (const std::exception &e) { + logException(e); + } catch (...) {} +} diff --git a/client/utilities.h b/client/utilities.h index 9bf8c82a..2a17a1f4 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -34,6 +34,9 @@ public: static QString certUtilPath(); static QString tun2socksPath(); + static void logException(const std::exception &e); + static void logException(const std::exception_ptr &eptr = std::current_exception()); + #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif From 5601bc4fdfcf4cd663c58df57c345bb9a6f8e6d0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 18 Oct 2024 21:39:09 +0800 Subject: [PATCH 13/27] chore: added new env for workflows --- .github/workflows/deploy.yml | 15 +++++++++++++++ .github/workflows/tag-deploy.yml | 3 +++ 2 files changed, 18 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f9fb19a5..64a4986d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,10 @@ jobs: QT_VERSION: 6.6.2 QIF_VERSION: 4.7 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Install Qt' @@ -83,7 +86,10 @@ jobs: QIF_VERSION: 4.7 BUILD_ARCH: 64 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Get sources' @@ -146,7 +152,10 @@ jobs: CC: cc CXX: c++ PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Setup xcode' @@ -238,7 +247,10 @@ jobs: QT_VERSION: 6.4.3 QIF_VERSION: 4.6 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Setup xcode' @@ -304,7 +316,10 @@ jobs: QT_VERSION: 6.7.3 QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Install desktop Qt' diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml index dffb3ab1..2bcbd8c6 100644 --- a/.github/workflows/tag-deploy.yml +++ b/.github/workflows/tag-deploy.yml @@ -16,7 +16,10 @@ jobs: QT_VERSION: 6.4.1 QIF_VERSION: 4.5 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} steps: - name: 'Install desktop Qt' From 928c4f18c98e6f601b634015ff472be8ecb56fbe Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 22 Oct 2024 22:24:23 +0800 Subject: [PATCH 14/27] chore/using the global network manager --- client/CMakeLists.txt | 1 - client/core/controllers/apiController.cpp | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2ec4082c..05f9f17c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -25,7 +25,6 @@ execute_process( add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}") -add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}") add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}") add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 31a561d8..a7c304f3 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -352,7 +352,6 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co QThread::msleep(10); #endif - QNetworkAccessManager manager; QNetworkRequest request; request.setTransferTimeout(7000); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -410,7 +409,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); - QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson()); + QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson()); QEventLoop wait; connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); @@ -425,7 +424,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co } for (const QString &proxyUrl : m_proxyUrls) { request.setUrl(QString("%1v1/config").arg(proxyUrl)); - reply = manager.post(request, QJsonDocument(requestBody).toJson()); + 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; }); From e31a2066c080358be07fe48ffe0b60674db335ae Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 22 Oct 2024 23:05:58 +0800 Subject: [PATCH 15/27] feature/added support tag to PageSetupWizardConfigSource --- .../Pages2/PageSetupWizardConfigSource.qml | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 7f7cf9e1..7c031997 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -49,6 +49,8 @@ PageType { HeaderType { + property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() + Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 @@ -56,7 +58,7 @@ PageType { headerText: qsTr("Connection") - actionButtonImage: PageController.isStartPageVisible() ? "qrc:/images/controls/more-vertical.svg" : "" + actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonFunction: function() { moreActionsDrawer.open() } @@ -67,18 +69,19 @@ PageType { parent: root anchors.fill: parent - expandedHeight: root.height * 0.35 + expandedHeight: root.height * 0.5 expandedContent: ColumnLayout { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 + spacing: 0 HeaderType { Layout.fillWidth: true Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: qsTr("Settings") } @@ -87,9 +90,12 @@ PageType { id: switcher Layout.fillWidth: true Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Enable logs") + visible: PageController.isStartPageVisible() checked: SettingsController.isLoggingEnabled onCheckedChanged: { if (checked !== SettingsController.isLoggingEnabled) { @@ -98,6 +104,28 @@ PageType { } } + LabelWithButtonType { + id: supportUuid + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Support tag") + descriptionText: SettingsController.getInstallationUuid() + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + visible: SettingsController.getInstallationUuid() !== "" + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + if (!GC.isMobile()) { + this.rightButton.forceActiveFocus() + } + } + } } } } From 5358aaeb00cd2315d5cc82e5fe1d6ae959867c64 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 22 Oct 2024 23:14:41 +0800 Subject: [PATCH 16/27] chore/displaying route addresses when adding to split tunneling fails --- client/platforms/windows/daemon/wireguardutilswindows.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index a68551d7..1a220235 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -248,7 +248,7 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) { } if (result != NO_ERROR) { logger.error() << "Failed to create route to" - << logger.sensitive(prefix.toString()) + << prefix.toString() << "result:" << result; } return result == NO_ERROR; @@ -265,7 +265,7 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) { } if (result != NO_ERROR) { logger.error() << "Failed to delete route to" - << logger.sensitive(prefix.toString()) + << prefix.toString() << "result:" << result; } return result == NO_ERROR; From 92b19eccf6753ce3734b6ba51acaaee6ebeb93cb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 23 Oct 2024 00:33:22 +0800 Subject: [PATCH 17/27] bugfix/removed adding routes in vpnconnection class for awg and wg protocols --- client/vpnconnection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 591e396f..ac881bd7 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -56,14 +56,15 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP - QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex()); + auto container = m_settings->defaultContainer(m_settings->defaultServerIndex()); if (IpcClient::Interface()) { if (state == Vpn::ConnectionState::Connected) { IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); - if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) { + if (!m_vpnConfiguration.value(config_key::configVersion).toInt() && container != DockerContainer::Awg + && container != DockerContainer::WireGuard) { QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); From 923e358aaaf458cfa9b319064326b2ec6c308e25 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Oct 2024 01:02:30 +0800 Subject: [PATCH 18/27] added a check to trigger proxy bypass --- client/core/controllers/apiController.cpp | 56 +++++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 31a561d8..1f8257ee 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -11,8 +11,8 @@ #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" #include "core/enums/apiEnums.h" -#include "version.h" #include "utilities.h" +#include "version.h" namespace { @@ -65,6 +65,28 @@ namespace 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) @@ -320,24 +342,27 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody) connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); wait.exec(); - if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) { + responseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) { m_proxyUrls = getProxyUrls(); 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(); - if (reply->error() != QNetworkReply::NetworkError::TimeoutError - && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) { + + responseBody = reply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) { break; } - reply->deleteLater(); } } - responseBody = reply->readAll(); auto errorCode = checkErrors(sslErrors, reply); reply->deleteLater(); return errorCode; @@ -419,32 +444,33 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; }); wait.exec(); - if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) { - if (m_proxyUrls.isEmpty()) { - m_proxyUrls = getProxyUrls(); - } + auto encryptedResponseBody = reply->readAll(); + + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true)) { + m_proxyUrls = getProxyUrls(); 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 = 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(); - if (reply->error() != QNetworkReply::NetworkError::TimeoutError - && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) { + + encryptedResponseBody = reply->readAll(); + if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, false)) { break; } - reply->deleteLater(); } } auto errorCode = checkErrors(sslErrors, reply); + reply->deleteLater(); if (errorCode) { return errorCode; } - auto encryptedResponseBody = reply->readAll(); - reply->deleteLater(); try { auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig); From d511220f8befbe076f320e94d667000f0e9843dc Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Oct 2024 01:03:54 +0800 Subject: [PATCH 19/27] added a randomized proxy bypass --- client/core/controllers/apiController.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index 1f8257ee..dbd621a8 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -1,5 +1,8 @@ #include "apiController.h" +#include +#include + #include #include #include @@ -346,6 +349,9 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody) 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)); @@ -448,6 +454,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true)) { 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)); From 4685d3b543b19a7bf4ac1ab81de31c20021c71a7 Mon Sep 17 00:00:00 2001 From: Nethius Date: Thu, 24 Oct 2024 19:12:53 +0400 Subject: [PATCH 20/27] bugfix/api auth data saving (#1195) * bugfix: fixed authData saving * bugfix: added serviceInfo processing from api response --- client/core/controllers/apiController.cpp | 6 ++++++ client/ui/controllers/installController.cpp | 2 ++ client/ui/models/apiCountryModel.cpp | 4 ++++ client/ui/models/apiCountryModel.h | 3 ++- client/ui/models/servers_model.cpp | 2 +- client/ui/qml/Pages2/PageSettingsApiLanguageList.qml | 2 +- 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index a7cbd40a..193ac481 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -37,6 +37,7 @@ namespace 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"; @@ -163,6 +164,11 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle 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; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 4ac0bc32..306e7f38 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -847,6 +847,8 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol)); newServerConfig.insert(configKey::apiConfig, newApiConfig); + newServerConfig.insert(configKey::authData, authData); + newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc)); m_serversModel->editServer(newServerConfig, serverIndex); if (reloadServiceConfig) { diff --git a/client/ui/models/apiCountryModel.cpp b/client/ui/models/apiCountryModel.cpp index ae58329f..922a9d56 100644 --- a/client/ui/models/apiCountryModel.cpp +++ b/client/ui/models/apiCountryModel.cpp @@ -39,6 +39,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const case CountryNameRole: { return countryInfo.value(configKey::serverCountryName).toString(); } + case CountryImageCodeRole: { + return countryInfo.value(configKey::serverCountryCode).toString().toUpper(); + } } return QVariant(); @@ -76,5 +79,6 @@ QHash ApiCountryModel::roleNames() const QHash roles; roles[CountryNameRole] = "countryName"; roles[CountryCodeRole] = "countryCode"; + roles[CountryImageCodeRole] = "countryImageCode"; return roles; } diff --git a/client/ui/models/apiCountryModel.h b/client/ui/models/apiCountryModel.h index 8789158b..b9e243d0 100644 --- a/client/ui/models/apiCountryModel.h +++ b/client/ui/models/apiCountryModel.h @@ -11,7 +11,8 @@ class ApiCountryModel : public QAbstractListModel public: enum Roles { CountryNameRole = Qt::UserRole + 1, - CountryCodeRole + CountryCodeRole, + CountryImageCodeRole }; explicit ApiCountryModel(QObject *parent = nullptr); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 85e5dae2..c87499a7 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -771,5 +771,5 @@ const QString ServersModel::getDefaultServerImagePathCollapsed() if (countryCode.isEmpty()) { return ""; } - return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode); + return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper()); } diff --git a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml index 234e5142..120313cd 100644 --- a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml +++ b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml @@ -90,7 +90,7 @@ PageType { Layout.rightMargin: 32 Layout.alignment: Qt.AlignRight - source: "qrc:/countriesFlags/images/flagKit/" + countryCode + ".svg" + source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" } } From 5065262aac9ebd70f1c8614cb794650d2a9f14e1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Oct 2024 14:05:26 +0800 Subject: [PATCH 21/27] bugfix: fixed clientInfoDrawer recursive rearrange --- client/ui/qml/Pages2/PageShare.qml | 83 ++++++++++++++---------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6640df36..617b1091 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -772,7 +772,8 @@ PageType { } } - anchors.fill: parent + width: root.width + height: root.height expandedContent: ColumnLayout { id: expandedContent @@ -783,8 +784,6 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 - spacing: 8 - onImplicitHeightChanged: { clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32 } @@ -797,57 +796,54 @@ PageType { } } - Header2Type { - Layout.fillWidth: true - - headerText: clientName - } - - ColumnLayout - { - id: textColumn - property string textColor: AmneziaStyle.color.mutedGray + Header2TextType { + Layout.maximumWidth: parent.width Layout.bottomMargin: 24 - ParagraphTextType { - color: textColumn.textColor - visible: creationDate - Layout.fillWidth: true + text: clientName + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Qt.ElideRight + } - text: qsTr("Creation date: %1").arg(creationDate) - } + ParagraphTextType { + color: AmneziaStyle.color.mutedGray + visible: creationDate + Layout.fillWidth: true - ParagraphTextType { - color: textColumn.textColor - visible: latestHandshake - Layout.fillWidth: true + text: qsTr("Creation date: %1").arg(creationDate) + } - text: qsTr("Latest handshake: %1").arg(latestHandshake) - } + ParagraphTextType { + color: AmneziaStyle.color.mutedGray + visible: latestHandshake + Layout.fillWidth: true - ParagraphTextType { - color: textColumn.textColor - visible: dataReceived - Layout.fillWidth: true + text: qsTr("Latest handshake: %1").arg(latestHandshake) + } - text: qsTr("Data received: %1").arg(dataReceived) - } + ParagraphTextType { + color: AmneziaStyle.color.mutedGray + visible: dataReceived + Layout.fillWidth: true - ParagraphTextType { - color: textColumn.textColor - visible: dataSent - Layout.fillWidth: true + text: qsTr("Data received: %1").arg(dataReceived) + } - text: qsTr("Data sent: %1").arg(dataSent) - } + ParagraphTextType { + color: AmneziaStyle.color.mutedGray + visible: dataSent + Layout.fillWidth: true - ParagraphTextType { - color: textColumn.textColor - visible: allowedIps - Layout.fillWidth: true + text: qsTr("Data sent: %1").arg(dataSent) + } - text: qsTr("Allowed IPs: %1").arg(allowedIps) - } + ParagraphTextType { + color: AmneziaStyle.color.mutedGray + visible: allowedIps + Layout.fillWidth: true + + text: qsTr("Allowed IPs: %1").arg(allowedIps) } Item { @@ -952,6 +948,7 @@ PageType { BasicButtonType { id: revokeButton Layout.fillWidth: true + Layout.topMargin: 8 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite From 7261a86c48c80f7c745f0e055fbafcac52c66243 Mon Sep 17 00:00:00 2001 From: albexk Date: Thu, 24 Oct 2024 19:25:44 +0300 Subject: [PATCH 22/27] Bump version to 4.8.2.1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce5777e4..b94e7e73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.2.0 +project(${PROJECT} VERSION 4.8.2.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2067) +set(APP_ANDROID_VERSION_CODE 2068) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 9f3f215452639c698c6756445702320ce8a4c780 Mon Sep 17 00:00:00 2001 From: Aftershock669 Date: Thu, 24 Oct 2024 22:27:53 +0300 Subject: [PATCH 23/27] Update README - add website mirror links - remove direct platform download links - add "Testiny" sponsored badge --- README.md | 12 ++++-------- metadata/img-readme/andr.png | Bin 13621 -> 0 bytes metadata/img-readme/apl.png | Bin 14495 -> 0 bytes metadata/img-readme/download.png | Bin 0 -> 3451 bytes metadata/img-readme/lin.png | Bin 11749 -> 0 bytes metadata/img-readme/mac.png | Bin 9513 -> 0 bytes metadata/img-readme/testiny.png | Bin 0 -> 5313 bytes 7 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 metadata/img-readme/andr.png delete mode 100644 metadata/img-readme/apl.png create mode 100644 metadata/img-readme/download.png delete mode 100644 metadata/img-readme/lin.png delete mode 100644 metadata/img-readme/mac.png create mode 100644 metadata/img-readme/testiny.png diff --git a/README.md b/README.md index e4a6bf0c..eed800f5 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,17 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
- - - - - -
- + +[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads) [All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
+ ## Features @@ -37,7 +33,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep ## Links -- [https://amnezia.org](https://amnezia.org) - project website +- [https://amnezia.org](https://amnezia.org) - project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org) - [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit - [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English) - [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi) diff --git a/metadata/img-readme/andr.png b/metadata/img-readme/andr.png deleted file mode 100644 index a39cd52f8ac97ba1b3a4bda78cfee2f725ead50a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13621 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 KGBr|W;r|C110n?g diff --git a/metadata/img-readme/apl.png b/metadata/img-readme/apl.png deleted file mode 100644 index 6dedfa12ea1d6605b1419bb2c079d64dac02080f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14495 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 diff --git a/metadata/img-readme/download.png b/metadata/img-readme/download.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6a885004aba6dd61392155d4edbe021d4a304d GIT binary patch literal 3451 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 D@^Fny literal 0 HcmV?d00001 diff --git a/metadata/img-readme/lin.png b/metadata/img-readme/lin.png deleted file mode 100644 index 352eae5a3bb23b4f197723bdcd91dc524db2c408..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11749 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 C$Vk=z diff --git a/metadata/img-readme/mac.png b/metadata/img-readme/mac.png deleted file mode 100644 index 2cbb32ae41f1b18c0bb5550671fe56d683fc404d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9513 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 diff --git a/metadata/img-readme/testiny.png b/metadata/img-readme/testiny.png new file mode 100644 index 0000000000000000000000000000000000000000..4f38a3a91c82b2e3a3409e46e7548f93c2c69f6b GIT binary patch literal 5313 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 T$;wY@00000NkvXXu0mjf(4uGS literal 0 HcmV?d00001 From af55af5e760daf00cebe1124d67e9e40d79c9bb3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 25 Oct 2024 17:48:22 +0800 Subject: [PATCH 24/27] bugfix: fixed proxy bypass encryption check --- client/core/controllers/apiController.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index dbd621a8..075f2cc2 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -399,7 +399,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co } apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::uuid] = installationUuid; - apiPayload[configKey::authData] = authData; + if (!authData.isEmpty()) { + apiPayload[configKey::authData] = authData; + } QSimpleCrypto::QBlockCipher blockCipher; QByteArray key = blockCipher.generatePrivateSalt(32); @@ -452,7 +454,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co auto encryptedResponseBody = reply->readAll(); - if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true)) { + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { m_proxyUrls = getProxyUrls(); std::random_device randomDevice; std::mt19937 generator(randomDevice()); @@ -468,7 +470,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co wait.exec(); encryptedResponseBody = reply->readAll(); - if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, false)) { + if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { break; } } From 4f3bae4a9ae68cc13965633a18d6a072f3d7bd15 Mon Sep 17 00:00:00 2001 From: Aftershock669 Date: Fri, 25 Oct 2024 17:00:28 +0300 Subject: [PATCH 25/27] Fix / Update README --- metadata/img-readme/apl.png | Bin 0 -> 14495 bytes metadata/img-readme/win.png | Bin 11018 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 metadata/img-readme/apl.png delete mode 100644 metadata/img-readme/win.png diff --git a/metadata/img-readme/apl.png b/metadata/img-readme/apl.png new file mode 100644 index 0000000000000000000000000000000000000000..6dedfa12ea1d6605b1419bb2c079d64dac02080f GIT binary patch literal 14495 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 literal 0 HcmV?d00001 diff --git a/metadata/img-readme/win.png b/metadata/img-readme/win.png deleted file mode 100644 index 5a35cf490cff7b584ec68df4785dd454b7edfa31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11018 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 JrI=B`{{h}5>cap4 From 9e71e64cbdc514e04878d33aa4568e8a130090de Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 25 Oct 2024 19:19:28 +0400 Subject: [PATCH 26/27] chore: bump version to 4.8.2.3 (#1203) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b94e7e73..420a7eb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.2.1 +project(${PROJECT} VERSION 4.8.2.3 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From e7b25719e44271dd445bb8a7ab3644d6dfcc1024 Mon Sep 17 00:00:00 2001 From: albexk Date: Fri, 25 Oct 2024 19:23:05 +0300 Subject: [PATCH 27/27] Chore/bump version (#1204) * chore: bump Android version code --------- Co-authored-by: Nethius --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 420a7eb2..b5e64e32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2068) +set(APP_ANDROID_VERSION_CODE 2069) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux")