diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index d8f199e9..f2c09d45 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -48,6 +48,9 @@ void CoreController::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get()); + m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this)); m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get()); @@ -130,6 +133,9 @@ void CoreController::initControllers() m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel)); + m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get()); + m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel)); m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get()); @@ -214,6 +220,7 @@ void CoreController::initSignalHandlers() initAutoConnectHandler(); initAmneziaDnsToggledHandler(); initPrepareConfigHandler(); + initStrictKillSwitchHandler(); } void CoreController::initNotificationHandler() @@ -356,6 +363,12 @@ void CoreController::initPrepareConfigHandler() }); } +void CoreController::initStrictKillSwitchHandler() +{ + connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, + m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged); +} + QSharedPointer CoreController::pageController() const { return m_pageController; diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 700504af..6342d738 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -8,6 +8,7 @@ #include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiSettingsController.h" #include "ui/controllers/appSplitTunnelingController.h" +#include "ui/controllers/allowedDnsController.h" #include "ui/controllers/connectionController.h" #include "ui/controllers/exportController.h" #include "ui/controllers/focusController.h" @@ -18,6 +19,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" +#include "ui/models/allowed_dns_model.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -80,6 +82,7 @@ private: void initAutoConnectHandler(); void initAmneziaDnsToggledHandler(); void initPrepareConfigHandler(); + void initStrictKillSwitchHandler(); QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? std::shared_ptr m_settings; @@ -102,6 +105,7 @@ private: QScopedPointer m_sitesController; QScopedPointer m_systemController; QScopedPointer m_appSplitTunnelingController; + QScopedPointer m_allowedDnsController; QScopedPointer m_apiSettingsController; QScopedPointer m_apiConfigsController; @@ -112,6 +116,7 @@ private: QSharedPointer m_languageModel; QSharedPointer m_protocolsModel; QSharedPointer m_sitesModel; + QSharedPointer m_allowedDnsModel; QSharedPointer m_appSplitTunnelingModel; QSharedPointer m_clientManagementModel; diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index f8c23c1a..0d86b9d5 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "QBlockCipher.h" #include "QRsa.h" @@ -14,6 +15,11 @@ #include "amnezia_application.h" #include "core/api/apiUtils.h" #include "utilities.h" +#include "core/networkUtilities.h" + +#ifdef AMNEZIA_DESKTOP + #include "core/ipcclient.h" +#endif namespace { @@ -50,6 +56,17 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo request.setUrl(QString(endpoint).arg(m_gatewayEndpoint)); + // bypass killSwitch exceptions for API-gateway +#ifdef AMNEZIA_DESKTOP + { + QString host = QUrl(request.url()).host(); + QString ip = NetworkUtilities::getIPAddress(host); + if (!ip.isEmpty()) { + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + } + } +#endif + QNetworkReply *reply; reply = amnApp->networkManager()->get(request); @@ -101,6 +118,17 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api request.setUrl(endpoint.arg(m_gatewayEndpoint)); + // bypass killSwitch exceptions for API-gateway +#ifdef AMNEZIA_DESKTOP + { + QString host = QUrl(request.url()).host(); + QString ip = NetworkUtilities::getIPAddress(host); + if (!ip.isEmpty()) { + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + } + } +#endif + QSimpleCrypto::QBlockCipher blockCipher; QByteArray key = blockCipher.generatePrivateSalt(32); QByteArray iv = blockCipher.generatePrivateSalt(32); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 081a7a90..e4b0ab3d 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -371,6 +371,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) { + return false; + } config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool(); diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index b2ad31c6..f0adcc92 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -48,6 +48,13 @@ QJsonObject InterfaceConfig::toJson() const { } json.insert("excludedAddresses", jsExcludedAddresses); + + QJsonArray jsAllowedDnsServers; + for (const QString& i : m_allowedDnsServers) { + jsAllowedDnsServers.append(QJsonValue(i)); + } + json.insert("allowedDnsServers", jsAllowedDnsServers); + QJsonArray disabledApps; for (const QString& i : m_vpnDisabledApps) { disabledApps.append(QJsonValue(i)); diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 4c93e740..ee43a253 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -38,6 +38,7 @@ class InterfaceConfig { QList m_allowedIPAddressRanges; QStringList m_excludedAddresses; QStringList m_vpnDisabledApps; + QStringList m_allowedDnsServers; bool m_killSwitchEnabled; #if defined(MZ_ANDROID) || defined(MZ_IOS) QString m_installationId; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 1081bcae..afa29c47 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -123,6 +123,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt(); QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray(); + QJsonArray allowedDns = rawConfig.value(amnezia::config_key::allowedDnsServers).toArray(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); @@ -226,6 +227,8 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("vpnDisabledApps", splitTunnelApps); + json.insert("allowedDnsServers", allowedDns); + json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption)); if (protocolName == amnezia::config_key::awg) { diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp index 96194bc7..de88c962 100644 --- a/client/platforms/linux/daemon/linuxfirewall.cpp +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -455,9 +455,6 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers) void LinuxFirewall::updateAllowNets(const QStringList& servers) { - static QStringList existingServers {}; - - existingServers = servers; execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName)); for (const QString& rule : getAllowRule(servers)) execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule)); diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 1528d901..0fbb65a8 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -17,6 +17,8 @@ #include "leakdetector.h" #include "logger.h" +#include "killswitch.h" + constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; @@ -182,7 +184,7 @@ bool WireguardUtilsLinux::deleteInterface() { QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); // double-check + ensure our firewall is installed and enabled - LinuxFirewall::uninstall(); + KillSwitch::instance()->disableKillSwitch(); return true; } diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index eae22837..1d8aa6e0 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -16,6 +16,8 @@ #include "leakdetector.h" #include "logger.h" +#include "killswitch.h" + constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; @@ -180,7 +182,7 @@ bool WireguardUtilsMacos::deleteInterface() { QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); // double-check + ensure our firewall is installed and enabled - MacOSFirewall::uninstall(); + KillSwitch::instance()->disableKillSwitch(); return true; } diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 03525387..85a2a155 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -29,6 +29,8 @@ #include "logger.h" #include "platforms/windows/windowsutils.h" +#include "killswitch.h" + #define IPV6_ADDRESS_SIZE 16 // ID for the Firewall Sublayer @@ -180,16 +182,29 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { } \ } - logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex; + logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex; + if (vpnAdapterIndex < 0) + { + IPAddress allv4("0.0.0.0/0"); + if (!blockTrafficTo(allv4, MED_WEIGHT, + "Block Internet", "killswitch")) { + return false; + } + IPAddress allv6("::/0"); + if (!blockTrafficTo(allv6, MED_WEIGHT, + "Block Internet", "killswitch")) { + return false; + } + } else FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT, - "Allow usage of VPN Adapter")); + "Allow usage of VPN Adapter")); FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); - FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic")); + FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic")); FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT, "Allow all for AmneziaVPN.exe")); FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS")); - FW_OK( - allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1")); + FW_OK(allowLoopbackTraffic(MED_WEIGHT, + "Allow Loopback traffic on device %1")); logger.debug() << "Killswitch on! Rules:" << m_activeRules.length(); return true; @@ -226,6 +241,37 @@ bool WindowsFirewall::enableLanBypass(const QList& ranges) { return true; } +// Allow unprotected traffic sent to the following address ranges. +bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) { + // Start the firewall transaction + auto result = FwpmTransactionBegin(m_sessionHandle, NULL); + if (result != ERROR_SUCCESS) { + disableKillSwitch(); + return false; + } + auto cleanup = qScopeGuard([&] { + FwpmTransactionAbort0(m_sessionHandle); + disableKillSwitch(); + }); + + for (const QString& addr : ranges) { + logger.debug() << "Allow killswitch exclude: " << addr; + if (!allowTrafficTo(QHostAddress(addr), LOW_WEIGHT + 1, "Allow killswitch bypass traffic")) { + return false; + } + } + + result = FwpmTransactionCommit0(m_sessionHandle); + if (result != ERROR_SUCCESS) { + logger.error() << "FwpmTransactionCommit0 failed with error:" << result; + return false; + } + + cleanup.dismiss(); + return true; +} + + bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { // Start the firewall transaction auto result = FwpmTransactionBegin(m_sessionHandle, NULL); @@ -262,12 +308,20 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { } } + for (const QString& dns : config.m_allowedDnsServers) { + logger.debug() << "Allow DNS: " << dns; + if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT, + "Allow DNS-Server", config.m_serverPublicKey)) { + return false; + } + } + if (!config.m_excludedAddresses.empty()) { for (const QString& i : config.m_excludedAddresses) { logger.debug() << "excludedAddresses range: " << i; if (!allowTrafficTo(i, HIGH_WEIGHT, - "Allow Ecxlude route", config.m_serverPublicKey)) { + "Allow Ecxlude route", config.m_serverPublicKey)) { return false; } } @@ -313,37 +367,41 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) { } bool WindowsFirewall::disableKillSwitch() { - auto result = FwpmTransactionBegin(m_sessionHandle, NULL); - auto cleanup = qScopeGuard([&] { + return KillSwitch::instance()->disableKillSwitch(); +} + +bool WindowsFirewall::allowAllTraffic() { + auto result = FwpmTransactionBegin(m_sessionHandle, NULL); + auto cleanup = qScopeGuard([&] { + if (result != ERROR_SUCCESS) { + FwpmTransactionAbort0(m_sessionHandle); + } + }); if (result != ERROR_SUCCESS) { - FwpmTransactionAbort0(m_sessionHandle); + logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n" + << result; + return false; } - }); - if (result != ERROR_SUCCESS) { - logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n" - << result; - return false; - } - for (const auto& filterID : m_peerRules.values()) { - FwpmFilterDeleteById0(m_sessionHandle, filterID); - } + for (const auto& filterID : m_peerRules.values()) { + FwpmFilterDeleteById0(m_sessionHandle, filterID); + } - for (const auto& filterID : qAsConst(m_activeRules)) { - FwpmFilterDeleteById0(m_sessionHandle, filterID); - } + for (const auto& filterID : qAsConst(m_activeRules)) { + FwpmFilterDeleteById0(m_sessionHandle, filterID); + } - // Commit! - result = FwpmTransactionCommit0(m_sessionHandle); - if (result != ERROR_SUCCESS) { - logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" - << result; - return false; - } - m_peerRules.clear(); - m_activeRules.clear(); - logger.debug() << "Firewall Disabled!"; - return true; + // Commit! + result = FwpmTransactionCommit0(m_sessionHandle); + if (result != ERROR_SUCCESS) { + logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" + << result; + return false; + } + m_peerRules.clear(); + m_activeRules.clear(); + logger.debug() << "Firewall Disabled!"; + return true; } bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath, diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index 55ee9417..9a0062da 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -43,6 +43,8 @@ class WindowsFirewall final : public QObject { bool enablePeerTraffic(const InterfaceConfig& config); bool disablePeerTraffic(const QString& pubkey); bool disableKillSwitch(); + bool allowAllTraffic(); + bool allowTrafficRange(const QStringList& ranges); private: static bool initSublayer(); diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 4c2feb52..0c8f5907 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -171,6 +171,11 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress( + m_configData.value(amnezia::config_key::hostName).toString()))); +#endif + // Detect default gateway #ifdef Q_OS_MAC QProcess p; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 865edae4..feeabb2f 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -95,6 +95,8 @@ namespace amnezia constexpr char splitTunnelApps[] = "splitTunnelApps"; constexpr char appSplitTunnelType[] = "appSplitTunnelType"; + constexpr char allowedDnsServers[] = "allowedDnsServers"; + constexpr char killSwitchOption[] = "killSwitchOption"; constexpr char crc[] = "crc"; diff --git a/client/resources.qrc b/client/resources.qrc index 16071da0..a36b60d1 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -129,6 +129,7 @@ ui/qml/Components/SettingsContainersListView.qml ui/qml/Components/ShareConnectionDrawer.qml ui/qml/Components/TransportProtoSelector.qml + ui/qml/Components/AddSitePanel.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir ui/qml/Controls2/BackButtonType.qml @@ -143,7 +144,9 @@ ui/qml/Controls2/DropDownType.qml ui/qml/Controls2/FlickableType.qml ui/qml/Controls2/Header2Type.qml - ui/qml/Controls2/HeaderType.qml + ui/qml/Controls2/BaseHeaderType.qml + ui/qml/Controls2/HeaderTypeWithButton.qml + ui/qml/Controls2/HeaderTypeWithSwitcher.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/ImageButtonType.qml ui/qml/Controls2/LabelWithButtonType.qml @@ -199,6 +202,8 @@ ui/qml/Pages2/PageSettingsBackup.qml ui/qml/Pages2/PageSettingsConnection.qml ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsKillSwitch.qml + ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml ui/qml/Pages2/PageSettingsLogging.qml ui/qml/Pages2/PageSettingsServerData.qml ui/qml/Pages2/PageSettingsServerInfo.qml diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index 4fd199db..8df24e74 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -1,7 +1,7 @@ #include "secure_qsettings.h" -#include "QAead.h" -#include "QBlockCipher.h" +#include "../client/3rd/QSimpleCrypto/src/include/QAead.h" +#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h" #include "utilities.h" #include #include diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 3f04096e..8878e1d5 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -6,7 +6,7 @@ #include #include -#include "keychain.h" +#include "../client/3rd/qtkeychain/qtkeychain/keychain.h" class SecureQSettings : public QObject { diff --git a/client/settings.cpp b/client/settings.cpp index 94b11d00..9a0a32e5 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -443,6 +443,16 @@ void Settings::setKillSwitchEnabled(bool enabled) setValue("Conf/killSwitchEnabled", enabled); } +bool Settings::isStrictKillSwitchEnabled() const +{ + return value("Conf/strictKillSwitchEnabled", false).toBool(); +} + +void Settings::setStrictKillSwitchEnabled(bool enabled) +{ + setValue("Conf/strictKillSwitchEnabled", enabled); +} + QString Settings::getInstallationUuid(const bool needCreate) { auto uuid = value("Conf/installationUuid", "").toString(); @@ -548,3 +558,13 @@ void Settings::disableHomeAdLabel() { setValue("Conf/homeAdLabelVisible", false); } + +QStringList Settings::allowedDnsServers() const +{ + return value("Conf/allowedDnsServers").toStringList(); +} + +void Settings::setAllowedDnsServers(const QStringList &servers) +{ + setValue("Conf/allowedDnsServers", servers); +} diff --git a/client/settings.h b/client/settings.h index b383d3da..01155c0c 100644 --- a/client/settings.h +++ b/client/settings.h @@ -213,6 +213,10 @@ public: bool isKillSwitchEnabled() const; void setKillSwitchEnabled(bool enabled); + + bool isStrictKillSwitchEnabled() const; + void setStrictKillSwitchEnabled(bool enabled); + QString getInstallationUuid(const bool needCreate); void resetGatewayEndpoint(); @@ -225,6 +229,9 @@ public: bool isHomeAdLabelVisible(); void disableHomeAdLabel(); + QStringList allowedDnsServers() const; + void setAllowedDnsServers(const QStringList &servers); + signals: void saveLogsChanged(bool enabled); void screenshotsEnabledChanged(bool enabled); diff --git a/client/ui/controllers/allowedDnsController.cpp b/client/ui/controllers/allowedDnsController.cpp new file mode 100644 index 00000000..12e8b599 --- /dev/null +++ b/client/ui/controllers/allowedDnsController.cpp @@ -0,0 +1,101 @@ +#include "allowedDnsController.h" + +#include +#include +#include +#include +#include + +#include "systemController.h" +#include "core/networkUtilities.h" +#include "core/defs.h" + +AllowedDnsController::AllowedDnsController(const std::shared_ptr &settings, + const QSharedPointer &allowedDnsModel, + QObject *parent) + : QObject(parent), m_settings(settings), m_allowedDnsModel(allowedDnsModel) +{ +} + +void AllowedDnsController::addDns(QString ip) +{ + if (ip.isEmpty()) { + return; + } + + if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) { + emit errorOccurred(tr("The address does not look like a valid IP address")); + return; + } + + if (m_allowedDnsModel->addDns(ip)) { + emit finished(tr("New DNS server added: %1").arg(ip)); + } else { + emit errorOccurred(tr("DNS server already exists: %1").arg(ip)); + } +} + +void AllowedDnsController::removeDns(int index) +{ + auto modelIndex = m_allowedDnsModel->index(index); + auto ip = m_allowedDnsModel->data(modelIndex, AllowedDnsModel::Roles::IpRole).toString(); + m_allowedDnsModel->removeDns(modelIndex); + + emit finished(tr("DNS server removed: %1").arg(ip)); +} + +void AllowedDnsController::importDns(const QString &fileName, bool replaceExisting) +{ + QByteArray jsonData; + if (!SystemController::readFile(fileName, jsonData)) { + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); + return; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); + return; + } + + auto jsonArray = jsonDocument.array(); + QStringList dnsServers; + + for (auto jsonValue : jsonArray) { + auto ip = jsonValue.toString(); + + if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) { + qDebug() << ip << " is not a valid IP address"; + continue; + } + + dnsServers.append(ip); + } + + m_allowedDnsModel->addDnsList(dnsServers, replaceExisting); + + emit finished(tr("Import completed")); +} + +void AllowedDnsController::exportDns(const QString &fileName) +{ + auto dnsServers = m_allowedDnsModel->getCurrentDnsServers(); + + QJsonArray jsonArray; + + for (const auto &ip : dnsServers) { + jsonArray.append(ip); + } + + QJsonDocument jsonDocument(jsonArray); + QByteArray jsonData = jsonDocument.toJson(); + + SystemController::saveFile(fileName, jsonData); + + emit finished(tr("Export completed")); +} diff --git a/client/ui/controllers/allowedDnsController.h b/client/ui/controllers/allowedDnsController.h new file mode 100644 index 00000000..5509a036 --- /dev/null +++ b/client/ui/controllers/allowedDnsController.h @@ -0,0 +1,35 @@ +#ifndef ALLOWEDDNSCONTROLLER_H +#define ALLOWEDDNSCONTROLLER_H + +#include + +#include "settings.h" +#include "ui/models/allowed_dns_model.h" + +class AllowedDnsController : public QObject +{ + Q_OBJECT +public: + explicit AllowedDnsController(const std::shared_ptr &settings, + const QSharedPointer &allowedDnsModel, + QObject *parent = nullptr); + +public slots: + void addDns(QString ip); + void removeDns(int index); + + void importDns(const QString &fileName, bool replaceExisting); + void exportDns(const QString &fileName); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + + void saveFile(const QString &fileName, const QString &data); + +private: + std::shared_ptr m_settings; + QSharedPointer m_allowedDnsModel; +}; + +#endif // ALLOWEDDNSCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 60621414..fc981091 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -31,13 +31,15 @@ namespace PageLoader PageSettingsLogging, PageSettingsSplitTunneling, PageSettingsAppSplitTunneling, + PageSettingsKillSwitch, PageSettingsApiServerInfo, PageSettingsApiAvailableCountries, PageSettingsApiSupport, PageSettingsApiInstructions, PageSettingsApiNativeConfigs, PageSettingsApiDevices, - + PageSettingsKillSwitchExceptions, + PageServiceSftpSettings, PageServiceTorWebsiteSettings, PageServiceDnsSettings, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f4e3d83d..c5c569db 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -245,6 +245,23 @@ bool SettingsController::isKillSwitchEnabled() void SettingsController::toggleKillSwitch(bool enable) { m_settings->setKillSwitchEnabled(enable); + emit killSwitchEnabledChanged(); + if (enable == false) { + emit strictKillSwitchEnabledChanged(false); + } else { + emit strictKillSwitchEnabledChanged(isStrictKillSwitchEnabled()); + } +} + +bool SettingsController::isStrictKillSwitchEnabled() +{ + return m_settings->isStrictKillSwitchEnabled(); +} + +void SettingsController::toggleStrictKillSwitch(bool enable) +{ + m_settings->setStrictKillSwitchEnabled(enable); + emit strictKillSwitchEnabledChanged(enable); } bool SettingsController::isNotificationPermissionGranted() diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 7781f6c7..1485e1a0 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -24,6 +24,8 @@ public: Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged) Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged) + Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged) + Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged) Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled) Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged) @@ -75,6 +77,9 @@ public slots: bool isKillSwitchEnabled(); void toggleKillSwitch(bool enable); + bool isStrictKillSwitchEnabled(); + void toggleStrictKillSwitch(bool enable); + bool isNotificationPermissionGranted(); void requestNotificationPermission(); @@ -98,6 +103,8 @@ signals: void primaryDnsChanged(); void secondaryDnsChanged(); void loggingStateChanged(); + void killSwitchEnabledChanged(); + void strictKillSwitchEnabledChanged(bool enabled); void restoreBackupFinished(); void changeSettingsFinished(const QString &finishedMessage); diff --git a/client/ui/models/allowed_dns_model.cpp b/client/ui/models/allowed_dns_model.cpp new file mode 100644 index 00000000..e3c59945 --- /dev/null +++ b/client/ui/models/allowed_dns_model.cpp @@ -0,0 +1,86 @@ +#include "allowed_dns_model.h" + +AllowedDnsModel::AllowedDnsModel(std::shared_ptr settings, QObject *parent) + : QAbstractListModel(parent), m_settings(settings) +{ + fillDnsServers(); +} + +int AllowedDnsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_dnsServers.size(); +} + +QVariant AllowedDnsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + switch (role) { + case IpRole: + return m_dnsServers.at(index.row()); + default: + return QVariant(); + } +} + +bool AllowedDnsModel::addDns(const QString &ip) +{ + if (m_dnsServers.contains(ip)) { + return false; + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_dnsServers.append(ip); + m_settings->setAllowedDnsServers(m_dnsServers); + endInsertRows(); + return true; +} + +void AllowedDnsModel::addDnsList(const QStringList &dnsServers, bool replaceExisting) +{ + beginResetModel(); + + if (replaceExisting) { + m_dnsServers.clear(); + } + + for (const QString &ip : dnsServers) { + if (!m_dnsServers.contains(ip)) { + m_dnsServers.append(ip); + } + } + + m_settings->setAllowedDnsServers(m_dnsServers); + endResetModel(); +} + +void AllowedDnsModel::removeDns(QModelIndex index) +{ + if (!index.isValid() || index.row() >= m_dnsServers.size()) { + return; + } + + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_dnsServers.removeAt(index.row()); + m_settings->setAllowedDnsServers(m_dnsServers); + endRemoveRows(); +} + +QStringList AllowedDnsModel::getCurrentDnsServers() +{ + return m_dnsServers; +} + +QHash AllowedDnsModel::roleNames() const +{ + QHash roles; + roles[IpRole] = "ip"; + return roles; +} + +void AllowedDnsModel::fillDnsServers() +{ + m_dnsServers = m_settings->allowedDnsServers(); +} diff --git a/client/ui/models/allowed_dns_model.h b/client/ui/models/allowed_dns_model.h new file mode 100644 index 00000000..fdefcc0e --- /dev/null +++ b/client/ui/models/allowed_dns_model.h @@ -0,0 +1,37 @@ +#ifndef ALLOWEDDNSMODEL_H +#define ALLOWEDDNSMODEL_H + +#include +#include "settings.h" + +class AllowedDnsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + IpRole = Qt::UserRole + 1 + }; + + explicit AllowedDnsModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + bool addDns(const QString &ip); + void addDnsList(const QStringList &dnsServers, bool replaceExisting); + void removeDns(QModelIndex index); + QStringList getCurrentDnsServers(); + +protected: + QHash roleNames() const override; + +private: + void fillDnsServers(); + + std::shared_ptr m_settings; + QStringList m_dnsServers; +}; + +#endif // ALLOWEDDNSMODEL_H diff --git a/client/ui/qml/Components/AddSitePanel.qml b/client/ui/qml/Components/AddSitePanel.qml new file mode 100644 index 00000000..18fdfa57 --- /dev/null +++ b/client/ui/qml/Components/AddSitePanel.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 +import "../Controls2" +import "../Controls2/TextTypes" + +Item { + id: root + + property bool enabled: true + property string placeholderText: "" + property alias textField: searchField.textField + + signal addClicked(string text) + signal moreClicked() + + implicitWidth: 360 + implicitHeight: 96 + + Rectangle { + id: background + anchors.fill: parent + color: "#0E0F12" + opacity: 0.85 + z: -1 + } + + RowLayout { + id: addSiteButton + + enabled: root.enabled + spacing: 2 + + anchors { + fill: parent + topMargin: 16 + leftMargin: 16 + rightMargin: 16 + bottomMargin: 24 + } + + TextFieldWithHeaderType { + id: searchField + + Layout.fillWidth: true + rightButtonClickedOnEnter: true + + textField.placeholderText: root.placeholderText + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + root.addClicked(textField.text) + textField.text = "" + } + } + + ImageButtonType { + id: addSiteButtonImage + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: AmneziaStyle.color.paleGray + + onClicked: root.moreClicked() + + Keys.onReturnPressed: addSiteButtonImage.clicked() + Keys.onEnterPressed: addSiteButtonImage.clicked() + } + } +} diff --git a/client/ui/qml/Controls2/BaseHeaderType.qml b/client/ui/qml/Controls2/BaseHeaderType.qml new file mode 100644 index 00000000..eb7fe36f --- /dev/null +++ b/client/ui/qml/Controls2/BaseHeaderType.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Item { + id: root + + property string headerText + property int headerTextMaximumLineCount: 2 + property int headerTextElide: Qt.ElideRight + property string descriptionText + property alias headerRow: headerRow + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + RowLayout { + id: headerRow + + Header1TextType { + id: header + Layout.fillWidth: true + text: root.headerText + maximumLineCount: root.headerTextMaximumLineCount + elide: root.headerTextElide + } + } + + ParagraphTextType { + id: description + Layout.topMargin: 16 + Layout.fillWidth: true + text: root.descriptionText + color: AmneziaStyle.color.mutedGray + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml deleted file mode 100644 index 1366148d..00000000 --- a/client/ui/qml/Controls2/HeaderType.qml +++ /dev/null @@ -1,86 +0,0 @@ -import QtQuick -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Item { - id: root - - property string actionButtonImage - property var actionButtonFunction - - property alias actionButton: headerActionButton - - property string headerText - property int headerTextMaximumLineCount: 2 - property int headerTextElide: Qt.ElideRight - - property string descriptionText - - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight - - ColumnLayout { - id: content - anchors.fill: parent - - RowLayout { - Header1TextType { - id: header - - Layout.fillWidth: true - - text: root.headerText - maximumLineCount: root.headerTextMaximumLineCount - elide: root.headerTextElide - } - - ImageButtonType { - id: headerActionButton - - implicitWidth: 40 - implicitHeight: 40 - - Layout.alignment: Qt.AlignRight - - image: root.actionButtonImage - imageColor: AmneziaStyle.color.paleGray - - visible: image ? true : false - - onClicked: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } - } - } - - ParagraphTextType { - id: description - - Layout.topMargin: 16 - Layout.fillWidth: true - - text: root.descriptionText - - color: AmneziaStyle.color.mutedGray - - visible: root.descriptionText !== "" - } - } - - Keys.onEnterPressed: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } - - Keys.onReturnPressed: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } -} diff --git a/client/ui/qml/Controls2/HeaderTypeWithButton.qml b/client/ui/qml/Controls2/HeaderTypeWithButton.qml new file mode 100644 index 00000000..7feff3ce --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTypeWithButton.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +BaseHeaderType { + id: root + + property string actionButtonImage + property var actionButtonFunction + property alias actionButton: headerActionButton + + Component.onCompleted: { + headerRow.children.push(headerActionButton) + } + + ImageButtonType { + id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + Layout.alignment: Qt.AlignRight + image: root.actionButtonImage + imageColor: AmneziaStyle.color.paleGray + visible: image ? true : false + + onClicked: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + } + + Keys.onEnterPressed: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + + Keys.onReturnPressed: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } +} diff --git a/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml b/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml new file mode 100644 index 00000000..2fa4e735 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +BaseHeaderType { + id: root + + property var switcherFunction + property bool showSwitcher: false + property alias switcher: headerSwitcher + + Component.onCompleted: { + headerRow.children.push(headerSwitcher) + } + + SwitcherType { + id: headerSwitcher + Layout.alignment: Qt.AlignRight + visible: root.showSwitcher + + onToggled: { + if (switcherFunction && typeof switcherFunction === "function") { + switcherFunction(checked) + } + } + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index bee8ef7b..1c878f15 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -20,7 +20,11 @@ RadioButton { property string selectedColor: AmneziaStyle.color.transparent property string textColor: AmneziaStyle.color.paleGray + property string textDisabledColor: AmneziaStyle.color.mutedGray property string selectedTextColor: AmneziaStyle.color.goldenApricot + property string selectedTextDisabledColor: AmneziaStyle.color.burntOrange + property string descriptionColor: AmneziaStyle.color.mutedGray + property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray property string borderFocusedColor: AmneziaStyle.color.paleGray property int borderFocusedWidth: 1 @@ -30,6 +34,12 @@ RadioButton { property bool isFocusable: true + + property string radioButtonInnerCirclePressedSource: "qrc:/images/controls/radio-button-inner-circle-pressed.png" + property string radioButtonInnerCircleSource: "qrc:/images/controls/radio-button-inner-circle.png" + property string radioButtonPressedSource: "qrc:/images/controls/radio-button-pressed.svg" + property string radioButtonDefaultSource: "qrc:/images/controls/radio-button.svg" + Keys.onTabPressed: { FocusController.nextKeyTabItem() } @@ -94,14 +104,15 @@ RadioButton { if (showImage) { return imageSource } else if (root.pressed) { - return "qrc:/images/controls/radio-button-inner-circle-pressed.png" + return root.radioButtonInnerCirclePressedSource } else if (root.checked) { - return "qrc:/images/controls/radio-button-inner-circle.png" + return root.radioButtonInnerCircleSource } return "" } + opacity: root.enabled ? 1.0 : 0.3 anchors.centerIn: parent width: 24 @@ -113,12 +124,13 @@ RadioButton { if (showImage) { return "" } else if (root.pressed || root.checked) { - return "qrc:/images/controls/radio-button-pressed.svg" + return root.radioButtonPressedSource } else { - return "qrc:/images/controls/radio-button.svg" + return root.radioButtonDefaultSource } } + opacity: root.enabled ? 1.0 : 0.3 anchors.centerIn: parent width: 24 @@ -148,10 +160,11 @@ RadioButton { elide: root.textElide color: { - if (root.checked) { - return selectedTextColor + if (root.enabled) { + return root.checked ? selectedTextColor : textColor + } else { + return root.checked ? selectedTextDisabledColor : textDisabledColor } - return textColor } Layout.fillWidth: true @@ -164,7 +177,7 @@ RadioButton { CaptionTextType { id: description - color: AmneziaStyle.color.mutedGray + color: root.enabled ? root.descriptionColor : root.descriptionDisabledColor text: root.descriptionText visible: root.descriptionText !== "" diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index f5fdb29a..69b1f319 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -56,7 +56,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index d7afde1d..5fccb43a 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -39,7 +39,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index c22fdf0c..b8cf5f93 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -91,7 +91,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("AmneziaWG settings") diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 8c629b68..e8fd2b94 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -91,7 +91,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("AmneziaWG settings") diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 686ccd7b..7a0fafbd 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -76,7 +76,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Cloak settings") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 9cc628b7..2e00d54a 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -75,7 +75,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("OpenVPN settings") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 03b4e297..bba3eafe 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -32,7 +32,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 5786012b..63e60dcb 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -78,7 +78,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Shadowsocks settings") diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index a30c17e7..96ec1dc6 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -85,7 +85,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("WG settings") diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 10523b74..7b5180f3 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -77,7 +77,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("WG settings") } diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 90705d3e..ca30e0a9 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -75,7 +75,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("XRay settings") } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index cef29813..d534f991 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -43,7 +43,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 2deb315c..b58cb2e0 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -85,7 +85,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 1b77267a..b1daa0fb 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -77,7 +77,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -217,7 +217,7 @@ PageType { } } - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("SOCKS5 settings") diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 249c70c7..200beeb8 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -54,7 +54,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a47bb535..bb83ec92 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -29,7 +29,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 6e67ef1f..3e999cff 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -69,7 +69,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml index c6a2f98c..d8e24d42 100644 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -35,7 +35,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 7961594b..9b325b82 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -91,7 +91,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 44b2d2fa..d2042a76 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -38,7 +38,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 689502c1..48080c3a 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -93,7 +93,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 0ea8ec84..af629ebe 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -71,7 +71,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index b6920a8f..e31c92db 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -79,29 +79,22 @@ PageType { id: backButton } - RowLayout { - HeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("App split tunneling") + headerText: qsTr("App split tunneling") + enabled: root.pageEnabled + showSwitcher: true + switcher { + checked: AppSplitTunnelingModel.isTunnelingEnabled enabled: root.pageEnabled } - - SwitcherType { - id: switcher - - Layout.fillWidth: true - Layout.rightMargin: 16 - - enabled: root.pageEnabled - - checked: AppSplitTunnelingModel.isTunnelingEnabled - onToggled: { - AppSplitTunnelingModel.toggleSplitTunneling(checked) - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - } + switcherFunction: function(checked) { + AppSplitTunnelingModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 6f77a521..cbc04075 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -38,7 +38,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index d2dd4f2a..83e0f567 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -60,7 +60,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Back up your configuration") diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 69671f27..84b98230 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -36,7 +36,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -94,9 +94,7 @@ PageType { } } - DividerType { - visible: root.isAppSplitTinnelingEnabled - } + DividerType {} LabelWithButtonType { id: splitTunnelingButton2 @@ -119,29 +117,20 @@ PageType { visible: root.isAppSplitTinnelingEnabled } - SwitcherType { - id: killSwitchSwitcher + LabelWithButtonType { + id: killSwitchButton visible: !GC.isMobile() Layout.fillWidth: true - Layout.margins: 16 text: qsTr("KillSwitch") - descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.") + descriptionText: qsTr("Blocks network connections without VPN") + rightImageSource: "qrc:/images/controls/chevron-right.svg" parentFlickable: fl - checked: SettingsController.isKillSwitchEnabled() - checkable: !ConnectionController.isConnected - onCheckedChanged: { - if (checked !== SettingsController.isKillSwitchEnabled()) { - SettingsController.toggleKillSwitch(checked) - } - } - onClicked: { - if (!checkable) { - PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection")) - } + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsKillSwitch) } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index d78c5aa8..d5e2c52b 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -50,7 +50,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("DNS servers") diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml new file mode 100644 index 00000000..f6e0f5d7 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -0,0 +1,123 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" + +PageType { + id: root + + BackButtonType { + id: backButton + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("KillSwitch") + descriptionText: qsTr("Enable to ensure network traffic goes through a secure VPN tunnel, preventing accidental exposure of your IP and DNS queries if the connection drops") + + showSwitcher: true + switcher { + checked: SettingsController.isKillSwitchEnabled + enabled: !ConnectionController.isConnected + } + switcherFunction: function(checked) { + if (!ConnectionController.isConnected) { + SettingsController.isKillSwitchEnabled = checked + } else { + PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) + switcher.checked = SettingsController.isKillSwitchEnabled + } + } + } + + VerticalRadioButton { + id: softKillSwitch + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + checked: !SettingsController.strictKillSwitchEnabled + + text: qsTr("Soft KillSwitch") + descriptionText: qsTr("Internet connection is blocked if VPN connection drops accidentally") + + onClicked: function() { + SettingsController.strictKillSwitchEnabled = false + } + } + + DividerType {} + + VerticalRadioButton { + id: strictKillSwitch + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + checked: SettingsController.strictKillSwitchEnabled + + text: qsTr("Strict KillSwitch") + descriptionText: qsTr("Internet connection is blocked even if VPN was turned off manually or not started") + + onClicked: function() { + var headerText = qsTr("Just a little heads-up") + var descriptionText = qsTr("If you disconnect from VPN or the VPN connection drops while the Strict Kill Switch is turned on, your internet access will be disabled. To restore it, connect to VPN, change the Kill Switch mode or turn the Kill Switch off.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SettingsController.strictKillSwitchEnabled = true + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.topMargin: 32 + Layout.fillWidth: true + + enabled: true + text: qsTr("DNS Exceptions") + descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsKillSwitchExceptions) + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml new file mode 100644 index 00000000..d442b60c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml @@ -0,0 +1,302 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property bool pageEnabled: true + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + id: backButton + } + + BaseHeaderType { + enabled: root.pageEnabled + + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("DNS Exceptions") + descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + } + } + + ListView { + id: listView + + anchors.top: header.bottom + anchors.topMargin: 16 + anchors.bottom: parent.bottom + + width: parent.width + + enabled: root.pageEnabled + + property bool isFocusable: true + + cacheBuffer: 200 + displayMarginBeginning: 40 + displayMarginEnd: 40 + + ScrollBar.vertical: ScrollBarType { } + + footer: Item { + width: listView.width + height: addSitePanel.height + } + + footerPositioning: ListView.InlineFooter + + model: SortFilterProxyModel { + id: dnsFilterModel + sourceModel: AllowedDnsModel + filters: [ + RegExpFilter { + roleName: "ip" + pattern: ".*" + addSitePanel.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + ] + } + + clip: true + + reuseItems: true + + delegate: ColumnLayout { + id: delegateContent + + width: listView.width + + LabelWithButtonType { + id: site + Layout.fillWidth: true + + text: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + var headerText = qsTr("Delete ") + ip + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + AllowedDnsController.removeDns(dnsFilterModel.mapToSource(index)) + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + AddSitePanel { + id: addSitePanel + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + enabled: root.pageEnabled + placeholderText: qsTr("IPv4 address") + + onAddClicked: function(text) { + PageController.showBusyIndicator(true) + AllowedDnsController.addDns(text) + PageController.showBusyIndicator(false) + } + + onMoreClicked: { + moreActionsDrawer.openTriggered() + } + } + + DrawerType2 { + id: moreActionsDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import / Export addresses") + } + + LabelWithButtonType { + id: importSitesButton + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.openTriggered() + } + } + + DividerType {} + + LabelWithButtonType { + id: exportSitesButton + Layout.fillWidth: true + text: qsTr("Save address list") + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "amnezia_killswitch_exceptions.json" + } else { + fileName = SystemController.getFileName(qsTr("Save addresses"), + qsTr("Address files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_killswitch_exceptions", + true, + ".json") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + AllowedDnsController.exportDns(fileName) + moreActionsDrawer.closeTriggered() + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } + + DrawerType2 { + id: importSitesDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: Item { + implicitHeight: importSitesDrawer.expandedHeight + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.closeTriggered() + } + } + + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import address list") + } + + LabelWithButtonType { + id: importSitesButton2 + Layout.fillWidth: true + + text: qsTr("Replace address list") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open address file"), + qsTr("Address files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } + } + } + + DividerType {} + + LabelWithButtonType { + id: importSitesButton3 + Layout.fillWidth: true + text: qsTr("Add imported addresses to existing ones") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open address file"), + qsTr("Address files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } + } + } + + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + AllowedDnsController.importDns(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.closeTriggered() + moreActionsDrawer.closeTriggered() + } + + DividerType {} + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 2c760e37..5b20936c 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -40,7 +40,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index d350ebef..6ac81764 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -71,7 +71,7 @@ PageType { objectName: "backButton" } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index ade94ebb..fce9b2a3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -34,7 +34,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 554b6cbb..57e39ae8 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -31,7 +31,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index f5978687..292f903a 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -94,33 +94,22 @@ PageType { id: backButton } - RowLayout { - HeaderType { - enabled: root.pageEnabled + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - Layout.fillWidth: true - Layout.leftMargin: 16 - - headerText: qsTr("Split tunneling") - } - - SwitcherType { - id: switcher - - enabled: root.pageEnabled - - Layout.fillWidth: true - Layout.rightMargin: 16 - - function onToggledFunc() { - SitesModel.toggleSplitTunneling(this.checked) - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - } + headerText: qsTr("Split tunneling") + enabled: root.pageEnabled + showSwitcher: true + switcher { checked: SitesModel.isTunnelingEnabled - onToggled: { onToggledFunc() } - Keys.onEnterPressed: { onToggledFunc() } - Keys.onReturnPressed: { onToggledFunc() } + enabled: root.pageEnabled + } + switcherFunction: function(checked) { + SitesModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 134e73b6..30128de6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -35,7 +35,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 8 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index c3e3edbc..549eb381 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -28,7 +28,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 8 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index bcbb7fb2..7159ab59 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -45,7 +45,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + HeaderTypeWithButton { id: moreButton property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() @@ -76,7 +76,7 @@ PageType { anchors.right: parent.right spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 32 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index cdafa47f..63d4d5f6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -66,7 +66,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index ae04f635..096406e9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -59,7 +59,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { id: header implicitWidth: parent.width diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 1128761d..822931b8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -118,7 +118,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 50d1ea81..ac7fc4b2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -96,7 +96,7 @@ PageType { Layout.leftMargin: -16 } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 6b6b6038..7afab630 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -58,7 +58,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 3cf154e4..930efb57 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -33,7 +33,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 14096742..cfa9c90f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -61,7 +61,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { headerText: qsTr("New connection") } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index af208544..48f74acf 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -169,7 +169,7 @@ PageType { spacing: 0 - HeaderType { + HeaderTypeWithButton { id: header Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 70fd6292..82effb57 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -44,7 +44,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index ff875b39..3de0f035 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -52,6 +52,28 @@ void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) emit bytesChanged(receivedBytes, sentBytes); } +void VpnConnection::onKillSwitchModeChanged(bool enabled) +{ +#ifdef AMNEZIA_DESKTOP + if (!m_IpcClient) { + m_IpcClient = new IpcClient(this); + } + + if (!m_IpcClient->isSocketConnected()) { + if (!IpcClient::init(m_IpcClient)) { + qWarning() << "Error occurred when init IPC client"; + emit serviceIsNotReady(); + return; + } + } + + if (IpcClient::Interface()) { + qDebug() << "Set KillSwitch Strict mode enabled " << enabled; + IpcClient::Interface()->refreshKillSwitch(enabled); + } +#endif +} + void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { @@ -286,6 +308,7 @@ void VpnConnection::createProtocolConnections() void VpnConnection::appendKillSwitchConfig() { m_vpnConfiguration.insert(config_key::killSwitchOption, QVariant(m_settings->isKillSwitchEnabled()).toString()); + m_vpnConfiguration.insert(config_key::allowedDnsServers, QVariant(m_settings->allowedDnsServers()).toJsonValue()); } void VpnConnection::appendSplitTunnelingConfig() diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 0160edce..cb5aaaf9 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -48,14 +48,14 @@ public: public slots: void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void disconnectFromVpn(); - void addRoutes(const QStringList &ips); void deleteRoutes(const QStringList &ips); void flushDns(); + void onKillSwitchModeChanged(bool enabled); signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); diff --git a/deploy/build_linux.sh b/deploy/build_linux.sh index 57217a1e..aaf59eb5 100755 --- a/deploy/build_linux.sh +++ b/deploy/build_linux.sh @@ -41,6 +41,10 @@ if [ -z "${QT_VERSION+x}" ]; then QT_BIN_DIR=/opt/Qt/$QT_VERSION/gcc_64/bin elif [ -f $HOME/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then QT_BIN_DIR=$HOME/Qt/$QT_VERSION/gcc_64/bin + elif [ -f /usr/lib/qt6/bin/qmake ]; then + QT_BIN_DIR=/usr/lib/qt6/bin + elif [ -f /usr/lib/x86_64-linux-gnu/qt6/bin/qmake ]; then + QT_BIN_DIR=/usr/lib/x86_64-linux-gnu/qt6/bin fi fi @@ -56,7 +60,7 @@ echo "Building App..." cd $BUILD_DIR $QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -cmake --build . --config release +cmake --build . -j --config release # Build and run tests here diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index c0f031fe..4ecae9bc 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -29,6 +29,10 @@ class IpcInterface SLOT( void StopRoutingIpv6() ); SLOT( bool disableKillSwitch() ); + SLOT( bool disableAllTraffic() ); + SLOT( bool refreshKillSwitch( bool enabled ) ); + SLOT( bool addKillSwitchAllowedRange( const QStringList ranges ) ); + SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); SLOT( bool updateResolvers(const QString& ifname, const QList& resolvers) ); diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 17f34499..0c7f5295 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -8,21 +8,12 @@ #include "logger.h" #include "router.h" -#include "../core/networkUtilities.h" -#include "../client/protocols/protocols_defs.h" +#include "killswitch.h" + #ifdef Q_OS_WIN - #include "../client/platforms/windows/daemon/windowsdaemon.h" - #include "../client/platforms/windows/daemon/windowsfirewall.h" #include "tapcontroller_win.h" #endif -#ifdef Q_OS_LINUX - #include "../client/platforms/linux/daemon/linuxfirewall.h" -#endif - -#ifdef Q_OS_MACOS - #include "../client/platforms/macos/daemon/macosfirewall.h" -#endif IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent) @@ -188,174 +179,37 @@ void IpcServer::setLogsEnabled(bool enabled) } } +bool IpcServer::resetKillSwitchAllowedRange(QStringList ranges) +{ + return KillSwitch::instance()->resetAllowedRange(ranges); +} + +bool IpcServer::addKillSwitchAllowedRange(QStringList ranges) +{ + return KillSwitch::instance()->addAllowedRange(ranges); +} + +bool IpcServer::disableAllTraffic() +{ + return KillSwitch::instance()->disableAllTraffic(); +} + bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { -#ifdef Q_OS_WIN - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - return firewallManager->enableInterface(vpnAdapterIndex); -#endif - -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - int splitTunnelType = configStr.value("splitTunnelType").toInt(); - QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); - bool blockAll = 0; - bool allowNets = 0; - bool blockNets = 0; - QStringList allownets; - QStringList blocknets; - - if (splitTunnelType == 0) { - blockAll = true; - allowNets = true; - allownets.append(configStr.value("vpnServer").toString()); - } else if (splitTunnelType == 1) { - blockNets = true; - for (auto v : splitTunnelSites) { - blocknets.append(v.toString()); - } - } else if (splitTunnelType == 2) { - blockAll = true; - allowNets = true; - allownets.append(configStr.value("vpnServer").toString()); - for (auto v : splitTunnelSites) { - allownets.append(v.toString()); - } - } -#endif - -#ifdef Q_OS_LINUX - // double-check + ensure our firewall is installed and enabled - if (!LinuxFirewall::isInstalled()) - LinuxFirewall::install(); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); - LinuxFirewall::updateAllowNets(allownets); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); - LinuxFirewall::updateBlockNets(blocknets); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - dnsServers.append("127.0.0.1"); - dnsServers.append("127.0.0.53"); - LinuxFirewall::updateDNSServers(dnsServers); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); -#endif - -#ifdef Q_OS_MACOS - - // double-check + ensure our firewall is installed and enabled. This is necessary as - // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) - if (!MacOSFirewall::isInstalled()) - MacOSFirewall::install(); - - MacOSFirewall::ensureRootAnchorPriority(); - MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); - MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); - MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets); - - MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); - MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets); - MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); - - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); - MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); -#endif - - return true; + return KillSwitch::instance()->enableKillSwitch(configStr, vpnAdapterIndex); } bool IpcServer::disableKillSwitch() { -#ifdef Q_OS_WIN - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - return firewallManager->disableKillSwitch(); -#endif - -#ifdef Q_OS_LINUX - LinuxFirewall::uninstall(); -#endif - -#ifdef Q_OS_MACOS - MacOSFirewall::uninstall(); -#endif - - return true; + return KillSwitch::instance()->disableKillSwitch(); } bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) { -#ifdef Q_OS_WIN - InterfaceConfig config; - config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); - config.m_serverPublicKey = "openvpn"; - config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); - config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); - int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt(); - int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt(); - - int splitTunnelType = configStr.value("splitTunnelType").toInt(); - QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); - - QStringList AllowedIPAddesses; - - // Use APP split tunnel - if (splitTunnelType == 0 || splitTunnelType == 2) { - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0)); - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0)); - } - - if (splitTunnelType == 1) { - for (auto v : splitTunnelSites) { - QString ipRange = v.toString(); - if (ipRange.split('/').size() > 1) { - config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); - } else { - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32)); - } - } - } - - config.m_excludedAddresses.append(configStr.value("vpnServer").toString()); - if (splitTunnelType == 2) { - for (auto v : splitTunnelSites) { - QString ipRange = v.toString(); - config.m_excludedAddresses.append(ipRange); - } - } - - for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { - if (!i.isString()) { - break; - } - config.m_vpnDisabledApps.append(i.toString()); - } - - // killSwitch toggle - if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - firewallManager->enablePeerTraffic(config); - } - - WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex); - WindowsDaemon::instance()->activateSplitTunnel(config, vpnAdapterIndex); -#endif - return true; + return KillSwitch::instance()->enablePeerTraffic(configStr); +} + +bool IpcServer::refreshKillSwitch(bool enabled) +{ + return KillSwitch::instance()->refresh(enabled); } diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 9810046b..00d36354 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -34,9 +34,13 @@ public: virtual bool deleteTun(const QString &dev) override; virtual void StartRoutingIpv6() override; virtual void StopRoutingIpv6() override; + virtual bool disableAllTraffic() override; + virtual bool addKillSwitchAllowedRange(QStringList ranges) override; + virtual bool resetKillSwitchAllowedRange(QStringList ranges) override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; + virtual bool refreshKillSwitch( bool enabled ) override; virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; private: diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 28174774..aa7661fa 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -12,8 +12,47 @@ qt_standard_project_setup() configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +set(QSIMPLECRYPTO_DIR ${CMAKE_CURRENT_LIST_DIR}/../../client/3rd/QSimpleCrypto/src) + + +set(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../../client/3rd-prebuilt/3rd-prebuilt/openssl/") +set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib") + +set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include") +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib") +else() + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib") +endif() + + +if(WIN32) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include") + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib") + else() + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib") + endif() +elseif(APPLE AND NOT IOS) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a") +elseif(LINUX) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/linux/include") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a") +endif() + +set(OPENSSL_USE_STATIC_LIBS TRUE) + +include_directories( + ${OPENSSL_INCLUDE_DIR} + ${QSIMPLECRYPTO_DIR} +) + set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.h ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h @@ -22,12 +61,20 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/localserver.h ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.h ${CMAKE_CURRENT_LIST_DIR}/router.h + ${CMAKE_CURRENT_LIST_DIR}/killswitch.h ${CMAKE_CURRENT_LIST_DIR}/systemservice.h ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${QSIMPLECRYPTO_DIR}/include/QAead.h + ${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h + ${QSIMPLECRYPTO_DIR}/include/QRsa.h + ${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h + ${QSIMPLECRYPTO_DIR}/include/QX509.h + ${QSIMPLECRYPTO_DIR}/include/QX509Store.h ) set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.cpp ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp @@ -36,7 +83,13 @@ set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/main.cpp ${CMAKE_CURRENT_LIST_DIR}/router.cpp + ${CMAKE_CURRENT_LIST_DIR}/killswitch.cpp ${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp + ${QSIMPLECRYPTO_DIR}/sources/QAead.cpp + ${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp + ${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp + ${QSIMPLECRYPTO_DIR}/sources/QX509.cpp + ${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp ) # Mozilla headres @@ -133,6 +186,7 @@ if(WIN32) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.cpp ${CMAKE_CURRENT_LIST_DIR}/router_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemontunnel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsfirewall.cpp @@ -159,6 +213,8 @@ if(WIN32) gdi32 Advapi32 Kernel32 + ${OPENSSL_LIB_CRYPTO_PATH} + qt6keychain ) add_compile_definitions(_WINSOCKAPI_) @@ -203,6 +259,9 @@ if(APPLE) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.cpp ) + + set(LIBS ${OPENSSL_LIB_CRYPTO_PATH} qt6keychain) + endif() if(LINUX) @@ -233,6 +292,9 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp ) + + set(LIBS ${OPENSSL_LIB_CRYPTO_PATH} qt6keychain -static-libstdc++ -static-libgcc -ldl) + endif() include(${CMAKE_CURRENT_LIST_DIR}/../src/qtservice.cmake) @@ -245,6 +307,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + add_executable(${PROJECT} ${SOURCES} ${HEADERS}) target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS}) target_compile_definitions(${PROJECT} PRIVATE "MZ_$") diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp new file mode 100644 index 00000000..c44bd6a2 --- /dev/null +++ b/service/server/killswitch.cpp @@ -0,0 +1,358 @@ +#include "killswitch.h" + + +#include +#include + +#include "../client/protocols/protocols_defs.h" +#include "qjsonarray.h" +#include "version.h" + +#ifdef Q_OS_WIN + #include "../client/platforms/windows/daemon/windowsfirewall.h" + #include "../client/platforms/windows/daemon/windowsdaemon.h" +#endif + +#ifdef Q_OS_LINUX + #include "../client/platforms/linux/daemon/linuxfirewall.h" +#endif + +#ifdef Q_OS_MACOS + #include "../client/platforms/macos/daemon/macosfirewall.h" +#endif + +KillSwitch* s_instance = nullptr; + +KillSwitch* KillSwitch::instance() +{ + if (s_instance == nullptr) { + s_instance = new KillSwitch(qApp); + } + return s_instance; +} + +bool KillSwitch::init() +{ +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + m_appSettigns = QSharedPointer(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr)); +#endif +#ifdef Q_OS_MACOS + if (!MacOSFirewall::isInstalled()) { + MacOSFirewall::install(); + } + m_appSettigns = QSharedPointer(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr)); +#endif + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } + + return true; +} + +bool KillSwitch::refresh(bool enabled) +{ +#ifdef Q_OS_WIN + QSettings RegHLM("HKEY_LOCAL_MACHINE\\Software\\" + QString(ORGANIZATION_NAME) + + "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat); + RegHLM.setValue("strictKillSwitchEnabled", enabled); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled); +#endif + + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } else { + return disableKillSwitch(); + } + +} + +bool KillSwitch::isStrictKillSwitchEnabled() +{ +#ifdef Q_OS_WIN + QSettings RegHLM("HKEY_LOCAL_MACHINE\\Software\\" + QString(ORGANIZATION_NAME) + + "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat); + return RegHLM.value("strictKillSwitchEnabled", false).toBool(); +#endif + m_appSettigns->sync(); + return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool(); +} + +bool KillSwitch::disableKillSwitch() { +#ifdef Q_OS_LINUX + if (isStrictKillSwitchEnabled()) { + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false); + } else { + LinuxFirewall::uninstall(); + } +#endif + +#ifdef Q_OS_MACOS + if (isStrictKillSwitchEnabled()) { + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), false); + } else { + MacOSFirewall::uninstall(); + } +#endif + +#ifdef Q_OS_WIN + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } + return WindowsFirewall::create(this)->allowAllTraffic(); +#endif + + m_allowedRanges.clear(); + return true; +} + +bool KillSwitch::disableAllTraffic() { +#ifdef Q_OS_WIN + WindowsFirewall::create(this)->enableInterface(-1); +#endif +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); +#endif +#ifdef Q_OS_MACOS + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) + MacOSFirewall::install(); + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); +#endif + m_allowedRanges.clear(); + return true; +} + +bool KillSwitch::resetAllowedRange(const QStringList &ranges) { + + m_allowedRanges = ranges; + +#ifdef Q_OS_LINUX + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true); + LinuxFirewall::updateAllowNets(m_allowedRanges); +#endif + +#ifdef Q_OS_MACOS + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), true, QStringLiteral("allownets"), m_allowedRanges); +#endif + +#ifdef Q_OS_WIN + if (isStrictKillSwitchEnabled()) { + WindowsFirewall::create(this)->enableInterface(-1); + } + WindowsFirewall::create(this)->allowTrafficRange(m_allowedRanges); +#endif + + return true; +} + +bool KillSwitch::addAllowedRange(const QStringList &ranges) { + for (const QString &range : ranges) { + if (!range.isEmpty() && !m_allowedRanges.contains(range)) { + m_allowedRanges.append(range); + } + } + + return resetAllowedRange(m_allowedRanges); +} + +bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { +#ifdef Q_OS_WIN + InterfaceConfig config; + config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); + config.m_serverPublicKey = "openvpn"; + config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); + config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); + int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt(); + int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt(); + + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + + // Use APP split tunnel + if (splitTunnelType == 0 || splitTunnelType == 2) { + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0)); + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0)); + } + + if (splitTunnelType == 1) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + if (ipRange.split('/').size() > 1) { + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); + } else { + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32)); + } + } + } + + config.m_excludedAddresses.append(configStr.value("vpnServer").toString()); + if (splitTunnelType == 2) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + config.m_excludedAddresses.append(ipRange); + } + } + + for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { + if (!i.isString()) { + break; + } + config.m_vpnDisabledApps.append(i.toString()); + } + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + config.m_allowedDnsServers.append(dns.toString()); + } + + // killSwitch toggle + if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { + WindowsFirewall::create(this)->enablePeerTraffic(config); + } + + WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex); + WindowsDaemon::instance()->activateSplitTunnel(config, vpnAdapterIndex); +#endif + return true; +} + +bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { +#ifdef Q_OS_WIN + return WindowsFirewall::create(this)->enableInterface(vpnAdapterIndex); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + bool blockAll = 0; + bool allowNets = 0; + bool blockNets = 0; + QStringList allownets; + QStringList blocknets; + + if (splitTunnelType == 0) { + blockAll = true; + allowNets = true; + allownets.append(configStr.value("vpnServer").toString()); + } else if (splitTunnelType == 1) { + blockNets = true; + for (auto v : splitTunnelSites) { + blocknets.append(v.toString()); + } + } else if (splitTunnelType == 2) { + blockAll = true; + allowNets = true; + allownets.append(configStr.value("vpnServer").toString()); + for (auto v : splitTunnelSites) { + allownets.append(v.toString()); + } + } +#endif + +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + + // double-check + ensure our firewall is installed and enabled + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); + LinuxFirewall::updateAllowNets(allownets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); + LinuxFirewall::updateBlockNets(blocknets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + dnsServers.append("127.0.0.1"); + dnsServers.append("127.0.0.53"); + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + dnsServers.append(dns.toString()); + } + + LinuxFirewall::updateDNSServers(dnsServers); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); +#endif + +#ifdef Q_OS_MACOS + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) + MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + dnsServers.append(dns.toString()); + } + + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); +#endif + return true; +} diff --git a/service/server/killswitch.h b/service/server/killswitch.h new file mode 100644 index 00000000..519e2ed2 --- /dev/null +++ b/service/server/killswitch.h @@ -0,0 +1,31 @@ +#ifndef KILLSWITCH_H +#define KILLSWITCH_H + +#include +#include + +#include "secure_qsettings.h" + +class KillSwitch : public QObject +{ + Q_OBJECT +public: + static KillSwitch *instance(); + bool init(); + bool refresh(bool enabled); + bool disableKillSwitch(); + bool disableAllTraffic(); + bool enablePeerTraffic(const QJsonObject &configStr); + bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex); + bool resetAllowedRange(const QStringList &ranges); + bool addAllowedRange(const QStringList &ranges); + bool isStrictKillSwitchEnabled(); + +private: + KillSwitch(QObject* parent) {}; + QStringList m_allowedRanges; + QSharedPointer m_appSettigns; + +}; + +#endif // KILLSWITCH_H diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index 8a5079cb..4f005a59 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -5,13 +5,12 @@ #include "ipc.h" #include "localserver.h" -#include "utilities.h" -#include "router.h" +#include "killswitch.h" #include "logger.h" #ifdef Q_OS_WIN -#include "tapcontroller_win.h" + #include "tapcontroller_win.h" #endif namespace { @@ -47,6 +46,8 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent), return; } + KillSwitch::instance()->init(); + #ifdef Q_OS_LINUX // Signal handling for a proper shutdown. QObject::connect(qApp, &QCoreApplication::aboutToQuit,