File indexing completed on 2024-04-21 04:56:44

0001 /**
0002  * SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "device.h"
0008 
0009 #include <QSet>
0010 #include <QSslCertificate>
0011 #include <QSslKey>
0012 #include <QVector>
0013 
0014 #include <KConfigGroup>
0015 #include <KLocalizedString>
0016 #include <KSharedConfig>
0017 
0018 #include "backends/devicelink.h"
0019 #include "backends/lan/landevicelink.h"
0020 #include "backends/linkprovider.h"
0021 #include "core_debug.h"
0022 #include "daemon.h"
0023 #include "dbushelper.h"
0024 #include "kdeconnectconfig.h"
0025 #include "kdeconnectplugin.h"
0026 #include "networkpacket.h"
0027 #include "pluginloader.h"
0028 
0029 class Device::DevicePrivate
0030 {
0031 public:
0032     DevicePrivate(const DeviceInfo &deviceInfo)
0033         : m_deviceInfo(deviceInfo)
0034     {
0035     }
0036 
0037     ~DevicePrivate()
0038     {
0039         qDeleteAll(m_deviceLinks);
0040         m_deviceLinks.clear();
0041     }
0042 
0043     DeviceInfo m_deviceInfo;
0044 
0045     QVector<DeviceLink *> m_deviceLinks;
0046     QHash<QString, KdeConnectPlugin *> m_plugins;
0047 
0048     QMultiMap<QString, KdeConnectPlugin *> m_pluginsByIncomingCapability;
0049     QSet<QString> m_supportedPlugins;
0050     PairingHandler *m_pairingHandler;
0051 };
0052 
0053 static void warn(const QString &info)
0054 {
0055     qWarning() << "Device pairing error" << info;
0056 }
0057 
0058 Device::Device(QObject *parent, const QString &id)
0059     : QObject(parent)
0060 {
0061     DeviceInfo info = KdeConnectConfig::instance().getTrustedDevice(id);
0062     d = new Device::DevicePrivate(info);
0063 
0064     d->m_pairingHandler = new PairingHandler(this, PairState::Paired);
0065     const auto supported = PluginLoader::instance()->getPluginList();
0066     d->m_supportedPlugins = QSet(supported.begin(), supported.end()); // Assume every plugin is supported until we get the capabilities
0067 
0068     // Register in bus
0069     QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
0070 
0071     connect(d->m_pairingHandler, &PairingHandler::incomingPairRequest, this, &Device::pairingHandler_incomingPairRequest);
0072     connect(d->m_pairingHandler, &PairingHandler::pairingFailed, this, &Device::pairingHandler_pairingFailed);
0073     connect(d->m_pairingHandler, &PairingHandler::pairingSuccessful, this, &Device::pairingHandler_pairingSuccessful);
0074     connect(d->m_pairingHandler, &PairingHandler::unpaired, this, &Device::pairingHandler_unpaired);
0075 }
0076 
0077 Device::Device(QObject *parent, DeviceLink *dl)
0078     : QObject(parent)
0079 {
0080     d = new Device::DevicePrivate(dl->deviceInfo());
0081 
0082     d->m_pairingHandler = new PairingHandler(this, PairState::NotPaired);
0083     const auto supported = PluginLoader::instance()->getPluginList();
0084     d->m_supportedPlugins = QSet(supported.begin(), supported.end()); // Assume every plugin is supported until we get the capabilities
0085 
0086     addLink(dl);
0087 
0088     // Register in bus
0089     QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
0090 
0091     connect(this, &Device::pairingFailed, this, &warn);
0092 
0093     connect(this, &Device::reachableChanged, this, &Device::statusIconNameChanged);
0094     connect(this, &Device::pairStateChanged, this, &Device::statusIconNameChanged);
0095 
0096     connect(d->m_pairingHandler, &PairingHandler::incomingPairRequest, this, &Device::pairingHandler_incomingPairRequest);
0097     connect(d->m_pairingHandler, &PairingHandler::pairingFailed, this, &Device::pairingHandler_pairingFailed);
0098     connect(d->m_pairingHandler, &PairingHandler::pairingSuccessful, this, &Device::pairingHandler_pairingSuccessful);
0099     connect(d->m_pairingHandler, &PairingHandler::unpaired, this, &Device::pairingHandler_unpaired);
0100 
0101     if (protocolVersion() != NetworkPacket::s_protocolVersion) {
0102         qCWarning(KDECONNECT_CORE) << name() << " uses a different protocol version" << protocolVersion() << "expected" << NetworkPacket::s_protocolVersion;
0103     }
0104 }
0105 
0106 Device::~Device()
0107 {
0108     delete d;
0109 }
0110 
0111 QString Device::id() const
0112 {
0113     return d->m_deviceInfo.id;
0114 }
0115 
0116 QString Device::name() const
0117 {
0118     return d->m_deviceInfo.name;
0119 }
0120 
0121 DeviceType Device::type() const
0122 {
0123     return d->m_deviceInfo.type;
0124 }
0125 
0126 bool Device::isReachable() const
0127 {
0128     return !d->m_deviceLinks.isEmpty();
0129 }
0130 
0131 int Device::protocolVersion()
0132 {
0133     return d->m_deviceInfo.protocolVersion;
0134 }
0135 
0136 QStringList Device::supportedPlugins() const
0137 {
0138     return QList(d->m_supportedPlugins.cbegin(), d->m_supportedPlugins.cend());
0139 }
0140 
0141 bool Device::hasPlugin(const QString &name) const
0142 {
0143     return d->m_plugins.contains(name);
0144 }
0145 
0146 QStringList Device::loadedPlugins() const
0147 {
0148     return d->m_plugins.keys();
0149 }
0150 
0151 void Device::reloadPlugins()
0152 {
0153     qCDebug(KDECONNECT_CORE) << name() << "- reload plugins";
0154 
0155     QHash<QString, KdeConnectPlugin *> newPluginMap, oldPluginMap = d->m_plugins;
0156     QMultiMap<QString, KdeConnectPlugin *> newPluginsByIncomingCapability;
0157 
0158     if (isPaired() && isReachable()) { // Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices
0159 
0160         PluginLoader *loader = PluginLoader::instance();
0161 
0162         for (const QString &pluginName : qAsConst(d->m_supportedPlugins)) {
0163             const KPluginMetaData service = loader->getPluginInfo(pluginName);
0164 
0165             const bool pluginEnabled = isPluginEnabled(pluginName);
0166             const QStringList incomingCapabilities = service.rawData().value(QStringLiteral("X-KdeConnect-SupportedPacketType")).toVariant().toStringList();
0167 
0168             if (pluginEnabled) {
0169                 KdeConnectPlugin *plugin = d->m_plugins.take(pluginName);
0170 
0171                 if (!plugin) {
0172                     plugin = loader->instantiatePluginForDevice(pluginName, this);
0173                 }
0174                 Q_ASSERT(plugin);
0175 
0176                 for (const QString &interface : incomingCapabilities) {
0177                     newPluginsByIncomingCapability.insert(interface, plugin);
0178                 }
0179 
0180                 newPluginMap[pluginName] = plugin;
0181 
0182                 plugin->connected();
0183             }
0184         }
0185     }
0186 
0187     const bool differentPlugins = oldPluginMap != newPluginMap;
0188 
0189     // Erase all left plugins in the original map (meaning that we don't want
0190     // them anymore, otherwise they would have been moved to the newPluginMap)
0191     qDeleteAll(d->m_plugins);
0192     d->m_plugins = newPluginMap;
0193     d->m_pluginsByIncomingCapability = newPluginsByIncomingCapability;
0194 
0195     // Recreate dbus paths for all plugins (new and existing)
0196     QDBusConnection bus = QDBusConnection::sessionBus();
0197     for (KdeConnectPlugin *plugin : qAsConst(d->m_plugins)) {
0198         const QString dbusPath = plugin->dbusPath();
0199         if (!dbusPath.isEmpty()) {
0200             bus.registerObject(dbusPath,
0201                                plugin,
0202                                QDBusConnection::ExportAllProperties | QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportScriptableSignals
0203                                    | QDBusConnection::ExportScriptableSlots);
0204         }
0205     }
0206     if (differentPlugins) {
0207         Q_EMIT pluginsChanged();
0208     }
0209 }
0210 
0211 QString Device::pluginsConfigFile() const
0212 {
0213     return KdeConnectConfig::instance().deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config"));
0214 }
0215 
0216 void Device::requestPairing()
0217 {
0218     qCDebug(KDECONNECT_CORE) << "Request pairing";
0219     d->m_pairingHandler->requestPairing();
0220     Q_EMIT pairStateChanged(pairStateAsInt());
0221 }
0222 
0223 void Device::unpair()
0224 {
0225     qCDebug(KDECONNECT_CORE) << "Request unpairing";
0226     d->m_pairingHandler->unpair();
0227 }
0228 
0229 void Device::acceptPairing()
0230 {
0231     qCDebug(KDECONNECT_CORE) << "Accept pairing";
0232     d->m_pairingHandler->acceptPairing();
0233 }
0234 
0235 void Device::cancelPairing()
0236 {
0237     qCDebug(KDECONNECT_CORE) << "Cancel pairing";
0238     d->m_pairingHandler->cancelPairing();
0239 }
0240 
0241 void Device::pairingHandler_incomingPairRequest()
0242 {
0243     Q_ASSERT(d->m_pairingHandler->pairState() == PairState::RequestedByPeer);
0244     Q_EMIT pairStateChanged(pairStateAsInt());
0245 }
0246 
0247 void Device::pairingHandler_pairingSuccessful()
0248 {
0249     Q_ASSERT(d->m_pairingHandler->pairState() == PairState::Paired);
0250     KdeConnectConfig::instance().addTrustedDevice(d->m_deviceInfo);
0251     reloadPlugins(); // Will load/unload plugins
0252     Q_EMIT pairStateChanged(pairStateAsInt());
0253 }
0254 
0255 void Device::pairingHandler_pairingFailed(const QString &errorMessage)
0256 {
0257     Q_ASSERT(d->m_pairingHandler->pairState() == PairState::NotPaired);
0258     Q_EMIT pairingFailed(errorMessage);
0259     Q_EMIT pairStateChanged(pairStateAsInt());
0260 }
0261 
0262 void Device::pairingHandler_unpaired()
0263 {
0264     Q_ASSERT(d->m_pairingHandler->pairState() == PairState::NotPaired);
0265     qCDebug(KDECONNECT_CORE) << "Unpaired";
0266     KdeConnectConfig::instance().removeTrustedDevice(id());
0267     reloadPlugins(); // Will load/unload plugins
0268     Q_EMIT pairStateChanged(pairStateAsInt());
0269 }
0270 
0271 void Device::addLink(DeviceLink *link)
0272 {
0273     if (d->m_deviceLinks.contains(link)) {
0274         return;
0275     }
0276 
0277     d->m_deviceLinks.append(link);
0278 
0279     std::sort(d->m_deviceLinks.begin(), d->m_deviceLinks.end(), [](DeviceLink *a, DeviceLink *b) {
0280         return a->priority() > b->priority();
0281     });
0282 
0283     connect(link, &QObject::destroyed, this, &Device::linkDestroyed);
0284     connect(link, &DeviceLink::receivedPacket, this, &Device::privateReceivedPacket);
0285 
0286     bool hasChanges = updateDeviceInfo(link->deviceInfo());
0287 
0288     if (d->m_deviceLinks.size() == 1) {
0289         Q_EMIT reachableChanged(true);
0290         hasChanges = true;
0291     }
0292 
0293     if (hasChanges) {
0294         reloadPlugins();
0295     }
0296 }
0297 
0298 bool Device::updateDeviceInfo(const DeviceInfo &newDeviceInfo)
0299 {
0300     bool hasChanges = false;
0301     if (d->m_deviceInfo.name != newDeviceInfo.name || d->m_deviceInfo.type != newDeviceInfo.type) {
0302         hasChanges = true;
0303         d->m_deviceInfo.name = newDeviceInfo.name;
0304         d->m_deviceInfo.type = newDeviceInfo.type;
0305         Q_EMIT typeChanged(d->m_deviceInfo.type.toString());
0306         Q_EMIT nameChanged(d->m_deviceInfo.name);
0307         if (isPaired()) {
0308             KdeConnectConfig::instance().updateTrustedDeviceInfo(d->m_deviceInfo);
0309         }
0310     }
0311 
0312     if (d->m_deviceInfo.outgoingCapabilities != newDeviceInfo.outgoingCapabilities
0313         || d->m_deviceInfo.incomingCapabilities != newDeviceInfo.incomingCapabilities) {
0314         if (!newDeviceInfo.incomingCapabilities.isEmpty() && !newDeviceInfo.outgoingCapabilities.isEmpty()) {
0315             hasChanges = true;
0316             d->m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(newDeviceInfo.incomingCapabilities, newDeviceInfo.outgoingCapabilities);
0317             qDebug() << "new capabilities for " << name();
0318         }
0319     }
0320 
0321     return hasChanges;
0322 }
0323 
0324 void Device::linkDestroyed(QObject *o)
0325 {
0326     removeLink(static_cast<DeviceLink *>(o));
0327 }
0328 
0329 void Device::removeLink(DeviceLink *link)
0330 {
0331     d->m_deviceLinks.removeAll(link);
0332 
0333     // qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining";
0334 
0335     if (d->m_deviceLinks.isEmpty()) {
0336         reloadPlugins();
0337         Q_EMIT reachableChanged(false);
0338     }
0339 }
0340 
0341 bool Device::sendPacket(NetworkPacket &np)
0342 {
0343     Q_ASSERT(isPaired() || np.type() == PACKET_TYPE_PAIR);
0344 
0345     // Maybe we could block here any packet that is not an identity or a pairing packet to prevent sending non encrypted data
0346     for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
0347         if (dl->sendPacket(np))
0348             return true;
0349     }
0350 
0351     return false;
0352 }
0353 
0354 void Device::privateReceivedPacket(const NetworkPacket &np)
0355 {
0356     if (np.type() == PACKET_TYPE_PAIR) {
0357         d->m_pairingHandler->packetReceived(np);
0358     } else if (isPaired()) {
0359         const QList<KdeConnectPlugin *> plugins = d->m_pluginsByIncomingCapability.values(np.type());
0360         if (plugins.isEmpty()) {
0361             qWarning() << "discarding unsupported packet" << np.type() << "for" << name();
0362         }
0363         for (KdeConnectPlugin *plugin : plugins) {
0364             plugin->receivePacket(np);
0365         }
0366     } else {
0367         qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring packet" << np.type();
0368         unpair();
0369     }
0370 }
0371 
0372 PairState Device::pairState() const
0373 {
0374     return d->m_pairingHandler->pairState();
0375 }
0376 
0377 int Device::pairStateAsInt() const
0378 {
0379     return (int)pairState();
0380 }
0381 
0382 bool Device::isPaired() const
0383 {
0384     return d->m_pairingHandler->pairState() == PairState::Paired;
0385 }
0386 
0387 bool Device::isPairRequested() const
0388 {
0389     return d->m_pairingHandler->pairState() == PairState::Requested;
0390 }
0391 
0392 bool Device::isPairRequestedByPeer() const
0393 {
0394     return d->m_pairingHandler->pairState() == PairState::RequestedByPeer;
0395 }
0396 
0397 QHostAddress Device::getLocalIpAddress() const
0398 {
0399     for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
0400         LanDeviceLink *ldl = dynamic_cast<LanDeviceLink *>(dl);
0401         if (ldl) {
0402             return ldl->hostAddress();
0403         }
0404     }
0405     return QHostAddress::Null;
0406 }
0407 
0408 QString Device::iconName() const
0409 {
0410     return d->m_deviceInfo.type.icon();
0411 }
0412 
0413 QString Device::statusIconName() const
0414 {
0415     return d->m_deviceInfo.type.iconForStatus(isReachable(), isPaired());
0416 }
0417 
0418 KdeConnectPlugin *Device::plugin(const QString &pluginName) const
0419 {
0420     return d->m_plugins[pluginName];
0421 }
0422 
0423 void Device::setPluginEnabled(const QString &pluginName, bool enabled)
0424 {
0425     if (!PluginLoader::instance()->doesPluginExist(pluginName)) {
0426         qWarning() << "Tried to enable a plugin that doesn't exist" << pluginName;
0427         return;
0428     }
0429 
0430     KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group(QStringLiteral("Plugins"));
0431 
0432     const QString enabledKey = pluginName + QStringLiteral("Enabled");
0433     pluginStates.writeEntry(enabledKey, enabled);
0434     reloadPlugins();
0435 }
0436 
0437 bool Device::isPluginEnabled(const QString &pluginName) const
0438 {
0439     const QString enabledKey = pluginName + QStringLiteral("Enabled");
0440     KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group(QStringLiteral("Plugins"));
0441 
0442     return (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false)
0443                                             : PluginLoader::instance()->getPluginInfo(pluginName).isEnabledByDefault());
0444 }
0445 
0446 QString Device::encryptionInfo() const
0447 {
0448     QString result;
0449     const QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha256;
0450 
0451     QString localChecksum = QString::fromLatin1(KdeConnectConfig::instance().certificate().digest(digestAlgorithm).toHex());
0452     for (int i = 2; i < localChecksum.size(); i += 3) {
0453         localChecksum.insert(i, QLatin1Char(':')); // Improve readability
0454     }
0455     result += i18n("SHA256 fingerprint of your device certificate is: %1\n", localChecksum);
0456 
0457     QString remoteChecksum = QString::fromLatin1(certificate().digest(digestAlgorithm).toHex());
0458     for (int i = 2; i < remoteChecksum.size(); i += 3) {
0459         remoteChecksum.insert(i, QLatin1Char(':')); // Improve readability
0460     }
0461     result += i18n("SHA256 fingerprint of remote device certificate is: %1\n", remoteChecksum);
0462 
0463     return result;
0464 }
0465 
0466 QSslCertificate Device::certificate() const
0467 {
0468     return d->m_deviceInfo.certificate;
0469 }
0470 
0471 QByteArray Device::verificationKey() const
0472 {
0473     auto a = KdeConnectConfig::instance().certificate().publicKey().toDer();
0474     auto b = certificate().publicKey().toDer();
0475     if (a < b) {
0476         std::swap(a, b);
0477     }
0478 
0479     QCryptographicHash hash(QCryptographicHash::Sha256);
0480     hash.addData(a);
0481     hash.addData(b);
0482     return hash.result().toHex();
0483 }
0484 
0485 QString Device::pluginIconName(const QString &pluginName)
0486 {
0487     if (hasPlugin(pluginName)) {
0488         return d->m_plugins[pluginName]->iconName();
0489     }
0490     return QString();
0491 }
0492 
0493 #include "moc_device.cpp"