File indexing completed on 2024-04-28 08:49:01
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"