File indexing completed on 2024-12-08 08:02:39

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