diff --git a/CMakeLists.txt b/CMakeLists.txt index ec37cc2b..9901381e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,128 +1,128 @@ cmake_minimum_required(VERSION 3.0) project(kdeconnect) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(ECMSetupVersion) include(ECMInstallIcons) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) include(KDEConnectMacros.cmake) ecm_setup_version(1.3.3 VARIABLE_PREFIX KDECONNECT VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/kdeconnect-version.h ) if (SAILFISHOS) find_package(PkgConfig) set(KF5_MIN_VERSION "5.31.0") set(QT_MIN_VERSION "5.6.0") set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config) set(KF5_OPTIONAL_COMPONENTS) set(QCA_MIN_VERSION 2.0.0) pkg_search_module(SFOS REQUIRED sailfishapp) pkg_check_modules(QCA2 qca2-qt5>=${QCA_MIN_VERSION} REQUIRED) add_definitions(-DSAILFISHOS) include_directories(${QCA2_INCLUDEDIR}) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) else() set(KF5_MIN_VERSION "5.48.0") set(QT_MIN_VERSION "5.10.0") - set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Kirigami2) + set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Kirigami2 DNSSD) set(KF5_OPTIONAL_COMPONENTS DocTools) if(UNIX) set(KF5_OPTIONAL_COMPONENTS ${KF5_OPTIONAL_COMPONENTS} Runner) endif() set(QCA_MIN_VERSION "2.1.0") find_package(Qca-qt5 ${QCA_MIN_VERSION} REQUIRED) if(NOT WIN32 AND NOT APPLE) find_package(KF5PulseAudioQt REQUIRED) endif() add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS -DQT_NO_CAST_FROM_ASCII) endif() find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Quick Network) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ${KF5_REQUIRED_COMPONENTS}) if (KF5_OPTIONAL_COMPONENTS) find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS ${KF5_OPTIONAL_COMPONENTS}) endif() if (NOT ZSH_AUTOCOMPLETE_DIR) set(ZSH_AUTOCOMPLETE_DIR "${CMAKE_INSTALL_PREFIX}/share/zsh/site-functions") endif() find_package(Qt5Multimedia) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "QtQuick plugins to build user interfaces based on KDE UX guidelines" PURPOSE "Required for KDE Connect's QML-based GUI applications" URL "https://www.kde.org/products/kirigami/" TYPE RUNTIME ) option(PRIVATE_DBUS_ENABLED "Use private dbus session for kdeconnect" OFF) if(PRIVATE_DBUS_ENABLED OR APPLE) add_compile_definitions(USE_PRIVATE_DBUS) endif() add_subdirectory(core) if(NOT SAILFISHOS) add_subdirectory(kcm) add_subdirectory(kcmplugin) add_subdirectory(daemon) endif() if(NOT WIN32 AND NOT SAILFISHOS) add_subdirectory(kio) add_subdirectory(plasmoid) endif() add_subdirectory(icon) add_subdirectory(interfaces) add_subdirectory(data) add_subdirectory(plugins) add_subdirectory(cli) add_subdirectory(declarativeplugin) if(KF5Runner_FOUND) add_subdirectory(runners) endif() if (NOT SAILFISHOS) add_subdirectory(app) add_subdirectory(indicator) add_subdirectory(urlhandler) add_subdirectory(nautilus-extension) add_subdirectory(fileitemactionplugin) else() add_subdirectory(sfos) endif() find_package(KF5Kirigami2) find_package(KF5People REQUIRED) find_package(KF5PeopleVCard) set_package_properties(KF5PeopleVCard PROPERTIES PURPOSE "Read vcards from the file system" URL "https://phabricator.kde.org/source/kpeoplevcard/" TYPE RUNTIME ) add_subdirectory(smsapp) if(KF5DocTools_FOUND) add_subdirectory(doc) endif() if(BUILD_TESTING AND NOT SAILFISHOS) add_subdirectory(tests) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index db216793..72d2b964 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,82 +1,83 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") set(KDECONNECT_PRIVATE_DBUS_ADDR unix:tmpdir=/tmp) if(WIN32) set(KDECONNECT_PRIVATE_DBUS_ADDR tcp:host=localhost,port=0) endif() set(KDECONNECT_PRIVATE_DBUS_NAME DBusKDEConnectOnly) configure_file(dbushelper.h.in ${CMAKE_CURRENT_BINARY_DIR}/dbushelper.h) add_subdirectory(backends/lan) add_subdirectory(backends/loopback) option(BLUETOOTH_ENABLED "Bluetooth support for kdeconnect" OFF) if(BLUETOOTH_ENABLED) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Bluetooth) add_subdirectory(backends/bluetooth) endif() option(LOOPBACK_ENABLED "Loopback backend enabled" OFF) set(kdeconnectcore_SRCS ${backends_kdeconnect_SRCS} backends/linkprovider.cpp backends/devicelink.cpp backends/pairinghandler.cpp backends/devicelinereader.cpp kdeconnectplugin.cpp kdeconnectpluginconfig.cpp pluginloader.cpp kdeconnectconfig.cpp dbushelper.cpp networkpacket.cpp filetransferjob.cpp compositefiletransferjob.cpp daemon.cpp device.cpp notificationserverinfo.cpp ) ecm_qt_declare_logging_category( kdeconnectcore_SRCS HEADER core_debug.h IDENTIFIER KDECONNECT_CORE CATEGORY_NAME kdeconnect.core ) add_library(kdeconnectcore ${kdeconnectcore_SRCS}) target_include_directories(kdeconnectcore PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}) target_link_libraries(kdeconnectcore PUBLIC Qt5::Network KF5::CoreAddons KF5::KIOCore qca-qt5 PRIVATE Qt5::DBus KF5::I18n KF5::ConfigCore + KF5::DNSSD ) if (BLUETOOTH_ENABLED) target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_BLUETOOTH) target_link_libraries(kdeconnectcore PRIVATE Qt5::Bluetooth) endif() if (LOOPBACK_ENABLED) target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_LOOPBACK) endif() set_target_properties(kdeconnectcore PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) generate_export_header(kdeconnectcore EXPORT_FILE_NAME kdeconnectcore_export.h BASE_NAME KDEConnectCore) install(TARGETS kdeconnectcore EXPORT kdeconnectLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) diff --git a/core/backends/lan/CMakeLists.txt b/core/backends/lan/CMakeLists.txt index 6a838efb..b71dfa17 100644 --- a/core/backends/lan/CMakeLists.txt +++ b/core/backends/lan/CMakeLists.txt @@ -1,14 +1,15 @@ set(backends_kdeconnect_SRCS ${backends_kdeconnect_SRCS} backends/lan/server.cpp backends/lan/lanlinkprovider.cpp + backends/lan/mdnslinkprovider.cpp backends/lan/landevicelink.cpp backends/lan/lanpairinghandler.cpp backends/lan/compositeuploadjob.cpp backends/lan/uploadjob.cpp backends/lan/socketlinereader.cpp PARENT_SCOPE ) diff --git a/core/backends/lan/mdnslinkprovider.cpp b/core/backends/lan/mdnslinkprovider.cpp new file mode 100644 index 00000000..8b4d9692 --- /dev/null +++ b/core/backends/lan/mdnslinkprovider.cpp @@ -0,0 +1,455 @@ +/** + * Copyright 2019 Albert Vaca + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mdnslinkprovider.h" +#include "core_debug.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemon.h" +#include "landevicelink.h" +#include "lanpairinghandler.h" +#include "kdeconnectconfig.h" + +MDNSLinkProvider::MDNSLinkProvider() + : m_server(new Server(this)) + , m_tcpPort(0) + , m_publisher(nullptr) + , m_serviceBrowser(nullptr) +{ + + m_server->setProxy(QNetworkProxy::NoProxy); + connect(m_server, &QTcpServer::newConnection, this, &MDNSLinkProvider::newConnection); + + //Detect when a network interface changes status, so we announce ourselves in the new network + QNetworkConfigurationManager* networkManager = new QNetworkConfigurationManager(this); + connect(networkManager, &QNetworkConfigurationManager::configurationChanged, this, &MDNSLinkProvider::onNetworkConfigurationChanged); + +} + +#include + +void MDNSLinkProvider::initializeMDNS() +{ + KdeConnectConfig* config = KdeConnectConfig::instance(); + + std::cout << "Init PublicService" << std::endl; + + // Restart the publishing. + delete m_publisher; + m_publisher = new KDNSSD::PublicService(QStringLiteral("KDEConnect on ") + config->name(), QStringLiteral("_kdeconnect._tcp"), m_tcpPort); + +/* + QMap data; + data[QStringLiteral("protocolVersion")] = QString::number(NetworkPacket::s_protocolVersion).toUtf8(); + data[QStringLiteral("type")] = config->deviceType().toUtf8(); + data[QStringLiteral("name")] = config->name().toUtf8(); + data[QStringLiteral("id")] = config->deviceId().toUtf8(); + m_publisher->setTextData(data); +*/ + + if (m_publisher->publish()) { + qCDebug(KDECONNECT_CORE) << "mDNS: published successfully"; + } else { + qCDebug(KDECONNECT_CORE) << "mDNS: failed to publish"; + } + + std::cout << "Init ServiceBrowser" << std::endl; + + delete m_serviceBrowser; + m_serviceBrowser = new KDNSSD::ServiceBrowser(QStringLiteral("_kdeconnect._tcp"), true); //TODO: Check if autoresolve can be false to save bandwidth + connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::serviceAdded, [this](KDNSSD::RemoteService::Ptr service) { + std::cout << service->hostName().toStdString() << service->serviceName().toStdString() << std::endl; + QSslSocket* socket = new QSslSocket(this); + connect(socket, &QAbstractSocket::connected, this, &MDNSLinkProvider::tcpSocketConnected); + connect(socket, QOverload::of(&QAbstractSocket::error), this, &MDNSLinkProvider::connectError); + socket->setProxy(QNetworkProxy::NoProxy); + socket->connectToHost(service->hostName(), service->port()); + }); + connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::finished, []() { + std::cout << "Finished discovery" << std::endl; + }); + m_serviceBrowser->startBrowse(); + + //Discovery +} + + +void MDNSLinkProvider::onNetworkConfigurationChanged(const QNetworkConfiguration& config) +{ + if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) { + m_lastConfig = config; + onNetworkChange(); + } +} + +MDNSLinkProvider::~MDNSLinkProvider() +{ +} + +void MDNSLinkProvider::onStart() +{ + m_tcpPort = MIN_TCP_PORT; + while (!m_server->listen(QHostAddress::Any, m_tcpPort)) { + m_tcpPort++; + if (m_tcpPort > MAX_TCP_PORT) { //No ports available? + qCritical(KDECONNECT_CORE) << "Error opening a port in range" << MIN_TCP_PORT << "-" << MAX_TCP_PORT; + m_tcpPort = 0; + return; + } + } + + onNetworkChange(); + qCDebug(KDECONNECT_CORE) << "MDNSLinkProvider started"; +} + +void MDNSLinkProvider::onStop() +{ + m_server->close(); + qCDebug(KDECONNECT_CORE) << "MDNSLinkProvider stopped"; +} + +void MDNSLinkProvider::onNetworkChange() +{ + initializeMDNS(); +} + +void MDNSLinkProvider::connectError(QAbstractSocket::SocketError socketError) +{ + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCDebug(KDECONNECT_CORE) << "Socket error" << socketError; + + //The socket we created didn't work, and we didn't manage + //to create a LanDeviceLink from it, deleting everything. + delete m_receivedIdentityPackets.take(socket).np; + delete socket; +} + +//We received a UDP packet and answered by connecting to them by TCP. This gets called on a successful connection. +void MDNSLinkProvider::tcpSocketConnected() +{ + std::cout << "tcpSocketConnected" << std::endl; + QSslSocket* socket = qobject_cast(sender()); + if (!socket) { + return; + } + + configureSocket(socket); + + connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + + std::cout << "sending our identity packet to the socket we are connecting to" << std::endl; + sendOurIdentity(socket); + std::cout << "now receiving" << std::endl; + NetworkPacket* receivedPacket = receiveTheirIdentity(socket); + if (!receivedPacket) { + qCWarning(KDECONNECT_CORE) << "Aborting tcpSocketConnected since we couldn't get their identity"; + return; + } + m_receivedIdentityPackets[socket].np = receivedPacket; + const QString& deviceId = receivedPacket->get(QStringLiteral("deviceId")); + + + qCDebug(KDECONNECT_CORE) << "TCP connection done (i'm the existing device)"; + + bool isDeviceTrusted = KdeConnectConfig::instance()->trustedDevices().contains(deviceId); + configureSslSocket(socket, deviceId, isDeviceTrusted); + + qCDebug(KDECONNECT_CORE) << "Starting server ssl (I'm the client TCP socket)"; + + connect(socket, &QSslSocket::encrypted, this, &MDNSLinkProvider::encrypted); + + if (isDeviceTrusted) { + connect(socket, QOverload &>::of(&QSslSocket::sslErrors), this, &MDNSLinkProvider::sslErrors); + } + + socket->startServerEncryption(); +} + +void MDNSLinkProvider::encrypted() +{ + qCDebug(KDECONNECT_CORE) << "Socket successfully established an SSL connection"; + + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; + // TODO delete me? + disconnect(socket, QOverload &>::of(&QSslSocket::sslErrors), this, &MDNSLinkProvider::sslErrors); + + Q_ASSERT(socket->mode() != QSslSocket::UnencryptedMode); + LanDeviceLink::ConnectionStarted connectionOrigin = (socket->mode() == QSslSocket::SslClientMode)? LanDeviceLink::Locally : LanDeviceLink::Remotely; + + NetworkPacket* receivedPacket = m_receivedIdentityPackets[socket].np; + const QString& deviceId = receivedPacket->get(QStringLiteral("deviceId")); + + addLink(deviceId, socket, receivedPacket, connectionOrigin); + + // Copied from tcpSocketConnected slot, now delete received packet + delete m_receivedIdentityPackets.take(socket).np; +} + +void MDNSLinkProvider::sslErrors(const QList& errors) +{ + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCDebug(KDECONNECT_CORE) << "Failing due to " << errors; + Device* device = Daemon::instance()->getDevice(socket->peerVerifyName()); + if (device) { + device->unpair(); + } + + delete m_receivedIdentityPackets.take(socket).np; + // Socket disconnects itself on ssl error and will be deleted by deleteLater slot, no need to delete manually +} + +//I'm the new device and this is the answer to my UDP identity packet (no data received yet). They are connecting to us through TCP, and they should send an identity. +void MDNSLinkProvider::newConnection() +{ + qCDebug(KDECONNECT_CORE) << "MDNSLinkProvider newConnection"; + + while (m_server->hasPendingConnections()) { + QSslSocket* socket = m_server->nextPendingConnection(); + configureSocket(socket); + //This socket is still managed by us (and child of the QTcpServer), if + //it disconnects before we manage to pass it to a LanDeviceLink, it's + //our responsibility to delete it. We do so with this connection. + connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + connect(socket, &QIODevice::readyRead, this, &MDNSLinkProvider::dataReceived); + + } +} + +//I'm the new device and this is the answer to my UDP identity packet (data received) +void MDNSLinkProvider::dataReceived() +{ + QSslSocket* socket = qobject_cast(sender()); + socket->startTransaction(); + + std::cout << "dataReceived" << std::endl; + + sendOurIdentity(socket); + NetworkPacket* theirIdentity = receiveTheirIdentity(socket); + if (!theirIdentity) { + qCWarning(KDECONNECT_CORE) << "Aborting dataReceived since we couldn't get their identity"; + return; + } + m_receivedIdentityPackets[socket].np = theirIdentity; + + const QString& deviceId = theirIdentity->get(QStringLiteral("deviceId")); + qCDebug(KDECONNECT_CORE) << "Handshaking done (i'm the new device)"; + + //This socket will now be owned by the LanDeviceLink or we don't want more data to be received, forget about it + disconnect(socket, &QIODevice::readyRead, this, &MDNSLinkProvider::dataReceived); + + bool isDeviceTrusted = KdeConnectConfig::instance()->trustedDevices().contains(deviceId); + configureSslSocket(socket, deviceId, isDeviceTrusted); + + qCDebug(KDECONNECT_CORE) << "Starting client ssl (but I'm the server TCP socket)"; + + connect(socket, &QSslSocket::encrypted, this, &MDNSLinkProvider::encrypted); + + if (isDeviceTrusted) { + connect(socket, QOverload &>::of(&QSslSocket::sslErrors), this, &MDNSLinkProvider::sslErrors); + } + + socket->startClientEncryption(); + +} + +void MDNSLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink) +{ + const QString id = destroyedDeviceLink->property("deviceId").toString(); + //qCDebug(KDECONNECT_CORE) << "deviceLinkDestroyed" << id; + QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(id); + Q_ASSERT(linkIterator != m_links.end()); + if (linkIterator != m_links.end()) { + Q_ASSERT(linkIterator.value() == destroyedDeviceLink); + m_links.erase(linkIterator); + auto pairingHandler = m_pairingHandlers.take(id); + if (pairingHandler) { + pairingHandler->deleteLater(); + } + } +} + +void MDNSLinkProvider::configureSslSocket(QSslSocket* socket, const QString& deviceId, bool isDeviceTrusted) +{ + /* + // Setting supported ciphers manually, to match those on Android (FIXME: Test if this can be left unconfigured and still works for Android 4) + QList socketCiphers; + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES256-GCM-SHA384"))); + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES128-GCM-SHA256"))); + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-RSA-AES128-SHA"))); + // Configure for ssl + QSslConfiguration sslConfig; + sslConfig.setCiphers(socketCiphers); + + socket->setSslConfiguration(sslConfig); + */ + socket->setLocalCertificate(KdeConnectConfig::instance()->certificate()); + socket->setPrivateKey(KdeConnectConfig::instance()->privateKeyPath()); + socket->setPeerVerifyName(deviceId); + + if (isDeviceTrusted) { + QString certString = KdeConnectConfig::instance()->getDeviceProperty(deviceId, QStringLiteral("certificate"), QString()); + socket->addCaCertificate(QSslCertificate(certString.toLatin1())); + socket->setPeerVerifyMode(QSslSocket::VerifyPeer); + } else { + socket->setPeerVerifyMode(QSslSocket::QueryPeer); + } + + //Usually SSL errors are only bad for trusted devices. Uncomment this section to log errors in any case, for debugging. + //QObject::connect(socket, static_cast&)>(&QSslSocket::sslErrors), [](const QList& errors) + //{ + // Q_FOREACH (const QSslError& error, errors) { + // qCDebug(KDECONNECT_CORE) << "SSL Error:" << error.errorString(); + // } + //}); +} + +void MDNSLinkProvider::configureSocket(QSslSocket* socket) { + + socket->setProxy(QNetworkProxy::NoProxy); + + socket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1)); + + #ifdef TCP_KEEPIDLE + // time to start sending keepalive packets (seconds) + int maxIdle = 10; + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle)); + #endif + + #ifdef TCP_KEEPINTVL + // interval between keepalive packets after the initial period (seconds) + int interval = 5; + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); + #endif + + #ifdef TCP_KEEPCNT + // number of missed keepalive packets before disconnecting + int count = 3; + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); + #endif + +} + +void MDNSLinkProvider::addLink(const QString& deviceId, QSslSocket* socket, NetworkPacket* receivedPacket, LanDeviceLink::ConnectionStarted connectionOrigin) +{ + // Socket disconnection will now be handled by LanDeviceLink + disconnect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + + LanDeviceLink* deviceLink; + //Do we have a link for this device already? + QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(deviceId); + if (linkIterator != m_links.end()) { + //qCDebug(KDECONNECT_CORE) << "Reusing link to" << deviceId; + deviceLink = linkIterator.value(); + deviceLink->reset(socket, connectionOrigin); + } else { + deviceLink = new LanDeviceLink(deviceId, this, socket, connectionOrigin); + connect(deviceLink, &QObject::destroyed, this, &MDNSLinkProvider::deviceLinkDestroyed); + m_links[deviceId] = deviceLink; + if (m_pairingHandlers.contains(deviceId)) { + //We shouldn't have a pairinghandler if we didn't have a link. + //Crash if debug, recover if release (by setting the new devicelink to the old pairinghandler) + Q_ASSERT(m_pairingHandlers.contains(deviceId)); + m_pairingHandlers[deviceId]->setDeviceLink(deviceLink); + } + } + Q_EMIT onConnectionReceived(*receivedPacket, deviceLink); +} + +LanPairingHandler* MDNSLinkProvider::createPairingHandler(DeviceLink* link) +{ + LanPairingHandler* ph = m_pairingHandlers.value(link->deviceId()); + if (!ph) { + ph = new LanPairingHandler(link); + qCDebug(KDECONNECT_CORE) << "creating pairing handler for" << link->deviceId(); + connect (ph, &LanPairingHandler::pairingError, link, &DeviceLink::pairingError); + m_pairingHandlers[link->deviceId()] = ph; + } + return ph; +} + +void MDNSLinkProvider::userRequestsPair(const QString& deviceId) +{ + LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId)); + ph->requestPairing(); +} + +void MDNSLinkProvider::userRequestsUnpair(const QString& deviceId) +{ + LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId)); + ph->unpair(); +} + +void MDNSLinkProvider::incomingPairPacket(DeviceLink* deviceLink, const NetworkPacket& np) +{ + LanPairingHandler* ph = createPairingHandler(deviceLink); + ph->packetReceived(np); +} + + +NetworkPacket* MDNSLinkProvider::receiveTheirIdentity(QSslSocket* socket) +{ + + const QByteArray data = socket->readLine(); + qCDebug(KDECONNECT_CORE) << "MDNSLinkProvider received reply:" << data; + + NetworkPacket* theirIdentity = new NetworkPacket(); + bool success = NetworkPacket::unserialize(data, theirIdentity); + + if (!success) { + delete theirIdentity; + socket->rollbackTransaction(); + return nullptr; + } + socket->commitTransaction(); + + if (theirIdentity->type() != PACKET_TYPE_IDENTITY) { + qCWarning(KDECONNECT_CORE) << "MDNSLinkProvider/newConnection: Expected identity, received " << theirIdentity->type(); + delete theirIdentity; + return nullptr; + } + + // Needed in "encrypted" if ssl is used, similar to "tcpSocketConnected" + return theirIdentity; +} + +void MDNSLinkProvider::sendOurIdentity(QSslSocket* socket) +{ + NetworkPacket* ourIdentity = new NetworkPacket(); + NetworkPacket::createIdentityPacket(ourIdentity); + socket->write(ourIdentity->serialize()); + delete ourIdentity; +} diff --git a/core/backends/lan/mdnslinkprovider.h b/core/backends/lan/mdnslinkprovider.h new file mode 100644 index 00000000..6472a253 --- /dev/null +++ b/core/backends/lan/mdnslinkprovider.h @@ -0,0 +1,103 @@ +/** + * Copyright 2013 Albert Vaca + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MDNSLINKPROVIDER_H +#define MDNSLINKPROVIDER_H + +#include +#include +#include +#include +#include + +#include "kdeconnectcore_export.h" +#include "backends/linkprovider.h" +#include "server.h" +#include "landevicelink.h" + +namespace KDNSSD { + class PublicService; + class ServiceBrowser; +}; + +class LanPairingHandler; +class KDECONNECTCORE_EXPORT MDNSLinkProvider + : public LinkProvider +{ + Q_OBJECT + +public: + MDNSLinkProvider(); + ~MDNSLinkProvider() override; + + QString name() override { return QStringLiteral("MDNSLinkProvider"); } + int priority() override { return PRIORITY_HIGH; } + + void userRequestsPair(const QString& deviceId); + void userRequestsUnpair(const QString& deviceId); + void incomingPairPacket(DeviceLink* device, const NetworkPacket& np); + + static void configureSslSocket(QSslSocket* socket, const QString& deviceId, bool isDeviceTrusted); + static void configureSocket(QSslSocket* socket); + + const static quint16 MIN_TCP_PORT = 1716; + const static quint16 MAX_TCP_PORT = 1764; + +public Q_SLOTS: + void onNetworkChange() override; + void onStart() override; + void onStop() override; + void tcpSocketConnected(); + void encrypted(); + void connectError(QAbstractSocket::SocketError socketError); + +private Q_SLOTS: + void newConnection(); + void dataReceived(); + void deviceLinkDestroyed(QObject* destroyedDeviceLink); + void sslErrors(const QList& errors); + +private: + LanPairingHandler* createPairingHandler(DeviceLink* link); + + void onNetworkConfigurationChanged(const QNetworkConfiguration& config); + void addLink(const QString& deviceId, QSslSocket* socket, NetworkPacket* receivedPacket, LanDeviceLink::ConnectionStarted connectionOrigin); + void initializeMDNS(); + void sendOurIdentity(QSslSocket*); + NetworkPacket* receiveTheirIdentity(QSslSocket*); + + Server* m_server; + quint16 m_tcpPort; + + QMap m_links; + QMap m_pairingHandlers; + + struct PendingConnect { + NetworkPacket* np; + QHostAddress sender; + }; + QMap m_receivedIdentityPackets; + QNetworkConfiguration m_lastConfig; + + KDNSSD::PublicService *m_publisher; + KDNSSD::ServiceBrowser *m_serviceBrowser; +}; + +#endif diff --git a/core/daemon.cpp b/core/daemon.cpp index 1fdfe68a..133935be 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,328 +1,330 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "daemon.h" #include #include #include #include #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpacket.h" #include "dbushelper.h" #include "notificationserverinfo.h" #ifdef KDECONNECT_BLUETOOTH #include "backends/bluetooth/bluetoothlinkprovider.h" #endif #include "backends/lan/lanlinkprovider.h" +#include "backends/lan/mdnslinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet m_linkProviders; //Every known device QMap m_devices; QSet m_discoveryModeAcquisitions; bool m_testMode; }; Daemon* Daemon::instance() { Q_ASSERT(s_instance != nullptr); return s_instance; } Daemon::Daemon(QObject* parent, bool testMode) : QObject(parent) , d(new DaemonPrivate) { Q_ASSERT(!s_instance); s_instance = this; d->m_testMode = testMode; // HACK init may call pure virtual functions from this class so it can't be called directly from the ctor QTimer::singleShot(0, this, &Daemon::init); } void Daemon::init() { qCDebug(KDECONNECT_CORE) << "Daemon starting"; //Load backends if (d->m_testMode) d->m_linkProviders.insert(new LoopbackLinkProvider()); else { - d->m_linkProviders.insert(new LanLinkProvider()); + //d->m_linkProviders.insert(new LanLinkProvider()); + d->m_linkProviders.insert(new MDNSLinkProvider()); #ifdef KDECONNECT_BLUETOOTH d->m_linkProviders.insert(new BluetoothLinkProvider()); #endif #ifdef KDECONNECT_LOOPBACK d->m_linkProviders.insert(new LoopbackLinkProvider()); #endif } //Read remembered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); for (const QString& id : list) { addDevice(new Device(this, id)); } //Listen to new devices for (LinkProvider* a : qAsConst(d->m_linkProviders)) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus qDBusRegisterMetaType< QMap >(); DbusHelper::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); DbusHelper::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); NotificationServerInfo::instance().init(); qCDebug(KDECONNECT_CORE) << "Daemon started"; } void Daemon::acquireDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.insert(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.remove(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->m_devices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); Q_EMIT deviceListChanged(); } void Daemon::cleanDevices() { const auto devs = d->m_devices; for (Device* device : devs) { if (device->isTrusted()) { continue; } device->cleanUnneededLinks(); //If there are no links remaining if (!device->isReachable()) { removeDevice(device); } } } void Daemon::forceOnNetworkChange() { qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to" << d->m_linkProviders.size() << "LinkProviders"; for (LinkProvider* a : qAsConst(d->m_linkProviders)) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { for (Device* device : qAsConst(d->m_devices)) { if (device->id() == deviceId) { return device; } } return nullptr; } const QSet& Daemon::getLinkProviders() const { return d->m_linkProviders; } QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } QMap Daemon::deviceNames(bool onlyReachable, bool onlyTrusted) const { QMap ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret[device->id()] = device->name(); } return ret; } void Daemon::onNewDeviceLink(const NetworkPacket& identityPacket, DeviceLink* dl) { const QString& id = identityPacket.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->m_devices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = d->m_devices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPacket, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); Q_EMIT deviceListChanged(); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPacket, dl); //we discard the connections that we created but it's not paired. if (!isDiscoveringDevices() && !device->isTrusted() && !dl->linkShouldBeKeptAlive()) { device->deleteLater(); } else { addDevice(device); } } } void Daemon::onDeviceStatusChanged() { Device* device = (Device*)sender(); //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); if (!device->isReachable() && !device->isTrusted()) { //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); removeDevice(device); } else { Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); Q_EMIT deviceListChanged(); } } void Daemon::setAnnouncedName(const QString& name) { qCDebug(KDECONNECT_CORE()) << "Announcing name"; KdeConnectConfig::instance()->setName(name); forceOnNetworkChange(); Q_EMIT announcedNameChanged(name); } QString Daemon::announcedName() { return KdeConnectConfig::instance()->name(); } QNetworkAccessManager* Daemon::networkAccessManager() { static QPointer manager; if (!manager) { manager = new QNetworkAccessManager(this); } return manager; } QList Daemon::devicesList() const { return d->m_devices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->m_discoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString& name) const { for (Device* device : qAsConst(d->m_devices)) { if (device->name() == name && device->isTrusted()) return device->id(); } return {}; } void Daemon::addDevice(Device* device) { const QString id = device->id(); connect(device, &Device::reachableChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::trustedChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::hasPairingRequestsChanged, this, &Daemon::pairingRequestsChanged); connect(device, &Device::hasPairingRequestsChanged, this, [this, device](bool hasPairingRequests) { if (hasPairingRequests) askPairingConfirmation(device); } ); d->m_devices[id] = device; Q_EMIT deviceAdded(id); Q_EMIT deviceListChanged(); } QStringList Daemon::pairingRequests() const { QStringList ret; for(Device* dev: qAsConst(d->m_devices)) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/networkpacket.h b/core/networkpacket.h index b059c8b9..bfa6f2c9 100644 --- a/core/networkpacket.h +++ b/core/networkpacket.h @@ -1,101 +1,101 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef NETWORKPACKET_H #define NETWORKPACKET_H #include "networkpackettypes.h" #include #include #include #include #include #include #include "kdeconnectcore_export.h" class FileTransferJob; class KDECONNECTCORE_EXPORT NetworkPacket { Q_GADGET Q_PROPERTY( QString id READ id WRITE setId ) Q_PROPERTY( QString type READ type WRITE setType ) Q_PROPERTY( QVariantMap body READ body WRITE setBody ) Q_PROPERTY( QVariantMap payloadTransferInfo READ payloadTransferInfo WRITE setPayloadTransferInfo ) Q_PROPERTY( qint64 payloadSize READ payloadSize WRITE setPayloadSize ) public: const static int s_protocolVersion; explicit NetworkPacket(const QString& type = QStringLiteral("empty"), const QVariantMap& body = {}); NetworkPacket(const NetworkPacket& other); // Copy constructor, required for QMetaType and queued signals static void createIdentityPacket(NetworkPacket*); QByteArray serialize() const; static bool unserialize(const QByteArray& json, NetworkPacket* out); const QString& id() const { return m_id; } const QString& type() const { return m_type; } QVariantMap& body() { return m_body; } const QVariantMap& body() const { return m_body; } //Get and set info from body. Note that id and type can not be accessed through these. template T get(const QString& key, const T& defaultValue = {}) const { return m_body.value(key,defaultValue).template value(); //Important note: Awesome template syntax is awesome } template void set(const QString& key, const T& value) { m_body[key] = QVariant(value); } bool has(const QString& key) const { return m_body.contains(key); } QSharedPointer payload() const { return m_payload; } void setPayload(const QSharedPointer& device, qint64 payloadSize) { m_payload = device; m_payloadSize = payloadSize; Q_ASSERT(m_payloadSize >= -1); } bool hasPayload() const { return (m_payloadSize != 0); } qint64 payloadSize() const { return m_payloadSize; } //-1 means it is an endless stream FileTransferJob* createPayloadTransferJob(const QUrl& destination) const; //To be called by a particular DeviceLink QVariantMap payloadTransferInfo() const { return m_payloadTransferInfo; } void setPayloadTransferInfo(const QVariantMap& map) { m_payloadTransferInfo = map; } bool hasPayloadTransferInfo() const { return !m_payloadTransferInfo.isEmpty(); } private: void setId(const QString& id) { m_id = id; } void setType(const QString& t) { m_type = t; } void setBody(const QVariantMap& b) { m_body = b; } void setPayloadSize(qint64 s) { m_payloadSize = s; } QString m_id; QString m_type; QVariantMap m_body; - + QSharedPointer m_payload; qint64 m_payloadSize; QVariantMap m_payloadTransferInfo; }; KDECONNECTCORE_EXPORT QDebug operator<<(QDebug s, const NetworkPacket& pkg); Q_DECLARE_METATYPE(NetworkPacket) #endif // NETWORKPACKET_H