File indexing completed on 2023-12-03 12:27:01

0001 /*
0002     SPDX-FileCopyrightText: 2013-2014 Jan Grulich <jgrulich@redhat.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "handler.h"
0008 #include "configuration.h"
0009 #include "connectioneditordialog.h"
0010 #include "plasma_nm_libs.h"
0011 #include "uiutils.h"
0012 
0013 #include <NetworkManagerQt/AccessPoint>
0014 #include <NetworkManagerQt/ActiveConnection>
0015 #include <NetworkManagerQt/GsmSetting>
0016 #include <NetworkManagerQt/Ipv4Setting>
0017 #include <NetworkManagerQt/Manager>
0018 #include <NetworkManagerQt/Setting>
0019 #include <NetworkManagerQt/WiredDevice>
0020 #include <NetworkManagerQt/WiredSetting>
0021 #include <NetworkManagerQt/WirelessDevice>
0022 #include <NetworkManagerQt/WirelessSetting>
0023 
0024 #include <libnm/nm-vpn-plugin-info.h>
0025 
0026 #include <ModemManagerQt/Manager>
0027 #include <ModemManagerQt/ModemDevice>
0028 
0029 #include <QDBusError>
0030 #include <QDBusMetaType>
0031 #include <QDBusPendingReply>
0032 #include <QDBusReply>
0033 #include <QFile>
0034 #include <QIcon>
0035 #include <QStringBuilder>
0036 
0037 #include <KIO/OpenUrlJob>
0038 #include <KLocalizedString>
0039 #include <KNotification>
0040 #include <KOSRelease>
0041 #include <KPluginMetaData>
0042 #include <KProcess>
0043 #include <KUser>
0044 #include <KWallet>
0045 #include <KWindowSystem>
0046 
0047 #include <nm-client.h>
0048 #include <QCoroCore>
0049 #include <QCoroDBus>
0050 
0051 #define AGENT_SERVICE "org.kde.kded6"
0052 #define AGENT_PATH "/modules/networkmanagement"
0053 #define AGENT_IFACE "org.kde.plasmanetworkmanagement"
0054 
0055 // 10 seconds
0056 #define NM_REQUESTSCAN_LIMIT_RATE 10000
0057 
0058 Handler::Handler(QObject *parent)
0059     : QObject(parent)
0060     , m_tmpWirelessEnabled(NetworkManager::isWirelessEnabled())
0061     , m_tmpWwanEnabled(NetworkManager::isWwanEnabled())
0062 {
0063     QDBusConnection::sessionBus().connect(QStringLiteral(AGENT_SERVICE),
0064                                           QStringLiteral(AGENT_PATH),
0065                                           QStringLiteral(AGENT_IFACE),
0066                                           QStringLiteral("secretsError"),
0067                                           this,
0068                                           SLOT(secretAgentError(QString, QString)));
0069 
0070     if (!Configuration::self().hotspotConnectionPath().isEmpty()) {
0071         NetworkManager::ActiveConnection::Ptr hotspot = NetworkManager::findActiveConnection(Configuration::self().hotspotConnectionPath());
0072         if (!hotspot) {
0073             Configuration::self().setHotspotConnectionPath(QString());
0074         }
0075     }
0076 
0077     m_hotspotSupported = checkHotspotSupported();
0078 
0079     if (NetworkManager::checkVersion(1, 16, 0)) {
0080         connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionTypeChanged, this, &Handler::primaryConnectionTypeChanged);
0081     }
0082 }
0083 
0084 Handler::~Handler() = default;
0085 
0086 void Handler::activateConnection(const QString &connection, const QString &device, const QString &specificObject)
0087 {
0088     activateConnectionInternal(connection, device, specificObject);
0089 }
0090 
0091 QCoro::Task<void> Handler::activateConnectionInternal(const QString &connection, const QString &device, const QString &specificObject)
0092 {
0093     NetworkManager::Connection::Ptr con = NetworkManager::findConnection(connection);
0094 
0095     if (!con) {
0096         qCWarning(PLASMA_NM_LIBS_LOG) << "Not possible to activate this connection";
0097         co_return;
0098     }
0099 
0100     if (con->settings()->connectionType() == NetworkManager::ConnectionSettings::Vpn) {
0101         NetworkManager::VpnSetting::Ptr vpnSetting = con->settings()->setting(NetworkManager::Setting::Vpn).staticCast<NetworkManager::VpnSetting>();
0102         if (vpnSetting) {
0103             qCDebug(PLASMA_NM_LIBS_LOG) << "Checking VPN" << con->name() << "type:" << vpnSetting->serviceType();
0104 
0105             // Check missing plasma-nm VPN plugin
0106 
0107             const auto filter = [vpnSetting](const KPluginMetaData &md) -> bool {
0108                 return md.value(QStringLiteral("X-NetworkManager-Services")) == vpnSetting->serviceType();
0109             };
0110 
0111             const QVector<KPluginMetaData> plasmaNmPlugins = KPluginMetaData::findPlugins(QStringLiteral("plasma/network/vpn"), filter);
0112 
0113             const QString pluginBaseName = vpnSetting->serviceType().remove(QLatin1String("org.freedesktop.NetworkManager."));
0114 
0115             if (plasmaNmPlugins.empty()) {
0116                 qCWarning(PLASMA_NM_LIBS_LOG) << "VPN" << vpnSetting->serviceType() << "not found, skipping";
0117                 auto notification = new KNotification(QStringLiteral("MissingVpnPlugin"), KNotification::Persistent, this);
0118                 notification->setComponentName(QStringLiteral("networkmanagement"));
0119                 notification->setTitle(con->name());
0120                 notification->setText(i18n("Plasma is missing support for '%1' VPN connections.", pluginBaseName));
0121                 notification->setIconName(QStringLiteral("dialog-error"));
0122                 notification->setActions({i18n("Report Bug")});
0123                 connect(notification, &KNotification::action1Activated, this, [notification] {
0124                     auto *job = new KIO::OpenUrlJob(QUrl(KOSRelease().bugReportUrl()));
0125                     job->setStartupId(notification->xdgActivationToken().toUtf8());
0126                     job->start();
0127                 });
0128 
0129                 notification->sendEvent();
0130 
0131                 co_return;
0132             }
0133 
0134             // Check missing NetworkManager VPN plugin
0135             GSList *networkManagerPlugins = nullptr;
0136             networkManagerPlugins = nm_vpn_plugin_info_list_load();
0137 
0138             NMVpnPluginInfo *plugin_info = nm_vpn_plugin_info_list_find_by_service(networkManagerPlugins, vpnSetting->serviceType().toStdString().c_str());
0139 
0140             if (!plugin_info) {
0141                 qCWarning(PLASMA_NM_LIBS_LOG) << "VPN" << vpnSetting->serviceType() << "not found, skipping";
0142                 auto notification = new KNotification(QStringLiteral("MissingVpnPlugin"), KNotification::Persistent, this);
0143                 notification->setComponentName(QStringLiteral("networkmanagement"));
0144                 notification->setTitle(con->name());
0145                 notification->setText(i18n("NetworkManager is missing support for '%1' VPN connections.", pluginBaseName));
0146                 notification->setIconName(QStringLiteral("dialog-error"));
0147                 notification->setActions({i18n("Install")});
0148 
0149                 connect(notification, &KNotification::action1Activated, this, [notification, pluginBaseName] {
0150                     auto *job = new KIO::OpenUrlJob(QUrl(QStringLiteral("appstream:network-manager-") + pluginBaseName));
0151                     job->setStartupId(notification->xdgActivationToken().toUtf8());
0152                     job->start();
0153                 });
0154 
0155                 notification->sendEvent();
0156                 co_return;
0157             }
0158         }
0159     }
0160 
0161     if (con->settings()->connectionType() == NetworkManager::ConnectionSettings::Gsm) {
0162         NetworkManager::ModemDevice::Ptr nmModemDevice = NetworkManager::findNetworkInterface(device).objectCast<NetworkManager::ModemDevice>();
0163         if (nmModemDevice) {
0164             ModemManager::ModemDevice::Ptr mmModemDevice = ModemManager::findModemDevice(nmModemDevice->udi());
0165             if (mmModemDevice) {
0166                 ModemManager::Modem::Ptr modem = mmModemDevice->interface(ModemManager::ModemDevice::ModemInterface).objectCast<ModemManager::Modem>();
0167                 NetworkManager::GsmSetting::Ptr gsmSetting = con->settings()->setting(NetworkManager::Setting::Gsm).staticCast<NetworkManager::GsmSetting>();
0168                 if (gsmSetting && gsmSetting->pinFlags() == NetworkManager::Setting::NotSaved && modem && modem->unlockRequired() > MM_MODEM_LOCK_NONE) {
0169                     QDBusInterface managerIface(QStringLiteral("org.kde.plasmanetworkmanagement"),
0170                                                 QStringLiteral("/org/kde/plasmanetworkmanagement"),
0171                                                 QStringLiteral("org.kde.plasmanetworkmanagement"),
0172                                                 QDBusConnection::sessionBus(),
0173                                                 this);
0174                     managerIface.call(QStringLiteral("unlockModem"), mmModemDevice->uni());
0175                     connect(modem.data(), &ModemManager::Modem::unlockRequiredChanged, this, &Handler::unlockRequiredChanged);
0176                     m_tmpConnectionPath = connection;
0177                     m_tmpDevicePath = device;
0178                     m_tmpSpecificPath = specificObject;
0179                     co_return;
0180                 }
0181             }
0182         }
0183     }
0184 
0185     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::activateConnection(connection, device, specificObject);
0186 
0187     if (!reply.isValid()) {
0188         QString error = reply.error().message();
0189         KNotification *notification = new KNotification(QStringLiteral("FailedToActivateConnection"), KNotification::CloseOnTimeout, this);
0190         notification->setTitle(i18n("Failed to activate %1", con->name()));
0191         notification->setComponentName(QStringLiteral("networkmanagement"));
0192         notification->setText(error);
0193         notification->setIconName(QStringLiteral("dialog-warning"));
0194         notification->sendEvent();
0195     }
0196 }
0197 
0198 void Handler::requestWifiCode(const QString &connectionPath, const QString &ssid, int _securityType)
0199 {
0200     if (!m_requestWifiCodeWatcher.isNull()) {
0201         delete m_requestWifiCodeWatcher;
0202     }
0203 
0204     auto securityType = static_cast<NetworkManager::WirelessSecurityType>(_securityType);
0205 
0206     QString ret = QStringLiteral("WIFI:S:") + ssid + QLatin1Char(';');
0207     if (securityType != NetworkManager::NoneSecurity) {
0208         switch (securityType) {
0209         case NetworkManager::NoneSecurity:
0210             break;
0211         case NetworkManager::StaticWep:
0212             ret += QStringLiteral("T:WEP;");
0213             break;
0214         case NetworkManager::WpaPsk:
0215         case NetworkManager::Wpa2Psk:
0216             ret += QStringLiteral("T:WPA;");
0217             break;
0218         case NetworkManager::SAE:
0219             ret += QStringLiteral("T:SAE;");
0220             break;
0221         default:
0222         case NetworkManager::DynamicWep:
0223         case NetworkManager::WpaEap:
0224         case NetworkManager::Wpa2Eap:
0225         case NetworkManager::Wpa3SuiteB192:
0226         case NetworkManager::Leap:
0227             Q_EMIT wifiCodeReceived(QString(), ssid);
0228             return;
0229         }
0230     }
0231 
0232     NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(connectionPath);
0233     if (!connection) {
0234         Q_EMIT wifiCodeReceived(QString(), ssid);
0235         return;
0236     }
0237 
0238     const auto key = QStringLiteral("802-11-wireless-security");
0239     auto reply = connection->secrets(key);
0240     m_requestWifiCodeWatcher = new QDBusPendingCallWatcher(reply, this);
0241     m_requestWifiCodeWatcher->setProperty("key", key);
0242     m_requestWifiCodeWatcher->setProperty("ret", ret);
0243     m_requestWifiCodeWatcher->setProperty("securityType", static_cast<int>(securityType));
0244     m_requestWifiCodeWatcher->setProperty("ssid", ssid);
0245     connect(m_requestWifiCodeWatcher, &QDBusPendingCallWatcher::finished, this, &Handler::slotRequestWifiCode);
0246 }
0247 
0248 void Handler::addAndActivateConnection(const QString &device, const QString &specificParameter, const QString &password)
0249 {
0250     addAndActivateConnectionInternal(device, specificParameter, password);
0251 }
0252 
0253 QCoro::Task<void> Handler::addAndActivateConnectionInternal(const QString &device, const QString &specificObject, const QString &password)
0254 {
0255     NetworkManager::AccessPoint::Ptr ap;
0256     NetworkManager::WirelessDevice::Ptr wifiDev;
0257     for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) {
0258         if (dev->type() == NetworkManager::Device::Wifi) {
0259             wifiDev = dev.objectCast<NetworkManager::WirelessDevice>();
0260             ap = wifiDev->findAccessPoint(specificObject);
0261             if (ap) {
0262                 break;
0263             }
0264         }
0265     }
0266 
0267     if (!ap) {
0268         co_return;
0269     }
0270 
0271     NetworkManager::ConnectionSettings::Ptr settings =
0272         NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Wireless));
0273     settings->setId(ap->ssid());
0274     settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
0275     settings->setAutoconnect(true);
0276 
0277     UiUtils::setConnectionDefaultPermissions(settings);
0278 
0279     NetworkManager::WirelessSetting::Ptr wifiSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast<NetworkManager::WirelessSetting>();
0280     wifiSetting->setInitialized(true);
0281     wifiSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast<NetworkManager::WirelessSetting>();
0282     wifiSetting->setSsid(ap->ssid().toUtf8());
0283     if (ap->mode() == NetworkManager::AccessPoint::Adhoc) {
0284         wifiSetting->setMode(NetworkManager::WirelessSetting::Adhoc);
0285     }
0286     NetworkManager::WirelessSecuritySetting::Ptr wifiSecurity =
0287         settings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast<NetworkManager::WirelessSecuritySetting>();
0288 
0289     NetworkManager::WirelessSecurityType securityType = NetworkManager::findBestWirelessSecurity(wifiDev->wirelessCapabilities(),
0290                                                                                                  true,
0291                                                                                                  (ap->mode() == NetworkManager::AccessPoint::Adhoc),
0292                                                                                                  ap->capabilities(),
0293                                                                                                  ap->wpaFlags(),
0294                                                                                                  ap->rsnFlags());
0295 
0296     if (securityType != NetworkManager::NoneSecurity) {
0297         wifiSecurity->setInitialized(true);
0298         wifiSetting->setSecurity(QStringLiteral("802-11-wireless-security"));
0299     }
0300 
0301     if (securityType == NetworkManager::Leap //
0302         || securityType == NetworkManager::DynamicWep //
0303         || securityType == NetworkManager::Wpa3SuiteB192 //
0304         || securityType == NetworkManager::Wpa2Eap //
0305         || securityType == NetworkManager::WpaEap) {
0306         if (securityType == NetworkManager::DynamicWep || securityType == NetworkManager::Leap) {
0307             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Ieee8021x);
0308             if (securityType == NetworkManager::Leap) {
0309                 wifiSecurity->setAuthAlg(NetworkManager::WirelessSecuritySetting::Leap);
0310             }
0311         } else if (securityType == NetworkManager::Wpa3SuiteB192) {
0312             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaEapSuiteB192);
0313         } else {
0314             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaEap);
0315         }
0316         m_tmpConnectionUuid = settings->uuid();
0317         m_tmpDevicePath = device;
0318         m_tmpSpecificPath = specificObject;
0319 
0320         QPointer<ConnectionEditorDialog> editor = new ConnectionEditorDialog(settings);
0321         editor->setAttribute(Qt::WA_DeleteOnClose);
0322         editor->show();
0323         KWindowSystem::setState(editor->winId(), NET::KeepAbove);
0324         connect(editor.data(), &ConnectionEditorDialog::accepted, [editor, device, specificObject, this]() { //
0325             addAndActivateConnectionDBus(editor->setting(), device, specificObject);
0326         });
0327         editor->setModal(true);
0328         editor->show();
0329     } else {
0330         if (securityType == NetworkManager::StaticWep) {
0331             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Wep);
0332             wifiSecurity->setWepKey0(password);
0333         } else {
0334             if (ap->mode() == NetworkManager::AccessPoint::Adhoc) {
0335                 wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaNone);
0336             } else if (securityType == NetworkManager::SAE) {
0337                 wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::SAE);
0338             } else {
0339                 wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk);
0340             }
0341             wifiSecurity->setPsk(password);
0342         }
0343         addAndActivateConnectionDBus(settings->toMap(), device, specificObject);
0344     }
0345 
0346     settings.clear();
0347 }
0348 
0349 QCoro::Task<void> Handler::addConnection(const NMVariantMapMap &map)
0350 {
0351     const QString connectionId = map.value(QStringLiteral("connection")).value(QStringLiteral("id")).toString();
0352 
0353     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::addConnection(map);
0354 
0355     if (!reply.isValid()) {
0356         KNotification *notification = new KNotification(QStringLiteral("FailedToAddConnection"), KNotification::CloseOnTimeout, this);
0357         notification->setTitle(i18n("Failed to add connection %1", connectionId));
0358         notification->setComponentName(QStringLiteral("networkmanagement"));
0359         notification->setText(reply.error().message());
0360         notification->setIconName(QStringLiteral("dialog-warning"));
0361         notification->sendEvent();
0362     } else {
0363         KNotification *notification = new KNotification(QStringLiteral("ConnectionAdded"), KNotification::CloseOnTimeout, this);
0364         notification->setText(i18n("Connection %1 has been added", connectionId));
0365         notification->setComponentName(QStringLiteral("networkmanagement"));
0366         notification->setTitle(connectionId);
0367         notification->setIconName(QStringLiteral("dialog-information"));
0368         notification->sendEvent();
0369     }
0370 }
0371 
0372 struct AddConnectionData {
0373     QString id;
0374     Handler *handler;
0375 };
0376 
0377 void add_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data)
0378 {
0379     AddConnectionData *data = static_cast<AddConnectionData *>(user_data);
0380 
0381     GError *error = nullptr;
0382     NMRemoteConnection *connection = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error);
0383 
0384     if (error) {
0385         KNotification *notification = new KNotification(QStringLiteral("FailedToAddConnection"), KNotification::CloseOnTimeout, data->handler);
0386         notification->setTitle(i18n("Failed to add connection %1", data->id));
0387         notification->setComponentName(QStringLiteral("networkmanagement"));
0388         notification->setText(QString::fromUtf8(error->message));
0389         notification->setIconName(QStringLiteral("dialog-warning"));
0390         notification->sendEvent();
0391 
0392         g_error_free(error);
0393     } else {
0394         KNotification *notification = new KNotification(QStringLiteral("ConnectionAdded"), KNotification::CloseOnTimeout, data->handler);
0395         notification->setText(i18n("Connection %1 has been added", data->id));
0396         notification->setComponentName(QStringLiteral("networkmanagement"));
0397         notification->setTitle(data->id);
0398         notification->setIconName(QStringLiteral("dialog-information"));
0399         notification->sendEvent();
0400 
0401         g_object_unref(connection);
0402     }
0403 
0404     delete data;
0405 }
0406 
0407 void Handler::addConnection(NMConnection *connection)
0408 {
0409     NMClient *client = nm_client_new(nullptr, nullptr);
0410 
0411     AddConnectionData *userData = new AddConnectionData{QString::fromUtf8(nm_connection_get_id(connection)), this};
0412 
0413     nm_client_add_connection2(client,
0414                               nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL),
0415                               NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK,
0416                               nullptr,
0417                               true,
0418                               nullptr,
0419                               add_connection_cb,
0420                               userData);
0421 }
0422 
0423 QCoro::Task<void> Handler::addAndActivateConnectionDBus(const NMVariantMapMap &map, const QString &device, const QString &specificObject)
0424 {
0425     const QString name = map.value(QStringLiteral("connection")).value(QStringLiteral("id")).toString();
0426     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::addAndActivateConnection(map, device, specificObject);
0427 
0428     if (!reply.isValid()) {
0429         KNotification *notification = new KNotification(QStringLiteral("FailedToAddConnection"), KNotification::CloseOnTimeout, this);
0430         notification->setTitle(i18n("Failed to add %1", name));
0431         notification->setComponentName(QStringLiteral("networkmanagement"));
0432         notification->setText(reply.error().message());
0433         notification->setIconName(QStringLiteral("dialog-warning"));
0434         notification->sendEvent();
0435     }
0436 }
0437 
0438 void Handler::deactivateConnection(const QString &connection, const QString &device)
0439 {
0440     deactivateConnectionInternal(connection, device);
0441 }
0442 
0443 QCoro::Task<void> Handler::deactivateConnectionInternal(const QString &_connection, const QString &device)
0444 {
0445     const QString connection = _connection;
0446     NetworkManager::Connection::Ptr con = NetworkManager::findConnection(connection);
0447 
0448     if (!con) {
0449         qCWarning(PLASMA_NM_LIBS_LOG) << "Not possible to deactivate this connection";
0450         co_return;
0451     }
0452 
0453     QDBusReply<void> reply;
0454     for (const NetworkManager::ActiveConnection::Ptr &active : NetworkManager::activeConnections()) {
0455         if (active->uuid() == con->uuid() && ((!active->devices().isEmpty() && active->devices().first() == device) || active->vpn())) {
0456             if (active->vpn()) {
0457                 reply = co_await NetworkManager::deactivateConnection(active->path());
0458             } else {
0459                 NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(active->devices().first());
0460                 if (device) {
0461                     reply = co_await device->disconnectInterface();
0462                 }
0463             }
0464         }
0465     }
0466 
0467     if (!reply.isValid()) {
0468         KNotification *notification = new KNotification(QStringLiteral("FailedToDeactivateConnection"), KNotification::CloseOnTimeout, this);
0469         notification->setTitle(i18n("Failed to deactivate %1", connection));
0470         notification->setComponentName(QStringLiteral("networkmanagement"));
0471         notification->setText(reply.error().message());
0472         notification->setIconName(QStringLiteral("dialog-warning"));
0473         notification->sendEvent();
0474     }
0475 }
0476 
0477 void Handler::disconnectAll()
0478 {
0479     for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
0480         device->disconnectInterface();
0481     }
0482 }
0483 
0484 void Handler::enableAirplaneMode(bool enable)
0485 {
0486     if (enable) {
0487         m_tmpWirelessEnabled = NetworkManager::isWirelessEnabled();
0488         m_tmpWwanEnabled = NetworkManager::isWwanEnabled();
0489         enableBluetooth(false);
0490         enableWireless(false);
0491         enableWwan(false);
0492     } else {
0493         enableBluetooth(true);
0494         if (m_tmpWirelessEnabled) {
0495             enableWireless(true);
0496         }
0497         if (m_tmpWwanEnabled) {
0498             enableWwan(true);
0499         }
0500     }
0501 }
0502 
0503 void setBluetoothEnabled(const QString &path, bool enabled)
0504 {
0505     QDBusMessage message =
0506         QDBusMessage::createMethodCall(QStringLiteral("org.bluez"), path, QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Set"));
0507     QList<QVariant> arguments;
0508     arguments << QLatin1String("org.bluez.Adapter1");
0509     arguments << QLatin1String("Powered");
0510     arguments << QVariant::fromValue(QDBusVariant(QVariant(enabled)));
0511     message.setArguments(arguments);
0512     QDBusConnection::systemBus().asyncCall(message);
0513 }
0514 
0515 QCoro::Task<void> Handler::enableBluetooth(bool enable)
0516 {
0517     qDBusRegisterMetaType<QMap<QDBusObjectPath, NMVariantMapMap>>();
0518 
0519     const QDBusMessage getObjects = QDBusMessage::createMethodCall("org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
0520 
0521     QDBusReply<QMap<QDBusObjectPath, NMVariantMapMap>> reply = co_await QDBusConnection::systemBus().asyncCall(getObjects);
0522 
0523     if (!reply.isValid()) {
0524         qCWarning(PLASMA_NM_LIBS_LOG) << reply.error().message();
0525         co_return;
0526     }
0527 
0528     for (const QDBusObjectPath &path : reply.value().keys()) {
0529         const QString objPath = path.path();
0530         qCDebug(PLASMA_NM_LIBS_LOG) << "inspecting path" << objPath;
0531         const QStringList interfaces = reply.value().value(path).keys();
0532         qCDebug(PLASMA_NM_LIBS_LOG) << "interfaces:" << interfaces;
0533 
0534         if (!interfaces.contains(QStringLiteral("org.bluez.Adapter1"))) {
0535             continue;
0536         }
0537 
0538         // We need to check previous state first
0539         if (!enable) {
0540             QDBusMessage getPowered = QDBusMessage::createMethodCall("org.bluez", objPath, "org.freedesktop.DBus.Properties", "Get");
0541             const QList<QVariant> arguments{QLatin1String("org.bluez.Adapter1"), QLatin1String("Powered")};
0542             getPowered.setArguments(arguments);
0543 
0544             QDBusReply<QVariant> reply = co_await QDBusConnection::systemBus().asyncCall(getPowered);
0545 
0546             if (!reply.isValid()) {
0547                 qCWarning(PLASMA_NM_LIBS_LOG) << reply.error().message();
0548                 co_return;
0549             }
0550 
0551             m_bluetoothAdapters.insert(objPath, reply.value().toBool());
0552             setBluetoothEnabled(objPath, false);
0553         } else if (m_bluetoothAdapters.value(objPath)) {
0554             setBluetoothEnabled(objPath, true);
0555         }
0556     }
0557 }
0558 
0559 void Handler::enableNetworking(bool enable)
0560 {
0561     NetworkManager::setNetworkingEnabled(enable);
0562 }
0563 
0564 void Handler::enableWireless(bool enable)
0565 {
0566     NetworkManager::setWirelessEnabled(enable);
0567 }
0568 
0569 void Handler::enableWwan(bool enable)
0570 {
0571     NetworkManager::setWwanEnabled(enable);
0572 }
0573 
0574 void Handler::removeConnection(const QString &connection)
0575 {
0576     removeConnectionInternal(connection);
0577 }
0578 
0579 QCoro::Task<void> Handler::removeConnectionInternal(const QString &connection)
0580 {
0581     NetworkManager::Connection::Ptr con = NetworkManager::findConnection(connection);
0582 
0583     if (!con || con->uuid().isEmpty()) {
0584         qCWarning(PLASMA_NM_LIBS_LOG) << "Not possible to remove connection " << connection;
0585         co_return;
0586     }
0587 
0588     // Remove slave connections
0589     for (const NetworkManager::Connection::Ptr &connection : NetworkManager::listConnections()) {
0590         NetworkManager::ConnectionSettings::Ptr settings = connection->settings();
0591         if (settings->master() == con->uuid()) {
0592             connection->remove();
0593         }
0594     }
0595 
0596     QDBusReply<void> reply = co_await con->remove();
0597 
0598     if (!reply.isValid()) {
0599         KNotification *notification = new KNotification(QStringLiteral("FailedToRemoveConnection"), KNotification::CloseOnTimeout, this);
0600         notification->setTitle(i18n("Failed to remove %1", con->name()));
0601         notification->setComponentName(QStringLiteral("networkmanagement"));
0602         notification->setText(reply.error().message());
0603         notification->setIconName(QStringLiteral("dialog-warning"));
0604         notification->sendEvent();
0605     } else {
0606         KNotification *notification = new KNotification(QStringLiteral("ConnectionRemoved"), KNotification::CloseOnTimeout, this);
0607         notification->setText(i18n("Connection %1 has been removed", con->name()));
0608         notification->setComponentName(QStringLiteral("networkmanagement"));
0609         notification->setTitle(con->name());
0610         notification->setIconName(QStringLiteral("dialog-information"));
0611         notification->sendEvent();
0612     }
0613 }
0614 
0615 QCoro::Task<void> Handler::updateConnection(NetworkManager::Connection::Ptr connection, const NMVariantMapMap &map)
0616 {
0617     QDBusReply<void> reply = co_await connection->update(map);
0618 
0619     if (!reply.isValid()) {
0620         KNotification *notification = new KNotification(QStringLiteral("FailedToUpdateConnection"), KNotification::CloseOnTimeout, this);
0621         notification->setTitle(i18n("Failed to update connection %1", connection->name()));
0622         notification->setComponentName(QStringLiteral("networkmanagement"));
0623         notification->setText(reply.error().message());
0624         notification->setIconName(QStringLiteral("dialog-warning"));
0625         notification->sendEvent();
0626     } else {
0627         KNotification *notification = new KNotification(QStringLiteral("ConnectionUpdated"), KNotification::CloseOnTimeout, this);
0628         notification->setText(i18n("Connection %1 has been updated", connection->name()));
0629         notification->setComponentName(QStringLiteral("networkmanagement"));
0630         notification->setTitle(connection->name());
0631         notification->setIconName(QStringLiteral("dialog-information"));
0632         notification->sendEvent();
0633     }
0634 }
0635 
0636 void Handler::requestScan(const QString &interface)
0637 {
0638     requestScanInternal(interface);
0639 }
0640 
0641 QCoro::Task<void> Handler::requestScanInternal(const QString &interface)
0642 {
0643     for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
0644         if (device->type() == NetworkManager::Device::Wifi) {
0645             NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast<NetworkManager::WirelessDevice>();
0646 
0647             if (wifiDevice && wifiDevice->state() != NetworkManager::WirelessDevice::Unavailable) {
0648                 if (!interface.isEmpty() && interface != wifiDevice->interfaceName()) {
0649                     continue;
0650                 }
0651 
0652                 if (!checkRequestScanRateLimit(wifiDevice)) {
0653                     QDateTime now = QDateTime::currentDateTime();
0654                     // for NM < 1.12, lastScan is not available
0655                     QDateTime lastScan = wifiDevice->lastScan();
0656                     QDateTime lastRequestScan = wifiDevice->lastRequestScan();
0657                     // Compute the next time we can run a scan
0658                     int timeout = NM_REQUESTSCAN_LIMIT_RATE;
0659                     if (lastScan.isValid() && lastScan.msecsTo(now) < NM_REQUESTSCAN_LIMIT_RATE) {
0660                         timeout = NM_REQUESTSCAN_LIMIT_RATE - lastScan.msecsTo(now);
0661                     } else if (lastRequestScan.isValid() && lastRequestScan.msecsTo(now) < NM_REQUESTSCAN_LIMIT_RATE) {
0662                         timeout = NM_REQUESTSCAN_LIMIT_RATE - lastRequestScan.msecsTo(now);
0663                     }
0664                     qCDebug(PLASMA_NM_LIBS_LOG) << "Rescheduling a request scan for" << wifiDevice->interfaceName() << "in" << timeout;
0665                     scheduleRequestScan(wifiDevice->interfaceName(), timeout);
0666 
0667                     if (!interface.isEmpty()) {
0668                         co_return;
0669                     }
0670                     continue;
0671                 } else if (m_wirelessScanRetryTimer.contains(interface)) {
0672                     m_wirelessScanRetryTimer.value(interface)->stop();
0673                     delete m_wirelessScanRetryTimer.take(interface);
0674                 }
0675 
0676                 qCDebug(PLASMA_NM_LIBS_LOG) << "Requesting wifi scan on device" << wifiDevice->interfaceName();
0677 
0678                 incrementScansCount();
0679                 QDBusReply<void> reply = co_await wifiDevice->requestScan();
0680 
0681                 if (!reply.isValid()) {
0682                     const QString interface = wifiDevice->interfaceName();
0683                     qCWarning(PLASMA_NM_LIBS_LOG) << "Wireless scan on" << interface << "failed:" << reply.error().message();
0684                     scanRequestFailed(interface);
0685                 } else {
0686                     qCDebug(PLASMA_NM_LIBS_LOG) << "Wireless scan on" << wifiDevice->interfaceName() << "succeeded";
0687                 }
0688                 decrementScansCount();
0689             }
0690         }
0691     }
0692 }
0693 
0694 void Handler::incrementScansCount()
0695 {
0696     m_ongoingScansCount += 1;
0697     if (m_ongoingScansCount == 1) {
0698         Q_EMIT scanningChanged();
0699     }
0700 }
0701 
0702 void Handler::decrementScansCount()
0703 {
0704     if (m_ongoingScansCount == 0) {
0705         qCDebug(PLASMA_NM_LIBS_LOG) << "Extra decrementScansCount() called";
0706         return;
0707     }
0708     m_ongoingScansCount -= 1;
0709     if (m_ongoingScansCount == 0) {
0710         Q_EMIT scanningChanged();
0711     }
0712 }
0713 
0714 void Handler::createHotspot()
0715 {
0716     createHotspotInternal();
0717 }
0718 
0719 QCoro::Task<void> Handler::createHotspotInternal()
0720 {
0721     bool foundInactive = false;
0722     bool useApMode = false;
0723     NetworkManager::WirelessDevice::Ptr wifiDev;
0724 
0725     NetworkManager::ConnectionSettings::Ptr connectionSettings;
0726     connectionSettings = NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Wireless));
0727 
0728     NetworkManager::WirelessSetting::Ptr wifiSetting =
0729         connectionSettings->setting(NetworkManager::Setting::Wireless).dynamicCast<NetworkManager::WirelessSetting>();
0730     wifiSetting->setMode(NetworkManager::WirelessSetting::Adhoc);
0731     wifiSetting->setSsid(Configuration::self().hotspotName().toUtf8());
0732 
0733     for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
0734         if (device->type() == NetworkManager::Device::Wifi) {
0735             wifiDev = device.objectCast<NetworkManager::WirelessDevice>();
0736             if (wifiDev) {
0737                 if (!wifiDev->isActive()) {
0738                     foundInactive = true;
0739                 } else {
0740                     // Prefer previous device if it was inactive
0741                     if (foundInactive) {
0742                         break;
0743                     }
0744                 }
0745 
0746                 if (wifiDev->wirelessCapabilities().testFlag(NetworkManager::WirelessDevice::ApCap)) {
0747                     useApMode = true;
0748                 }
0749 
0750                 // We prefer inactive wireless card with AP capabilities
0751                 if (foundInactive && useApMode) {
0752                     break;
0753                 }
0754             }
0755         }
0756     }
0757 
0758     if (!wifiDev) {
0759         qCWarning(PLASMA_NM_LIBS_LOG) << "Failed to create hotspot: missing wireless device";
0760         co_return;
0761     }
0762 
0763     wifiSetting->setInitialized(true);
0764     wifiSetting->setMode(useApMode ? NetworkManager::WirelessSetting::Ap : NetworkManager::WirelessSetting::Adhoc);
0765 
0766     if (!Configuration::self().hotspotPassword().isEmpty()) {
0767         NetworkManager::WirelessSecuritySetting::Ptr wifiSecurity =
0768             connectionSettings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast<NetworkManager::WirelessSecuritySetting>();
0769         wifiSecurity->setInitialized(true);
0770 
0771         if (useApMode) {
0772             // Use WPA2
0773             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk);
0774             wifiSecurity->setPsk(Configuration::self().hotspotPassword());
0775             wifiSecurity->setPskFlags(NetworkManager::Setting::AgentOwned);
0776         } else {
0777             // Use WEP
0778             wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Wep);
0779             wifiSecurity->setWepKeyType(NetworkManager::WirelessSecuritySetting::Passphrase);
0780             wifiSecurity->setWepTxKeyindex(0);
0781             wifiSecurity->setWepKey0(Configuration::self().hotspotPassword());
0782             wifiSecurity->setWepKeyFlags(NetworkManager::Setting::AgentOwned);
0783             wifiSecurity->setAuthAlg(NetworkManager::WirelessSecuritySetting::Open);
0784         }
0785     }
0786 
0787     NetworkManager::Ipv4Setting::Ptr ipv4Setting = connectionSettings->setting(NetworkManager::Setting::Ipv4).dynamicCast<NetworkManager::Ipv4Setting>();
0788     ipv4Setting->setMethod(NetworkManager::Ipv4Setting::Shared);
0789     ipv4Setting->setInitialized(true);
0790 
0791     connectionSettings->setId(Configuration::self().hotspotName());
0792     connectionSettings->setAutoconnect(false);
0793     connectionSettings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
0794 
0795     const QVariantMap options = {{QLatin1String("persist"), QLatin1String("volatile")}};
0796 
0797     QDBusPendingReply<QDBusObjectPath, QDBusObjectPath, QVariantMap> reply =
0798         co_await NetworkManager::addAndActivateConnection2(connectionSettings->toMap(), wifiDev->uni(), QString(), options);
0799 
0800     if (!reply.isValid()) {
0801         KNotification *notification = new KNotification(QStringLiteral("FailedToCreateHotspot"), KNotification::CloseOnTimeout, this);
0802         notification->setTitle(i18n("Failed to create hotspot %1", Configuration::self().hotspotName()));
0803         notification->setComponentName(QStringLiteral("networkmanagement"));
0804         notification->setText(reply.error().message());
0805         notification->setIconName(QStringLiteral("dialog-warning"));
0806         notification->sendEvent();
0807     } else {
0808         const QString activeConnectionPath = reply.argumentAt(1).value<QDBusObjectPath>().path();
0809 
0810         if (activeConnectionPath.isEmpty()) {
0811             co_return;
0812         }
0813 
0814         Configuration::self().setHotspotConnectionPath(activeConnectionPath);
0815 
0816         NetworkManager::ActiveConnection::Ptr hotspot = NetworkManager::findActiveConnection(activeConnectionPath);
0817 
0818         if (!hotspot) {
0819             co_return;
0820         }
0821 
0822         connect(hotspot.data(), &NetworkManager::ActiveConnection::stateChanged, [this](NetworkManager::ActiveConnection::State state) {
0823             if (state > NetworkManager::ActiveConnection::Activated) {
0824                 Configuration::self().setHotspotConnectionPath(QString());
0825                 Q_EMIT hotspotDisabled();
0826             }
0827         });
0828 
0829         Q_EMIT hotspotCreated();
0830     }
0831 }
0832 
0833 void Handler::stopHotspot()
0834 {
0835     const QString activeConnectionPath = Configuration::self().hotspotConnectionPath();
0836 
0837     if (activeConnectionPath.isEmpty()) {
0838         return;
0839     }
0840 
0841     NetworkManager::ActiveConnection::Ptr hotspot = NetworkManager::findActiveConnection(activeConnectionPath);
0842 
0843     if (!hotspot) {
0844         return;
0845     }
0846 
0847     NetworkManager::deactivateConnection(activeConnectionPath);
0848     Configuration::self().setHotspotConnectionPath(QString());
0849 
0850     Q_EMIT hotspotDisabled();
0851 }
0852 
0853 bool Handler::checkRequestScanRateLimit(const NetworkManager::WirelessDevice::Ptr &wifiDevice)
0854 {
0855     QDateTime now = QDateTime::currentDateTime();
0856     QDateTime lastScan = wifiDevice->lastScan();
0857     QDateTime lastRequestScan = wifiDevice->lastRequestScan();
0858 
0859     // if the last scan finished within the last 10 seconds
0860     bool ret = lastScan.isValid() && lastScan.msecsTo(now) < NM_REQUESTSCAN_LIMIT_RATE;
0861     // or if the last Request was sent within the last 10 seconds
0862     ret |= lastRequestScan.isValid() && lastRequestScan.msecsTo(now) < NM_REQUESTSCAN_LIMIT_RATE;
0863     // skip the request scan
0864     if (ret) {
0865         qCDebug(PLASMA_NM_LIBS_LOG) << "Last scan finished" << lastScan.msecsTo(now) << "ms ago and last request scan was sent" //
0866                                     << lastRequestScan.msecsTo(now) << "ms ago, Skipping scanning interface:" << wifiDevice->interfaceName();
0867         return false;
0868     }
0869     return true;
0870 }
0871 
0872 bool Handler::checkHotspotSupported()
0873 {
0874     if (NetworkManager::checkVersion(1, 16, 0)) {
0875         bool unusedWifiFound = false;
0876         bool wifiFound = false;
0877 
0878         for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) {
0879             if (device->type() == NetworkManager::Device::Wifi) {
0880                 wifiFound = true;
0881 
0882                 NetworkManager::WirelessDevice::Ptr wifiDev = device.objectCast<NetworkManager::WirelessDevice>();
0883                 if (wifiDev && !wifiDev->isActive()) {
0884                     unusedWifiFound = true;
0885                 }
0886             }
0887         }
0888 
0889         if (!wifiFound) {
0890             return false;
0891         }
0892 
0893         if (unusedWifiFound) {
0894             return true;
0895         }
0896 
0897         // Check if the primary connection which is used for internet connectivity is not using WiFi
0898         if (NetworkManager::primaryConnectionType() != NetworkManager::ConnectionSettings::Wireless) {
0899             return true;
0900         }
0901     }
0902 
0903     return false;
0904 }
0905 
0906 void Handler::scheduleRequestScan(const QString &interface, int timeout)
0907 {
0908     QTimer *timer;
0909     if (!m_wirelessScanRetryTimer.contains(interface)) {
0910         // create a timer for the interface
0911         timer = new QTimer();
0912         timer->setSingleShot(true);
0913         m_wirelessScanRetryTimer.insert(interface, timer);
0914         auto retryAction = [this, interface]() {
0915             requestScan(interface);
0916         };
0917         connect(timer, &QTimer::timeout, this, retryAction);
0918     } else {
0919         // set the new value for an existing timer
0920         timer = m_wirelessScanRetryTimer.value(interface);
0921         if (timer->isActive()) {
0922             timer->stop();
0923         }
0924     }
0925 
0926     // +1 ms is added to avoid having the scan being rejetted by nm
0927     // because it is run at the exact last millisecond of the requestScan threshold
0928     timer->setInterval(timeout + 1);
0929     timer->start();
0930 }
0931 
0932 void Handler::scanRequestFailed(const QString &interface)
0933 {
0934     scheduleRequestScan(interface, 2000);
0935 }
0936 
0937 void Handler::secretAgentError(const QString &connectionPath, const QString &message)
0938 {
0939     // If the password was wrong, forget it
0940     removeConnection(connectionPath);
0941     Q_EMIT connectionActivationFailed(connectionPath, message);
0942 }
0943 
0944 void Handler::primaryConnectionTypeChanged(NetworkManager::ConnectionSettings::ConnectionType type)
0945 {
0946     Q_UNUSED(type)
0947     m_hotspotSupported = checkHotspotSupported();
0948     Q_EMIT hotspotSupportedChanged(m_hotspotSupported);
0949 }
0950 
0951 void Handler::unlockRequiredChanged(MMModemLock modemLock)
0952 {
0953     if (modemLock == MM_MODEM_LOCK_NONE) {
0954         activateConnection(m_tmpConnectionPath, m_tmpDevicePath, m_tmpSpecificPath);
0955     }
0956 }
0957 
0958 void Handler::slotRequestWifiCode(QDBusPendingCallWatcher *watcher)
0959 {
0960     watcher->deleteLater();
0961 
0962     QString ret = watcher->property("ret").toString();
0963     const QString ssid = watcher->property("ssid").toString();
0964     QDBusPendingReply<NMVariantMapMap> reply = *watcher;
0965     if (!reply.isValid() || reply.isError()) {
0966         Q_EMIT wifiCodeReceived(ret % QLatin1Char(';'), ssid);
0967         return;
0968     }
0969 
0970     const auto secret = reply.argumentAt<0>()[watcher->property("key").toString()];
0971     QString pass;
0972     switch (static_cast<NetworkManager::WirelessSecurityType>(watcher->property("securityType").toInt())) {
0973     case NetworkManager::NoneSecurity:
0974         break;
0975     case NetworkManager::WpaPsk:
0976     case NetworkManager::Wpa2Psk:
0977     case NetworkManager::SAE:
0978         pass = secret[QStringLiteral("psk")].toString();
0979         break;
0980     default:
0981         Q_EMIT wifiCodeReceived(QString(), ssid);
0982         return;
0983     }
0984     if (!pass.isEmpty()) {
0985         ret += QStringLiteral("P:") % pass % QLatin1Char(';');
0986     }
0987 
0988     Q_EMIT wifiCodeReceived(ret % QLatin1Char(';'), ssid);
0989 }
0990 
0991 #include "moc_handler.cpp"