diff --git a/libs/models/kcmidentitymodel.cpp b/libs/models/kcmidentitymodel.cpp --- a/libs/models/kcmidentitymodel.cpp +++ b/libs/models/kcmidentitymodel.cpp @@ -90,7 +90,9 @@ return tooltip; } else if (role == KcmVpnConnectionExportable) { if (type == NetworkManager::ConnectionSettings::Vpn && vpnSetting) { - return (vpnSetting->serviceType().endsWith(QLatin1String("vpnc")) || vpnSetting->serviceType().endsWith(QLatin1String("openvpn"))); + return (vpnSetting->serviceType().endsWith(QLatin1String("vpnc")) || + vpnSetting->serviceType().endsWith(QLatin1String("openvpn")) || + vpnSetting->serviceType().endsWith(QLatin1String("wireguard"))); } return false; } else { diff --git a/vpn/CMakeLists.txt b/vpn/CMakeLists.txt --- a/vpn/CMakeLists.txt +++ b/vpn/CMakeLists.txt @@ -11,4 +11,5 @@ add_subdirectory(sstp) add_subdirectory(strongswan) add_subdirectory(vpnc) +add_subdirectory(wireguard) diff --git a/vpn/wireguard/CMakeLists.txt b/vpn/wireguard/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/vpn/wireguard/CMakeLists.txt @@ -0,0 +1,34 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasmanetworkmanagement_wireguardui\") + +set(wireguard_SRCS + ../../libs/debug.cpp + wireguard.cpp + wireguardwidget.cpp + wireguardauth.cpp + wireguardadvancedwidget.cpp +) + +ki18n_wrap_ui(wireguard_SRCS wireguard.ui wireguardadvanced.ui wireguardauth.ui) + +add_library(plasmanetworkmanagement_wireguardui ${wireguard_SRCS}) + +kcoreaddons_desktop_to_json(plasmanetworkmanagement_wireguardui plasmanetworkmanagement_wireguardui.desktop) + +target_link_libraries(plasmanetworkmanagement_wireguardui + plasmanm_internal + plasmanm_editor + Qt5::Widgets + Qt5::Network + Qt5::DBus + KF5::NetworkManagerQt + KF5::Service + KF5::Completion + KF5::I18n + KF5::WidgetsAddons + KF5::KIOWidgets + KF5::CoreAddons +) + +install(TARGETS plasmanetworkmanagement_wireguardui DESTINATION ${PLUGIN_INSTALL_DIR}) + +install(FILES plasmanetworkmanagement_wireguardui.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/vpn/wireguard/nm-wireguard-service.h b/vpn/wireguard/nm-wireguard-service.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/nm-wireguard-service.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-openvpn-service - openvpn integration with NetworkManager + * + * Copyright 2018 Bruce Anderson + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef NM_WIREGUARD_SERVICE_H +#define NM_WIREGUARD_SERVICE_H + +#define NM_DBUS_SERVICE_WIREGUARD "org.freedesktop.NetworkManager.wireguard" +#define NM_DBUS_INTERFACE_WIREGUARD "org.freedesktop.NetworkManager.wireguard" +#define NM_DBUS_PATH_WIREGUARD "/org/freedesktop/NetworkManager/wireguard" + +#define NM_WG_KEY_ADDR_IP4 "local-ip4" +#define NM_WG_KEY_ADDR_IP6 "local-ip6" +#define NM_WG_KEY_LISTEN_PORT "local-listen-port" +#define NM_WG_KEY_PRIVATE_KEY "local-private-key" +#define NM_WG_KEY_DNS "connection-dns" +#define NM_WG_KEY_MTU "connection-mtu" +#define NM_WG_KEY_TABLE "connection_table" +#define NM_WG_KEY_PRE_UP "script-pre-up" +#define NM_WG_KEY_POST_UP "script-post-up" +#define NM_WG_KEY_PRE_DOWN "script-pre-down" +#define NM_WG_KEY_POST_DOWN "script-post-down" +#define NM_WG_KEY_PUBLIC_KEY "peer-public-key" +#define NM_WG_KEY_ALLOWED_IPS "peer-allowed-ips" +#define NM_WG_KEY_ENDPOINT "peer-endpoint" +#define NM_WG_KEY_PRESHARED_KEY "peer-preshared-key" +#define NM_WG_KEY_FWMARK "fwmark" + +#endif /* NM_WIREGUARD_SERVICE_H */ diff --git a/vpn/wireguard/passwordfield.h b/vpn/wireguard/passwordfield.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/passwordfield.h @@ -0,0 +1,71 @@ +/* + Copyright 2015 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef PLASMA_NM_PASSWORD_FIELD_H +#define PLASMA_NM_PASSWORD_FIELD_H + +#include +#include + +#include + +class Q_DECL_EXPORT PasswordField : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool passwordModeEnabled WRITE setPasswordModeEnabled) +public: + enum PasswordOption { + StoreForUser, + StoreForAllUsers, + AlwaysAsk, + NotRequired + }; + + explicit PasswordField(QWidget *parent = 0, Qt::WindowFlags f = Qt::WindowFlags()); + + void setMaxLength(int maxLength); + void setPasswordModeEnabled(bool passwordMode); + void setPasswordOptionsEnabled(bool enable); + void setPasswordNotRequiredEnabled(bool enable); + + PasswordOption passwordOption() const; + void setPasswordOption(PasswordOption option); + + void setText(const QString &text); + QString text() const; + +private Q_SLOTS: + void changePasswordOption(int index); + void showToggleEchoModeAction(const QString &text); + void toggleEchoMode(); + +Q_SIGNALS: + void textChanged(const QString &text); + void passwordOptionChanged(PasswordOption option); + +private: + PasswordOption m_currentPasswordOption; + QVBoxLayout *m_layout; + QLineEdit *m_passwordField; + QComboBox *m_passwordOptionsMenu; + QAction *m_toggleEchoModeAction; +}; + +#endif // PLASMA_NM_PASSWORD_FIELD_H diff --git a/vpn/wireguard/passwordfield.cpp b/vpn/wireguard/passwordfield.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/passwordfield.cpp @@ -0,0 +1,166 @@ +/* + Copyright 2015 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "passwordfield.h" + +#include +#include + +#include +#include +#include + +PasswordField::PasswordField(QWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f) + , m_currentPasswordOption(StoreForUser) +{ + m_layout = new QVBoxLayout(this); + // The widget will be already in layout, thus reset content margins + // to align it with the rest of widgets + m_layout->setContentsMargins(0, 0, 0, 0); + + m_passwordField = new QLineEdit(this); + connect(m_passwordField, &QLineEdit::textChanged, this, &PasswordField::textChanged); + + if (KAuthorized::authorize(QStringLiteral("lineedit_reveal_password"))) { + m_toggleEchoModeAction = m_passwordField->addAction(QIcon::fromTheme(QStringLiteral("visibility")), QLineEdit::TrailingPosition); + m_toggleEchoModeAction->setVisible(false); + m_toggleEchoModeAction->setToolTip(i18n("Change the visibility of the password")); + connect(m_passwordField, &QLineEdit::textChanged, this, &PasswordField::showToggleEchoModeAction); + connect(m_toggleEchoModeAction, &QAction::triggered, this, &PasswordField::toggleEchoMode); + } + + m_layout->addWidget(m_passwordField); + + m_passwordOptionsMenu = new QComboBox(this); + m_passwordOptionsMenu->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + + m_passwordOptionsMenu->addItem(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Store password for this user only (encrypted)"), StoreForUser); + m_passwordOptionsMenu->addItem(QIcon::fromTheme(QStringLiteral("document-save-all")), i18n("Store password for all users (not encrypted)"), StoreForAllUsers); + m_passwordOptionsMenu->addItem(QIcon::fromTheme(QStringLiteral("dialog-messages")), i18n("Ask for this password every time"), AlwaysAsk); + // Do not add by default + // m_passwordOptionsMenu->addItem(QIcon::fromTheme(QStringLiteral("document-close")), i18n("This password is not required"), NotRequired); + + if (KWallet::Wallet::isEnabled()) { + m_passwordOptionsMenu->setCurrentIndex(0); + } else { + m_passwordOptionsMenu->setCurrentIndex(1); + m_currentPasswordOption = StoreForAllUsers; + } + + connect(m_passwordOptionsMenu, static_cast(&QComboBox::currentIndexChanged), this, &PasswordField::changePasswordOption); + + // Disable by default + m_passwordOptionsMenu->setVisible(false); + // m_layout->addWidget(m_passwordOptionsMenu); + + setLayout(m_layout); +} + +void PasswordField::setMaxLength(int maxLength) +{ + m_passwordField->setMaxLength(maxLength); +} + +void PasswordField::setPasswordModeEnabled(bool enable) +{ + m_passwordField->setEchoMode(enable ? QLineEdit::Password : QLineEdit::Normal); +} + +void PasswordField::setPasswordOptionsEnabled(bool enable) +{ + if (enable) { + m_layout->addWidget(m_passwordOptionsMenu); + m_passwordOptionsMenu->setVisible(true); + } else { + m_layout->removeWidget(m_passwordOptionsMenu); + m_passwordOptionsMenu->setVisible(false); + } +} + +void PasswordField::setPasswordNotRequiredEnabled(bool enable) +{ + if (enable) { + const int index = m_passwordOptionsMenu->findData(NotRequired); + if (index == -1) { + m_passwordOptionsMenu->addItem(QIcon::fromTheme(QStringLiteral("document-close")), i18n("This password is not required"), NotRequired); + } + } else { + const int index = m_passwordOptionsMenu->findData(NotRequired); + if (index != -1) { + m_passwordOptionsMenu->removeItem(index); + } + } +} + +void PasswordField::setText(const QString &text) +{ + m_passwordField->setText(text); +} + +QString PasswordField::text() const +{ + return m_passwordField->text(); +} + +PasswordField::PasswordOption PasswordField::passwordOption() const +{ + return m_currentPasswordOption; +} + +void PasswordField::setPasswordOption(PasswordField::PasswordOption option) +{ + const int index = m_passwordOptionsMenu->findData(option); + if (index != -1) { + m_passwordOptionsMenu->setCurrentIndex(index); + } +} + +void PasswordField::showToggleEchoModeAction(const QString &text) +{ + m_toggleEchoModeAction->setVisible(!text.isEmpty()); +} + +void PasswordField::toggleEchoMode() +{ + if (m_passwordField->echoMode() == QLineEdit::Password) { + m_passwordField->setEchoMode(QLineEdit::Normal); + m_toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("hint"))); + } else if (m_passwordField->echoMode() == QLineEdit::Normal) { + m_passwordField->setEchoMode(QLineEdit::Password); + m_toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("visibility"))); + } +} + +void PasswordField::changePasswordOption(int index) +{ + Q_UNUSED(index); + + m_currentPasswordOption = (PasswordOption)m_passwordOptionsMenu->currentData().toUInt(); + + if (m_currentPasswordOption == PasswordField::NotRequired || m_currentPasswordOption == PasswordField::AlwaysAsk) { + m_passwordField->clear(); + m_passwordField->setDisabled(true); + } else { + m_passwordField->setEnabled(true); + } + + Q_EMIT passwordOptionChanged(m_currentPasswordOption); +} diff --git a/vpn/wireguard/plasmanetworkmanagement_wireguardui.desktop b/vpn/wireguard/plasmanetworkmanagement_wireguardui.desktop new file mode 100644 --- /dev/null +++ b/vpn/wireguard/plasmanetworkmanagement_wireguardui.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Type=Service +Icon= +ServiceTypes=PlasmaNetworkManagement/VpnUiPlugin +X-KDE-Library=plasmanetworkmanagement_wireguardui +X-NetworkManager-Services=org.freedesktop.NetworkManager.wireguard +X-KDE-PluginInfo-Author=Bruce Anderson +X-KDE-PluginInfo-Email=banderson19com@san.rr.com +X-KDE-PluginInfo-Name=plasmanetworkmanagement_wireguardui +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website= +X-KDE-PluginInfo-Category=VPNService +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=WireGuard +Comment=Handles WireGuard VPN connections diff --git a/vpn/wireguard/wireguard.h b/vpn/wireguard/wireguard.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguard.h @@ -0,0 +1,47 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PLASMANM_WIREGUARD_H +#define PLASMANM_WIREGUARD_H + +#include "vpnuiplugin.h" +#include "wireguardstrings.h" + +#include +#include + +class Q_DECL_EXPORT WireGuardUiPlugin : public VpnUiPlugin +{ +Q_OBJECT +public: + explicit WireGuardUiPlugin(QObject* parent = nullptr, const QVariantList& = QVariantList()); + ~WireGuardUiPlugin() override; + SettingWidget * widget(const NetworkManager::VpnSetting::Ptr &setting, QWidget* parent = nullptr) override; + SettingWidget * askUser(const NetworkManager::VpnSetting::Ptr &setting, QWidget * parent = nullptr) override; + + QString suggestedFileName(const NetworkManager::ConnectionSettings::Ptr &connection) const override; + QString supportedFileExtensions() const override; + NMVariantMapMap importConnectionSettings(const QString &fileName) override; + bool exportConnectionSettings(const NetworkManager::ConnectionSettings::Ptr &connection, const QString &fileName) override; +private: + struct WireGuardRegexStrings regexStrings; +}; + +#endif // PLASMANM_WIREGUARD_H diff --git a/vpn/wireguard/wireguard.cpp b/vpn/wireguard/wireguard.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguard.cpp @@ -0,0 +1,424 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "wireguard.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "wireguardwidget.h" +#include "wireguardauth.h" + +#include + +#include "nm-wireguard-service.h" +#include "wireguardstrings.h" + +K_PLUGIN_FACTORY_WITH_JSON(WireGuardUiPluginFactory, "plasmanetworkmanagement_wireguardui.json", registerPlugin();) + +#define NMV_WG_TAG_INTERFACE "Interface" +#define NMV_WG_TAG_PRIVATE_KEY "PrivateKey" +#define NMV_WG_TAG_LISTEN_PORT "ListenPort" +#define NMV_WG_TAG_ADDRESS "Address" +#define NMV_WG_TAG_DNS "DNS" +#define NMV_WG_TAG_MTU "MTU" +#define NMV_WG_TAG_TABLE "Table" +#define NMV_WG_TAG_PRE_UP "PreUp" +#define NMV_WG_TAG_POST_UP "PostUp" +#define NMV_WG_TAG_PRE_DOWN "PreDown" +#define NMV_WG_TAG_POST_DOWN "PostDown" +#define NMV_WG_TAG_FWMARK "FwMark" +#define NMV_WG_ASSIGN "=" + +#define NMV_WG_TAG_PEER "Peer" +#define NMV_WG_TAG_PUBLIC_KEY "PublicKey" +#define NMV_WG_TAG_ALLOWED_IPS "AllowedIPs" +#define NMV_WG_TAG_ENDPOINT "Endpoint" +#define NMV_WG_TAG_PRESHARED_KEY "PresharedKey" + + +WireGuardUiPlugin::WireGuardUiPlugin(QObject *parent, const QVariantList &) : VpnUiPlugin(parent) +{ + regexStrings.ip4Range = new QString( + "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"); + regexStrings.ip4Port = new QString( + "([1-9][0-9]{0,3}|[1-5][0-9]{4,4}|6[0-4][0-9]{3,3}|65[0-4][0-9]{2,2}|655[0-2][0-9]|6553[0-5])"); + regexStrings.ip4Address = new QString(*regexStrings.ip4Range + "\\." + + *regexStrings.ip4Range + "\\." + + *regexStrings.ip4Range + "\\." + + *regexStrings.ip4Range); + regexStrings.ip4AddressWithCidr = new QString(*regexStrings.ip4Address + + "/" + "(?:[0-9]|[1-2][0-9]|[3][0-2])"); + regexStrings.ip6Test1 = new QString("([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}"); + regexStrings.ip6Test2 = new QString("([0-9a-fA-F]{1,4}:){1,7}:"); + regexStrings.ip6Test3 = new QString("([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}"); + regexStrings.ip6Test4 = new QString("([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}"); + regexStrings.ip6Test5 = new QString("|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}"); + regexStrings.ip6Test6 = new QString("|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}"); + regexStrings.ip6Test7 = new QString("([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"); + regexStrings.ip6Test8 = new QString("[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})"); + regexStrings.ip6Test9 = new QString(":((:[0-9a-fA-F]{1,4}){1,7}|:)"); + regexStrings.ip6Cidr = new QString("(/0|[1-9][0-9]{0,1}|/1[0-1][0-9]|/12[0-8])"); + regexStrings.ip6AddressWithCidr = new QString("(" + *regexStrings.ip6Test1 + + "|" + *regexStrings.ip6Test2 + + "|" + *regexStrings.ip6Test3 + + "|" + *regexStrings.ip6Test4 + + "|" + *regexStrings.ip6Test5 + + "|" + *regexStrings.ip6Test6 + + "|" + *regexStrings.ip6Test7 + + "|" + *regexStrings.ip6Test8 + + "|" + *regexStrings.ip6Test9 + + ")" + *regexStrings.ip6Cidr); + regexStrings.ip4Orip6Address = new QString("(" + *regexStrings.ip4AddressWithCidr + "|" + + *regexStrings.ip6AddressWithCidr + ")"); + regexStrings.keySpec = new QString("[0-9a-zA-Z\\+/]{43,43}="); +} + +WireGuardUiPlugin::~WireGuardUiPlugin() +{ + delete regexStrings.ip4Range; + delete regexStrings.ip4Port; + delete regexStrings.ip4Address; + delete regexStrings.ip4AddressWithCidr; + delete regexStrings.ip6Test1; + delete regexStrings.ip6Test2; + delete regexStrings.ip6Test3; + delete regexStrings.ip6Test4; + delete regexStrings.ip6Test5; + delete regexStrings.ip6Test6; + delete regexStrings.ip6Test7; + delete regexStrings.ip6Test8; + delete regexStrings.ip6Test9; + delete regexStrings.ip6Cidr; + delete regexStrings.ip6AddressWithCidr; + delete regexStrings.ip4Orip6Address; + delete regexStrings.keySpec; +} + +SettingWidget *WireGuardUiPlugin::widget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent) +{ + return new WireGuardSettingWidget(setting, regexStrings, parent); +} + +SettingWidget *WireGuardUiPlugin::askUser(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent) +{ + return new WireGuardAuthWidget(setting, parent); +} + +QString WireGuardUiPlugin::suggestedFileName(const NetworkManager::ConnectionSettings::Ptr &connection) const +{ + return connection->id() + "_wireguard.conf"; +} + +QString WireGuardUiPlugin::supportedFileExtensions() const +{ + return "*.conf"; +} + +NMVariantMapMap WireGuardUiPlugin::importConnectionSettings(const QString &fileName) +{ + NMVariantMapMap result; + + KConfig importFile(fileName, KConfig::NoGlobals); + KConfigGroup interfaceGroup = importFile.group(NMV_WG_TAG_INTERFACE); + KConfigGroup peerGroup = importFile.group(NMV_WG_TAG_PEER);; + + const QString connectionName = QFileInfo(fileName).completeBaseName(); + NMStringMap dataMap; + QVariantMap ipv4Data; + + bool haveAddress = false; + bool havePrivateKey = false; + bool havePublicKey = false; + bool haveAllowedIps = false; + QString value; + QIntValidator *intValidator = new QIntValidator(nullptr); + + // The config file must have both [Interface] and [Peer] sections + if (!importFile.groupList().contains("Interface") + || !importFile.groupList().contains("Peer")) { + mError = VpnUiPlugin::Error; + mErrorMessage = i18n("Could not open file"); + return result; + } + + // Address + value = interfaceGroup.readEntry(NMV_WG_TAG_ADDRESS); + { + QStringList addressList; + addressList << value.split(QRegExp("\\s*,\\s*")); + for (int i = 0;i < addressList.size(); i++) { + QPair addressIn = QHostAddress::parseSubnet(addressList[i]); + if (addressIn.first.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + dataMap.insert(QLatin1String(NM_WG_KEY_ADDR_IP4), addressList[i]); + haveAddress = true; + } + else if (addressIn.first.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv6Protocol) { + dataMap.insert(QLatin1String(NM_WG_KEY_ADDR_IP6), addressList[i]); + haveAddress = true; + } else { // Error condition + return result; + } + } + } + + // Listen Port + value = interfaceGroup.readEntry(NMV_WG_TAG_LISTEN_PORT); + if (value.length() > 0) { + intValidator->setRange(0, 65535); + int pos = 0; + if (intValidator->validate(value, pos) != QValidator::State::Invalid) + dataMap.insert(QLatin1String(NM_WG_KEY_LISTEN_PORT), value); + else + return result; + } + + // Private Key + value = interfaceGroup.readEntry(NMV_WG_TAG_PRIVATE_KEY); + if (value.length() > 0) { + QRegExp validatorRegex(*regexStrings.keySpec); + QRegExpValidator validator(validatorRegex); + int pos = 0; + if (validator.validate(value, pos) != QValidator::State::Invalid) { + dataMap.insert(QLatin1String(NM_WG_KEY_PRIVATE_KEY), value); + havePrivateKey = true; + } + else { + return result; + } + } + + // DNS + value = interfaceGroup.readEntry(NMV_WG_TAG_DNS); + if (value.length() > 0) { + QHostAddress testAddress(value); + if (testAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) + dataMap.insert(QLatin1String(NM_WG_KEY_DNS), value); + else + return result; + } + + // MTU + value = interfaceGroup.readEntry(NMV_WG_TAG_MTU); + if (value.length() > 0) { + int pos; + intValidator->setRange(0, 100000); + if (intValidator->validate(value, pos) != QValidator::State::Invalid) + dataMap.insert(QLatin1String(NM_WG_KEY_MTU), value); + else + return result; + } + + // Table + value = interfaceGroup.readEntry(NMV_WG_TAG_TABLE); + if (value.length() > 0) { + dataMap.insert(QLatin1String(NM_WG_KEY_TABLE), value); + } + + // Pre-up script + value = interfaceGroup.readEntry(NMV_WG_TAG_PRE_UP); + if (value.length() > 0) + dataMap.insert(QLatin1String(NM_WG_KEY_PRE_UP), value); + + // Post-up script + value = interfaceGroup.readEntry(NMV_WG_TAG_POST_UP); + if (value.length() > 0) + dataMap.insert(QLatin1String(NM_WG_KEY_POST_UP), value); + + // Pre-down script + value = interfaceGroup.readEntry(NMV_WG_TAG_PRE_DOWN); + if (value.length() > 0) + dataMap.insert(QLatin1String(NM_WG_KEY_PRE_DOWN), value); + + // Post-down script + value = interfaceGroup.readEntry(NMV_WG_TAG_POST_DOWN); + if (value.length() > 0) + dataMap.insert(QLatin1String(NM_WG_KEY_POST_DOWN), value); + + // Public Key + value = peerGroup.readEntry(NMV_WG_TAG_PUBLIC_KEY); + if (value.length() > 0) { + QRegExp validatorRegex(*regexStrings.keySpec); + QRegExpValidator validator(validatorRegex); + int pos = 0; + if (validator.validate(value, pos) != QValidator::State::Invalid) { + dataMap.insert(QLatin1String(NM_WG_KEY_PUBLIC_KEY), value); + havePublicKey = true; + } + else { + return result; + } + } + + // Allowed IPs + value = peerGroup.readEntry(NMV_WG_TAG_ALLOWED_IPS); + if (value.length() > 0) { + int pos = 0; + QRegExp allowedIPsRegex ("(" + *regexStrings.ip4Orip6Address + + ", *)*" + *regexStrings.ip4Orip6Address); + QRegExpValidator *validator = new QRegExpValidator(allowedIPsRegex, this); + if (validator->validate(value, pos) != QValidator::State::Invalid) { + dataMap.insert(QLatin1String(NM_WG_KEY_ALLOWED_IPS), value); + haveAllowedIps = true; + } else { + return result; + } + } + + // Endpoint + value = peerGroup.readEntry(NMV_WG_TAG_ENDPOINT); + if (value.length() > 0) + dataMap.insert(QLatin1String(NM_WG_KEY_ENDPOINT), value); + + // Preshared Key + value = peerGroup.readEntry(NMV_WG_TAG_PRESHARED_KEY); + if (value.length() > 0) { + QRegExp validatorRegex(*regexStrings.keySpec); + QRegExpValidator validator(validatorRegex); + int pos = 0; + if (validator.validate(value, pos) != QValidator::State::Invalid) { + dataMap.insert(QLatin1String(NM_WG_KEY_PRESHARED_KEY), value); + } + else { + return result; + } + } + + // Check to see that we have all the required fields + if (!haveAddress || !havePrivateKey || !havePublicKey || !haveAllowedIps) { + + mError = VpnUiPlugin::Error; + mErrorMessage = i18n("File %1 is not a valid WireGuard configuration.", fileName); + return result; + } + + NetworkManager::VpnSetting setting; + setting.setServiceType(QLatin1String(NM_DBUS_SERVICE_WIREGUARD)); + setting.setData(dataMap); + + QVariantMap conn; + conn.insert("id", connectionName); + conn.insert("type", "vpn"); + result.insert("connection", conn); + result.insert("vpn", setting.toMap()); + + return result; +} + +bool WireGuardUiPlugin::exportConnectionSettings(const NetworkManager::ConnectionSettings::Ptr &connection, const QString &fileName) +{ + KConfig exportFile(fileName, KConfig::NoGlobals); + KConfigGroup interfaceGroup = exportFile.group(NMV_WG_TAG_INTERFACE); + KConfigGroup peerGroup = exportFile.group(NMV_WG_TAG_PEER); + + NMStringMap dataMap; + QString value; + + NetworkManager::VpnSetting::Ptr vpnSetting = connection->setting(NetworkManager::Setting::Vpn).dynamicCast(); + dataMap = vpnSetting->data(); + + if (dataMap.contains(QLatin1String(NM_WG_KEY_ADDR_IP4))) { + value = dataMap[NM_WG_KEY_ADDR_IP4]; + if (dataMap.contains(QLatin1String(NM_WG_KEY_ADDR_IP6))) { + value += "," + dataMap[NM_WG_KEY_ADDR_IP6]; + } + } else if (dataMap.contains(QLatin1String(NM_WG_KEY_ADDR_IP4))) { + value = dataMap[NM_WG_KEY_ADDR_IP4]; + } else { + return false; + } + interfaceGroup.writeEntry(NMV_WG_TAG_ADDRESS, value); + + // Do Private Key + if (dataMap.contains(QLatin1String(NM_WG_KEY_PRIVATE_KEY))) + interfaceGroup.writeEntry(NMV_WG_TAG_PRIVATE_KEY, dataMap[NM_WG_KEY_PRIVATE_KEY]); + else + return false; + + // Do DNS (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_DNS))) + interfaceGroup.writeEntry(NMV_WG_TAG_DNS, dataMap[NM_WG_KEY_DNS]); + + // Do MTU (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_MTU))) + interfaceGroup.writeEntry(NMV_WG_TAG_MTU, dataMap[NM_WG_KEY_MTU]); + + // Do Table number (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_TABLE))) + interfaceGroup.writeEntry(NMV_WG_TAG_TABLE, dataMap[NM_WG_KEY_TABLE]); + + // Do Listen Port (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_LISTEN_PORT))) + interfaceGroup.writeEntry(NMV_WG_TAG_LISTEN_PORT, dataMap[NM_WG_KEY_LISTEN_PORT]); + + // Do FwMark (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_FWMARK))) + interfaceGroup.writeEntry(NMV_WG_TAG_FWMARK, dataMap[NM_WG_KEY_FWMARK]); + + // Do the Pre, Post, Up, Down scripte (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_PRE_UP))) + interfaceGroup.writeEntry(NMV_WG_TAG_PRE_UP, dataMap[NM_WG_KEY_PRE_UP]); + + if (dataMap.contains(QLatin1String(NM_WG_KEY_POST_UP))) + interfaceGroup.writeEntry(NMV_WG_TAG_POST_UP, dataMap[NM_WG_KEY_POST_UP]); + + if (dataMap.contains(QLatin1String(NM_WG_KEY_PRE_DOWN))) + interfaceGroup.writeEntry(NMV_WG_TAG_PRE_DOWN, dataMap[NM_WG_KEY_PRE_DOWN]); + + if (dataMap.contains(QLatin1String(NM_WG_KEY_POST_DOWN))) + interfaceGroup.writeEntry(NMV_WG_TAG_POST_DOWN, dataMap[NM_WG_KEY_POST_DOWN]); + + // Do Pupblic key (required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_PUBLIC_KEY))) + peerGroup.writeEntry(NMV_WG_TAG_PUBLIC_KEY, dataMap[NM_WG_KEY_PUBLIC_KEY]); + else + return false; + + // Do Allowed IP list (Required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_ALLOWED_IPS))) + peerGroup.writeEntry(NMV_WG_TAG_ALLOWED_IPS, dataMap[NM_WG_KEY_ALLOWED_IPS]); + else + return false; + + // Do Endpoint (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_ENDPOINT))) + peerGroup.writeEntry(NMV_WG_TAG_ENDPOINT, dataMap[NM_WG_KEY_ENDPOINT]); + + // Do Preshared Key (Not required) + if (dataMap.contains(QLatin1String(NM_WG_KEY_PRESHARED_KEY))) + peerGroup.writeEntry(NMV_WG_TAG_PRESHARED_KEY, dataMap[NM_WG_KEY_PRESHARED_KEY]); + + exportFile.sync(); + return true; +} + +#include "wireguard.moc" diff --git a/vpn/wireguard/wireguard.ui b/vpn/wireguard/wireguard.ui new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguard.ui @@ -0,0 +1,194 @@ + + + WireGuardProp + + + + 0 + 0 + 495 + 454 + + + + WireGuard Settings + + + + + + Interface + + + + QFormLayout::ExpandingFieldsGrow + + + 6 + + + + + Address (IPv4) + + + + + + + <html><head/><body><p>IPv4 intrnrt address assigned to the local interface. IPv4 or IPv6 address (or both) required</p></body></html> + + + + + + + Address (IPv6) + + + + + + + IPv6 intrnrt address assigned to the local interface. IPv4 or IPv6 address (or both) required + + + + + + + Private Key + + + + + + + <html><head/><body><p>A base64 private key generated by wg genkey. Required.</p></body></html> + + + + + + + DNS + + + + + + + <html><head/><body><p>IPv4 or IPv6 address to set and the interface's DNS server. Optional.</p></body></html> + + + + + + + + + + Peer + + + + QFormLayout::ExpandingFieldsGrow + + + 6 + + + + + Public Key + + + + + + + <html><head/><body><p>A base64 public key calculated + by wg pubkey from a private key, and usually transmitted out of band to the author of the configuration file. Required.</p></body></html> + + + + + + + Allowed IPs + + + + + + + <html><head/><body><p>A comma-separated list of IP (v4 or v6) addresses with CIDR masks from which incoming traffic for this peer is allowed and to which outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may be specified for matching all IPv4 addresses, and ::/0 may be specified for matching all IPv6 addresses.</p></body></html> + + + + + + + Endpoint + + + + + + + <html><head/><body><p>An endpoint IP or hostname, followed by a colon, and then a port number. Optional.</p></body></html> + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Advanced... + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + PasswordField + QLineEdit +
passwordfield.h
+
+
+ + +
diff --git a/vpn/wireguard/wireguard_global.h b/vpn/wireguard/wireguard_global.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguard_global.h @@ -0,0 +1,12 @@ +#ifndef WIREGUARD_GLOBAL_H +#define WIREGUARD_GLOBAL_H + +#include + +#if defined(WIREGUARD_LIBRARY) +# define WIREGUARDSHARED_EXPORT Q_DECL_EXPORT +#else +# define WIREGUARDSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // WIREGUARD_GLOBAL_H diff --git a/vpn/wireguard/wireguardadvanced.ui b/vpn/wireguard/wireguardadvanced.ui new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardadvanced.ui @@ -0,0 +1,207 @@ + + + WireGuardAdvancedWidget + + + + 0 + 0 + 398 + 546 + + + + WireGuard Advanced + + + + + + Interface + + + + QFormLayout::ExpandingFieldsGrow + + + 6 + + + + + Listen Port + + + + + + + <html><head/><body><p>A 16-bit port for listening. Optional; if not specified, chosen randomly.</p></body></html> + + + + + + + MTU + + + + + + + <html><head/><body><p>If not specified, the MTU is automatically determined from the endpoint addresses or the system default route, which is usually a sane choice. However, to manually specify an MTU to override this automatic discovery, this value may be specified explicitly.</p></body></html> + + + + + + + Table + + + + + + + <html><head/><body><p>Controls the routing table to which routes are added. There are two special values: `off' disables the creation of routes altogether, and `auto' (the default) adds routes to the default table and enables special handling of default routes</p></body></html> + + + + + + + FwMark + + + + + + + <html><head/><body><p>A 32-bit fwmark for outgoing packets. If set to 0 or &quot;off&quot;, this option is disabled. May be specified in hexadecimal by prepending &quot;0x&quot;. Optional.</p></body></html> + + + + + + + PreUp Script + + + + + + + <html><head/><body><p>Script snippet which will be executed by bash(1) before setting up the interface, most commonly used to configure custom DNS options or firewall rules. The special string `%i' is expanded to INTERFACE.</p><p>This is NOT the same as the NetworkManager PreUp script which is preferred.</p></body></html> + + + + + + + PostUp Script + + + + + + + <html><head/><body><p>Script snippet which will be executed by bash(1) after setting up the interface, most commonly used to configure custom DNS options or firewall rules. The special string `%i' is expanded to INTERFACE.</p><p>This is NOT the same as the NetworkManager PostUp script which is preferred.</p></body></html> + + + + + + + PreDown Script + + + + + + + <html><head/><body><p>Script snippet which will be executed by bash(1) before tearing down the interface, most commonly used to configure custom DNS options or firewall rules. The special string `%i' is expanded to INTERFACE.</p><p>This is NOT the same as the NetworkManager PreDown script which is preferred.</p></body></html> + + + + + + + PostDown Script + + + + + + + <html><head/><body><p>Script snippet which will be executed by bash(1) after tearing down the interface, most commonly used to configure custom DNS options or firewall rules. The special string `%i' is expanded to INTERFACE.</p><p>This is NOT the same as the NetworkManager PostDown script which is preferred.</p></body></html> + + + + + + + + + + Peer + + + + QFormLayout::ExpandingFieldsGrow + + + 6 + + + + + Preshared Key + + + + + + + <html><head/><body><p>A base64 preshared key generated by wg genpsk. Optional. </p><p>This option adds an additional layer of symmetric-key cryptography to be mixed into the already existing public-key cryptography, for post-quantum resistance.</p></body></html>o + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + PasswordField + QLineEdit +
passwordfield.h
+
+
+ + +
diff --git a/vpn/wireguard/wireguardadvancedwidget.h b/vpn/wireguard/wireguardadvancedwidget.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardadvancedwidget.h @@ -0,0 +1,65 @@ +/* + Copyright 2018 Bruce Anderson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef PLASMA_NM_OPENVPN_ADVANCED_WIDGET_H +#define PLASMA_NM_OPENVPN_ADVANCED_WIDGET_H + +#include "passwordfield.h" + +#include +#include + +#include + +namespace Ui +{ +class WireGuardAdvancedWidget; +} + +class QLineEdit; +class WireGuardAdvancedWidget : public QDialog +{ + Q_OBJECT + + enum CertCheckType { + DontVerify = 0, + VerifyWholeSubjectExactly, + VerifyNameExactly, + VerifyNameByPrefix, + VerifySubjectPartially + }; + +public: + explicit WireGuardAdvancedWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent = 0); + ~WireGuardAdvancedWidget() override; + + NetworkManager::VpnSetting::Ptr setting() const; + +private Q_SLOTS: + +private: + void loadConfig(); + void setOrClear(NMStringMap &data, QLatin1String key, QString value) const; + Ui::WireGuardAdvancedWidget *m_ui; + class Private; + Private *const d; +}; + +#endif // PLASMA_NM_OPENVPN_ADVANCED_WIDGET_H diff --git a/vpn/wireguard/wireguardadvancedwidget.cpp b/vpn/wireguard/wireguardadvancedwidget.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardadvancedwidget.cpp @@ -0,0 +1,117 @@ +/* + Copyright 2018 Bruce Anderson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "wireguardadvancedwidget.h" +#include "ui_wireguardadvanced.h" +#include "nm-wireguard-service.h" +#include "settingwidget.h" + +#include +#include +#include + +#include +#include +#include + +class WireGuardAdvancedWidget::Private { +public: + NetworkManager::VpnSetting::Ptr setting; +}; + +WireGuardAdvancedWidget::WireGuardAdvancedWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent) + : QDialog(parent) + , m_ui(new Ui::WireGuardAdvancedWidget) + , d(new Private) +{ + m_ui->setupUi(this); + + setWindowTitle(i18nc("@title: window advanced wireguard properties", "Advanced WireGuard properties")); + + d->setting = setting; + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &WireGuardAdvancedWidget::accept); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &WireGuardAdvancedWidget::reject); + + m_ui->presharedKeyLineEdit->setPasswordModeEnabled(true); + + QIntValidator *listenPortValidator = new QIntValidator(0, 65535, nullptr); + m_ui->listenPortLineEdit->setValidator(listenPortValidator); + + QIntValidator *mTUValidator = new QIntValidator(nullptr); + m_ui->mTULineEdit->setValidator(mTUValidator); + + QIntValidator *fwMarkValidaor = new QIntValidator(0, 4294967296, nullptr); + m_ui->fwMarkLineEdit->setValidator(fwMarkValidaor); + + KAcceleratorManager::manage(this); + + if (d->setting) { + loadConfig(); + } +} + +WireGuardAdvancedWidget::~WireGuardAdvancedWidget() +{ + delete d; +} + +void WireGuardAdvancedWidget::loadConfig() +{ + const NMStringMap dataMap = d->setting->data(); + + m_ui->listenPortLineEdit->setText(dataMap[NM_WG_KEY_LISTEN_PORT]); + m_ui->mTULineEdit->setText(dataMap[NM_WG_KEY_MTU]); + m_ui->tableLineEdit->setText(dataMap[NM_WG_KEY_TABLE]); + m_ui->fwMarkLineEdit->setText(dataMap[NM_WG_KEY_FWMARK]); + m_ui->presharedKeyLineEdit->setText(dataMap[NM_WG_KEY_PRESHARED_KEY]); + m_ui->preUpScriptLineEdit->setText(dataMap[NM_WG_KEY_PRE_UP]); + m_ui->postUpScriptLineEdit->setText(dataMap[NM_WG_KEY_POST_UP]); + m_ui->preDownScriptLineEdit->setText(dataMap[NM_WG_KEY_PRE_DOWN]); + m_ui->postDownScriptLineEdit->setText(dataMap[NM_WG_KEY_POST_DOWN]); + +} + +void WireGuardAdvancedWidget::setOrClear(NMStringMap &data, QLatin1String key, QString value) const +{ + if (0 != value.length()) + data.insert(key, value); + else + data.remove(key); +} + +NetworkManager::VpnSetting::Ptr WireGuardAdvancedWidget::setting() const +{ + NMStringMap data; + + setOrClear(data, QLatin1String(NM_WG_KEY_LISTEN_PORT), m_ui->listenPortLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_MTU), m_ui->mTULineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_TABLE), m_ui->tableLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_FWMARK), m_ui->fwMarkLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_PRESHARED_KEY), m_ui->presharedKeyLineEdit->text()); + setOrClear(data, QLatin1String(NM_WG_KEY_PRE_UP), m_ui->preUpScriptLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_POST_UP), m_ui->postUpScriptLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_PRE_DOWN), m_ui->preDownScriptLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_POST_DOWN), m_ui->postDownScriptLineEdit->displayText()); + + d->setting->setData(data); + + return d->setting; +} diff --git a/vpn/wireguard/wireguardauth.h b/vpn/wireguard/wireguardauth.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardauth.h @@ -0,0 +1,43 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef WIREGUARDAUTH_H +#define WIREGUARDAUTH_H + +#include + +#include "settingwidget.h" + +class WireGuardAuthWidgetPrivate; + +class WireGuardAuthWidget : public SettingWidget +{ + Q_OBJECT + Q_DECLARE_PRIVATE(WireGuardAuthWidget) +public: + explicit WireGuardAuthWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent = 0); + ~WireGuardAuthWidget() override; + virtual QVariantMap setting() const override; + +private: + WireGuardAuthWidgetPrivate *const d_ptr; +}; + +#endif // WIREGUARDAUTH_H diff --git a/vpn/wireguard/wireguardauth.cpp b/vpn/wireguard/wireguardauth.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardauth.cpp @@ -0,0 +1,57 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "wireguardauth.h" +#include "ui_wireguardauth.h" + +#include + +class WireGuardAuthWidgetPrivate +{ +public: + Ui_WireGuardAuth ui; + NetworkManager::VpnSetting::Ptr setting; +}; + +WireGuardAuthWidget::WireGuardAuthWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent) + : SettingWidget(setting, parent) + , d_ptr(new WireGuardAuthWidgetPrivate) +{ + Q_D(WireGuardAuthWidget); + d->ui.setupUi(this); + d->setting = setting; + + KAcceleratorManager::manage(this); +} + +WireGuardAuthWidget::~WireGuardAuthWidget() +{ + delete d_ptr; +} + + + +QVariantMap WireGuardAuthWidget::setting() const +{ + Q_D(const WireGuardAuthWidget); + + QVariantMap result; + return result; +} diff --git a/vpn/wireguard/wireguardauth.ui b/vpn/wireguard/wireguardauth.ui new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardauth.ui @@ -0,0 +1,31 @@ + + + WireGuardAuth + + + + 0 + 0 + 263 + 51 + + + + + 6 + + + + + No WireGuard Secrets currently + + + false + + + + + + + + diff --git a/vpn/wireguard/wireguardstrings.h b/vpn/wireguard/wireguardstrings.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardstrings.h @@ -0,0 +1,46 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef _WIREGUARD_REGEX_STRINGS_INCLUDED +#define _WIREGUARD_REGEX_STRINGS_INCLUDED + +#include + +struct WireGuardRegexStrings +{ + QString *ip4Range; + QString *ip4Port; + QString *ip4Address; + QString *ip4AddressWithCidr; + QString *ip6Test1; + QString *ip6Test2; + QString *ip6Test3; + QString *ip6Test4; + QString *ip6Test5; + QString *ip6Test6; + QString *ip6Test7; + QString *ip6Test8; + QString *ip6Test9; + QString *ip6Cidr; + QString *ip6AddressWithCidr; + QString *ip4Orip6Address; + QString *keySpec; +}; + +#endif diff --git a/vpn/wireguard/wireguardutils.h b/vpn/wireguard/wireguardutils.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardutils.h @@ -0,0 +1,29 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +class WireGuardUtils +{ +public: + static bool is_num_valid(QString candidate, int min=0, int max=0); + static bool is_ip4(QString addr, bool allow_subnet=true, bool allow_port=true); + static bool is_ip6(QString addr, bool allow_subnet=true); + static bool is_key_valid(QString candidate); +}; diff --git a/vpn/wireguard/wireguardutils.cpp b/vpn/wireguard/wireguardutils.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardutils.cpp @@ -0,0 +1,250 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "wireguardutils.h" + +// Checks to see if a string contains only number characters. +// If min and max are given, will check to see if the number is in min <= number <= max +bool WireGuardUtils::is_num_valid(QString candidate, int min, int max) +{ + if (candidate.length() == 0 || + candidate.indexOf(QRegExp("[^0-9]")) > -1 || + (candidate.toInt() > max && max != min) || + (candidate.toInt() < min && max != min)) + { + return false; + } + else + { + return true; + } +} + +// check if the given string looks like an IPv4 address +// that is, four segments of numbers (0-255), separated by dots +// additionally, there may be a port suffix (separated from the address by a colon; 0 - 65535) +// and/or a subnet (separated by the rest by a slash; 0 - 32) +bool WireGuardUtils::is_ip4(QString addr, bool allow_subnet, bool allow_port) +{ + int idx = 0; + QStringList parts; + QStringList lastpart; + + // If we got an empty string, fail + if (0 == addr.length()) + { + return false; + } + + // Split up the string at the dots + parts << addr.split("."); + + // If there weren't 4 parts, fail + if(parts.length() != 4) + { + return false; + } + + // iterate over the first three parts, which cannot be anything else than numbers + for(idx = 0; idx < 3; idx++) + { + // Check that each "chunk" contains at least one character, is all numbers, and is less than <= 255 + if (!is_num_valid(parts[idx], 0 , 255)) + { + return false; + } + } + + // the last part might be just a number less than 255 or + // have a subnet suffix after a slash (e.g. 192.168.1.254/24) + // might have a port suffix after a colon (e.g. 192.168.1.254:8080) + // or both: 192.168.2.1:808/24 + + // First check that subnet and port are allowed + if ((parts[3].contains("/") && false == allow_subnet) || + (parts[3].contains(":") && false == allow_port)) + { + return false; + } + + // Split on both "/" and ":" to see if it contains either a subnet mask + // or a port + lastpart = parts[3].split(QRegExp("[/:]")); + + // test the last octet + if (!is_num_valid(lastpart[0], 0, 255)) + { + return false; + } + + // If there isn't either a netmask or a port, we're done + if (lastpart.length() == 1) + { + return true; + } + + // Split off and test the netmask if there is one + lastpart = parts[3].split("/"); + + if (lastpart.length() == 2 && + (!is_num_valid(lastpart[1], 0, 32))) + { + return false; + } + + // Split off and test the port if there is one + lastpart = lastpart[0].split(":"); + + if (lastpart.length() == 2 && + (!is_num_valid(lastpart[1], 0, 65535))) + { + return false; + } + + return true; +} + +// check if the given string looks like an IPv6 address +// that is, eight segments of hex numbers (00-ff), separated by colons +// additionally, there may be a subnet (separated by the rest by a slash; 0 - 128) +bool WireGuardUtils::is_ip6(QString addr, bool allow_subnet) +{ + QStringList parts; + QStringList lastpart; + QString subnet; + int num_parts; + int part_length[8]; + int num_empty = 0; + bool has_subnet = false; + + // If we got an empty string, fail + if (0 == addr.length()) + { + return false; + } + + // Split up the string at the cololn + parts << addr.split(":"); + num_parts = parts.size(); + + // If there aren't at least 2 parts, and at most 8 fail + if(num_parts < 3 || num_parts > 8) + { + return false; + } + + // Separate out the last (possibly blank) hextet and the netmask if it has one + lastpart = parts[num_parts-1].split("/"); + if (lastpart.size() > 1) + { + if (false == allow_subnet) + { + return false; + } + has_subnet = true; + subnet = lastpart[1]; + if (0 == subnet.length()) + { + return false; + } + parts[num_parts-1] = lastpart[0]; + } + + // Count the number of blank fields + for (int i = 0; i < num_parts; i++) + { + part_length[i] = parts[i].length(); + if (part_length[i] == 0) + { + num_empty++; + } + } + + // If there are more than 3 empty hextets it's an error + if (num_empty > 3) + { + return false; + } + + // If there are 3 empty hextets there must only be the 3 (i.e. ::) + else if (num_empty == 3 && num_parts != 3) + { + return false; + } + + // if there are 2 empty hextets they must either be the first 2 or last 2 + else if (num_empty == 2 && + !(part_length[0] == 0 && part_length[1] == 0) && + !(part_length[num_parts-1] == 0 && part_length[num_parts-2] == 0)) + { + return false; + } + + // If there is just 1 empty it must not be first or last + else if (num_empty == 1 && + (0 == part_length[0] || 0 == part_length[num_parts-1])) + { + return false; + } + + // If there are less than 8 parts there must be at least 1 empty + if (num_parts < 8 && (0 == num_empty || (1 == num_empty && 0 == part_length[num_parts-1]))) + { + return false; + } + + // Now just check that the hextets is valid + for (int i = 0; i < num_parts; i++) + { + if (0 == part_length[i]) + { + continue; + } + else if (part_length[i] > 4) + { + return false; + } + else if (parts[i].indexOf(QRegExp("[^0-9A-Fa-f]")) > -1) + { + return false; + } + } + + // Check the netmask + if (has_subnet && !is_num_valid(subnet, 0, 128)) + { + return false; + } + + return true; +} + +// Check if a string is a valid WireGuard key which should be +// 44 characters long and composed of alphanumeric characters, plus +// sign, and slash with the last one being an equal sign. +bool WireGuardUtils::is_key_valid(QString candidate) +{ + if (44 != candidate.length() || + 43 != candidate.indexOf(QRegExp("[^a-zA-Z0-9+/]")) || + 43 != candidate.indexOf("=")) + { + return false; + } + + return true; +} diff --git a/vpn/wireguard/wireguardwidget.h b/vpn/wireguard/wireguardwidget.h new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardwidget.h @@ -0,0 +1,62 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef WIREGUARDWIDGET_H +#define WIREGUARDWIDGET_H + +#include "settingwidget.h" + +#include + +#include "ui_wireguard.h" + +#include +#include "wireguardstrings.h" + +class QUrl; +class QLineEdit; + +class WireGuardSettingWidget : public SettingWidget +{ + Q_OBJECT +public: + explicit WireGuardSettingWidget(const NetworkManager::VpnSetting::Ptr &setting, + const struct WireGuardRegexStrings &strings, + QWidget *parent = 0); + ~WireGuardSettingWidget() override; + + void loadConfig(const NetworkManager::Setting::Ptr &setting) override; + void loadSecrets(const NetworkManager::Setting::Ptr &setting) override; + + QVariantMap setting() const override; + + virtual bool isValid() const override; + +private Q_SLOTS: + void showAdvanced(); + +private: + class Private; + Private *d; + void setOrClear(NMStringMap &data, QLatin1String key, QString value) const; + +}; + +#endif // WIREGUARDWIDGET_H diff --git a/vpn/wireguard/wireguardwidget.cpp b/vpn/wireguard/wireguardwidget.cpp new file mode 100644 --- /dev/null +++ b/vpn/wireguard/wireguardwidget.cpp @@ -0,0 +1,190 @@ +/* + Copyright 2018 Bruce Anderson + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "debug.h" +#include "wireguardwidget.h" +#include "wireguardadvancedwidget.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "nm-wireguard-service.h" + +class WireGuardSettingWidget::Private +{ +public: + Ui_WireGuardProp ui; + NetworkManager::VpnSetting::Ptr setting; +}; + + +WireGuardSettingWidget::WireGuardSettingWidget(const NetworkManager::VpnSetting::Ptr &setting, + const struct WireGuardRegexStrings &strings, + QWidget *parent) + : SettingWidget(setting, parent) + , d(new Private) +{ + qDBusRegisterMetaType(); + + d->ui.setupUi(this); + d->setting = setting; + + connect(d->ui.addressIPv4LineEdit, &QLineEdit::textChanged, this, &WireGuardSettingWidget::slotWidgetChanged); + connect(d->ui.addressIPv6LineEdit, &QLineEdit::textChanged, this, &WireGuardSettingWidget::slotWidgetChanged); + connect(d->ui.privateKeyLineEdit, &PasswordField::textChanged, this, &WireGuardSettingWidget::slotWidgetChanged); + connect(d->ui.publicKeyLineEdit, &QLineEdit::textChanged, this, &WireGuardSettingWidget::slotWidgetChanged); + connect(d->ui.allowedIPsLineEdit, &QLineEdit::textChanged, this, &WireGuardSettingWidget::slotWidgetChanged); + + d->ui.privateKeyLineEdit->setPasswordModeEnabled(true); + + connect(d->ui.btnAdvanced, &QPushButton::clicked, this, &WireGuardSettingWidget::showAdvanced); + + QRegExp ip4NoCidrRegex (*strings.ip4Address); + QRegExp ip4WithCidrRegex (*strings.ip4AddressWithCidr); + QRegExp ip4WithPortRegex (*strings.ip4Address + ":" + *strings.ip4Port); + + QRegExpValidator *ip4WithCidrValidator = new QRegExpValidator(ip4WithCidrRegex, this); + d->ui.addressIPv4LineEdit->setValidator(ip4WithCidrValidator); + + // Create a validator for the IPv6 address line edit + // Address must be a valid IP address with a CIDR suffix + QRegExp ip6WithCidrRegex (*strings.ip6AddressWithCidr); + QRegExpValidator *ip6WithCidrValidator = new QRegExpValidator(ip6WithCidrRegex, this); + d->ui.addressIPv6LineEdit->setValidator(ip6WithCidrValidator); + + // Create a validator for the key lineEdits. + // Key is 43 Alpha-numeric or '+' or '/' followed by '=' + QRegExp keyRegex (*strings.keySpec); + QRegExpValidator *keyValidator = new QRegExpValidator(keyRegex, this); + d->ui.publicKeyLineEdit->setValidator(keyValidator); + + // Create validator for DNS + QRegExpValidator *dnsValidator = new QRegExpValidator(ip4NoCidrRegex, this); + d->ui.dNSLineEdit->setValidator(dnsValidator); + + // Create validator for Endpoint + QRegExpValidator *endpointValidator = new QRegExpValidator(ip4WithPortRegex, this); + d->ui.endpointLineEdit->setValidator(endpointValidator); + + // Create validator for AllowedIPs + QRegExp allowedIPsRegex ("(" + *strings.ip4Orip6Address + ", *)*" + *strings.ip4Orip6Address); + QRegExpValidator *allowedIPsValidator = new QRegExpValidator(allowedIPsRegex, this); + d->ui.allowedIPsLineEdit->setValidator(allowedIPsValidator); + + // Connect for setting check + watchChangedSetting(); + + KAcceleratorManager::manage(this); + + if (setting && !setting->isNull()) { + loadConfig(d->setting); + } +} + +WireGuardSettingWidget::~WireGuardSettingWidget() +{ + delete d; +} + +void WireGuardSettingWidget::loadConfig(const NetworkManager::Setting::Ptr &setting) +{ + Q_UNUSED(setting) + // General settings + const NMStringMap dataMap = d->setting->data(); + + d->ui.addressIPv4LineEdit->setText(dataMap[NM_WG_KEY_ADDR_IP4]); + d->ui.addressIPv6LineEdit->setText(dataMap[NM_WG_KEY_ADDR_IP6]); + d->ui.privateKeyLineEdit->setText(dataMap[NM_WG_KEY_PRIVATE_KEY]); + d->ui.dNSLineEdit->setText(dataMap[NM_WG_KEY_DNS]); + d->ui.publicKeyLineEdit->setText(dataMap[NM_WG_KEY_PUBLIC_KEY]); + d->ui.allowedIPsLineEdit->setText(dataMap[NM_WG_KEY_ALLOWED_IPS]); + d->ui.endpointLineEdit->setText(dataMap[NM_WG_KEY_ENDPOINT]); +} + +void WireGuardSettingWidget::loadSecrets(const NetworkManager::Setting::Ptr &setting) +{ + // Currently WireGuard does not have any secrets +} + +QVariantMap WireGuardSettingWidget::setting() const +{ + NMStringMap data = d->setting->data(); + NetworkManager::VpnSetting setting; + setting.setServiceType(QLatin1String(NM_DBUS_SERVICE_WIREGUARD)); + + // required settings + + setOrClear(data, QLatin1String(NM_WG_KEY_ADDR_IP4), d->ui.addressIPv4LineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_ADDR_IP6), d->ui.addressIPv6LineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_PRIVATE_KEY), d->ui.privateKeyLineEdit->text()); + setOrClear(data, QLatin1String(NM_WG_KEY_PUBLIC_KEY), d->ui.publicKeyLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_ALLOWED_IPS), d->ui.allowedIPsLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_DNS), d->ui.dNSLineEdit->displayText()); + setOrClear(data, QLatin1String(NM_WG_KEY_ENDPOINT), d->ui.endpointLineEdit->displayText()); + + setting.setData(data); + + return setting.toMap(); +} + +void WireGuardSettingWidget::setOrClear(NMStringMap &data, QLatin1String key, QString value) const +{ + if (0 != value.length()) + data.insert(key, value); + else + data.remove(key); +} + +void WireGuardSettingWidget::showAdvanced() +{ + QPointer adv = new WireGuardAdvancedWidget(d->setting, this); + + connect(adv.data(), &WireGuardAdvancedWidget::accepted, + [adv, this] () { + NetworkManager::VpnSetting::Ptr advData = adv->setting(); + if (!advData.isNull()) { + d->setting->setData(advData->data()); + } + }); + connect(adv.data(), &WireGuardAdvancedWidget::finished, + [adv] () { + if (adv) { + adv->deleteLater(); + } + }); + adv->setModal(true); + adv->show(); +} + +bool WireGuardSettingWidget::isValid() const +{ + return ( (d->ui.addressIPv4LineEdit->displayText().length() != 0 + || d->ui.addressIPv6LineEdit->displayText().length() != 0) + && (d->ui.privateKeyLineEdit->text().length() != 0) + && (d->ui.publicKeyLineEdit->displayText().length() != 0) + && (d->ui.allowedIPsLineEdit->displayText().length() != 0)); +} +