diff --git a/CMakeLists.txt b/CMakeLists.txt index 45e085fe..09ed1a85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,132 +1,136 @@ 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.42.0") set(QT_MIN_VERSION "5.10.0") set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service) 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) option(EXPERIMENTALAPP_ENABLED OFF) if(EXPERIMENTALAPP_ENABLED) find_package(KF5Kirigami2) add_subdirectory(app) endif() add_subdirectory(plugins) add_subdirectory(cli) add_subdirectory(declarativeplugin) if(KF5Runner_FOUND) add_subdirectory(runners) endif() if (NOT SAILFISHOS) add_subdirectory(indicator) add_subdirectory(urlhandler) add_subdirectory(nautilus-extension) add_subdirectory(fileitemactionplugin) else() add_subdirectory(sfos) endif() option(SMSAPP_ENABLED OFF) if(SMSAPP_ENABLED) 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) endif() 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 5cc95fa6..db216793 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,79 +1,82 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") -set(KDECONNECT_PRIVATE_DBUS_ADDR unix:path=/tmp/kdeconnect-dbus) +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 ) 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/dbushelper.cpp b/core/dbushelper.cpp index 20383dee..6fe6c958 100644 --- a/core/dbushelper.cpp +++ b/core/dbushelper.cpp @@ -1,40 +1,147 @@ /** * Copyright 2014 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 "dbushelper.h" +#include "core_debug.h" + +#include +#include +#include +#include +#include +#include + +#include "kdeconnectconfig.h" + +#ifdef Q_OS_MAC +#include +#endif namespace DbusHelper { +#ifdef USE_PRIVATE_DBUS +class DBusInstancePrivate +{ +public: + DBusInstancePrivate(); + ~DBusInstancePrivate(); + + void launchDBusDaemon(); + void closeDBusDaemon(); +private: + QProcess *m_dbusProcess; +}; + +static DBusInstancePrivate dbusInstance; +#endif + void filterNonExportableCharacters(QString& s) { static QRegExp regexp(QStringLiteral("[^A-Za-z0-9_]"), Qt::CaseSensitive, QRegExp::Wildcard); s.replace(regexp,QLatin1String("_")); } QDBusConnection sessionBus() { -#ifdef Q_OS_MAC - return QDBusConnection::connectToBus(QStringLiteral(KDECONNECT_PRIVATE_DBUS_ADDR), QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME)); +#ifdef USE_PRIVATE_DBUS + return QDBusConnection::connectToBus(KdeConnectConfig::instance()->privateDBusAddress(), + QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME)); #else return QDBusConnection::sessionBus(); #endif } +#ifdef USE_PRIVATE_DBUS +void launchDBusDaemon() +{ + dbusInstance.launchDBusDaemon(); +} + +void closeDBusDaemon() +{ + dbusInstance.closeDBusDaemon(); +} + +void DBusInstancePrivate::launchDBusDaemon() +{ + // Kill old dbus daemon + if (m_dbusProcess != nullptr) closeDBusDaemon(); + + // Start dbus daemon + m_dbusProcess = new QProcess(); + #ifdef Q_OS_MAC + // On macOS, assuming the executable is in Contents/MacOS + CFURLRef url = (CFURLRef)CFAutorelease((CFURLRef)CFBundleCopyBundleURL(CFBundleGetMainBundle())); + QString basePath = QUrl::fromCFURL(url).path(); + QString kdeconnectDBusExecutable = basePath + QStringLiteral("Contents/MacOS/dbus-daemon"), + kdeconnectDBusConfiguration = basePath + QStringLiteral("Contents/Resources/dbus-1/session.conf"); + qCDebug(KDECONNECT_CORE) << "App package path: " << basePath; + + m_dbusProcess->setProgram(kdeconnectDBusExecutable); + m_dbusProcess->setArguments({QStringLiteral("--print-address"), + QStringLiteral("--nofork"), + QStringLiteral("--config-file=") + kdeconnectDBusConfiguration, + QStringLiteral("--address=") + QStringLiteral(KDECONNECT_PRIVATE_DBUS_ADDR)}); + m_dbusProcess->setWorkingDirectory(basePath); + #elif defined(Q_OS_WIN) + // On Windows + m_dbusProcess->setProgram(QStringLiteral("dbus-daemon.exe")); + m_dbusProcess->setArguments({QStringLiteral("--session"), + QStringLiteral("--print-address"), + QStringLiteral("--nofork"), + QStringLiteral("--address=") + QStringLiteral(KDECONNECT_PRIVATE_DBUS_ADDR)}); + #else + // On Linux or other unix-like system + m_dbusProcess->setProgram(QStringLiteral("dbus-daemon")); + m_dbusProcess->setArguments({QStringLiteral("--session"), + QStringLiteral("--print-address"), + QStringLiteral("--nofork"), + QStringLiteral("--address=") + QStringLiteral(KDECONNECT_PRIVATE_DBUS_ADDR)}); + #endif + m_dbusProcess->setStandardOutputFile(KdeConnectConfig::instance()->privateDBusAddressPath()); + m_dbusProcess->setStandardErrorFile(QProcess::nullDevice()); + m_dbusProcess->start(); +} + +void DBusInstancePrivate::closeDBusDaemon() +{ + if (m_dbusProcess != nullptr) + { + m_dbusProcess->terminate(); + m_dbusProcess->waitForFinished(); + delete m_dbusProcess; + m_dbusProcess = nullptr; + + QFile privateDBusAddressFile(KdeConnectConfig::instance()->privateDBusAddressPath()); + + if (privateDBusAddressFile.exists()) privateDBusAddressFile.resize(0); + } +} + +DBusInstancePrivate::DBusInstancePrivate() + :m_dbusProcess(nullptr){} + +DBusInstancePrivate::~DBusInstancePrivate() +{ + closeDBusDaemon(); +} +#endif + } diff --git a/core/dbushelper.h.in b/core/dbushelper.h.in index 9b8775fb..ae070176 100644 --- a/core/dbushelper.h.in +++ b/core/dbushelper.h.in @@ -1,37 +1,41 @@ /** * Copyright 2014 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 KDECONNECT_DBUSHELPER_H #define KDECONNECT_DBUSHELPER_H #include #include #include "kdeconnectcore_export.h" #define KDECONNECT_PRIVATE_DBUS_ADDR "${KDECONNECT_PRIVATE_DBUS_ADDR}" #define KDECONNECT_PRIVATE_DBUS_NAME "${KDECONNECT_PRIVATE_DBUS_NAME}" namespace DbusHelper { void KDECONNECTCORE_EXPORT filterNonExportableCharacters(QString& s); +#ifdef USE_PRIVATE_DBUS + void KDECONNECTCORE_EXPORT launchDBusDaemon(); + void KDECONNECTCORE_EXPORT closeDBusDaemon(); +#endif QDBusConnection KDECONNECTCORE_EXPORT sessionBus(); } #endif diff --git a/core/kdeconnectconfig.cpp b/core/kdeconnectconfig.cpp index 79cde179..84385323 100644 --- a/core/kdeconnectconfig.cpp +++ b/core/kdeconnectconfig.cpp @@ -1,329 +1,377 @@ /** * Copyright 2015 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 "kdeconnectconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "core_debug.h" #include "dbushelper.h" #include "daemon.h" const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; struct KdeConnectConfigPrivate { // The Initializer object sets things up, and also does cleanup when it goes out of scope // Note it's not being used anywhere. That's intended QCA::Initializer m_qcaInitializer; QCA::PrivateKey m_privateKey; QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket QSettings* m_config; QSettings* m_trustedDevices; +#ifdef USE_PRIVATE_DBUS + QString m_privateDBusAddress; // Private DBus Address cache +#endif }; KdeConnectConfig* KdeConnectConfig::instance() { static KdeConnectConfig* kcc = new KdeConnectConfig(); return kcc; } KdeConnectConfig::KdeConnectConfig() : d(new KdeConnectConfigPrivate) { //qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(","); if(!QCA::isSupported("rsa")) { qCritical() << "Could not find support for RSA in your QCA installation"; Daemon::instance()->reportError( i18n("KDE Connect failed to start"), i18n("Could not find support for RSA in your QCA installation. If your " "distribution provides separate packets for QCA-ossl and QCA-gnupg, " "make sure you have them installed and try again.")); } //Make sure base directory exists QDir().mkpath(baseConfigDir().path()); //.config/kdeconnect/config d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); loadPrivateKey(); loadCertificate(); } QString KdeConnectConfig::name() { QString username; #ifdef Q_OS_WIN username = QString::fromLatin1(qgetenv("USERNAME")); #else username = QString::fromLatin1(qgetenv("USER")); #endif QString defaultName = username + QStringLiteral("@") + QHostInfo::localHostName(); QString name = d->m_config->value(QStringLiteral("name"), defaultName).toString(); return name; } void KdeConnectConfig::setName(const QString& name) { d->m_config->setValue(QStringLiteral("name"), name); d->m_config->sync(); } QString KdeConnectConfig::deviceType() { return QStringLiteral("desktop"); // TODO } QString KdeConnectConfig::deviceId() { return d->m_certificate.subjectInfo(QSslCertificate::CommonName).constFirst(); } QString KdeConnectConfig::privateKeyPath() { return baseConfigDir().absoluteFilePath(QStringLiteral("privateKey.pem")); } QString KdeConnectConfig::certificatePath() { return baseConfigDir().absoluteFilePath(QStringLiteral("certificate.pem")); } QSslCertificate KdeConnectConfig::certificate() { return d->m_certificate; } QDir KdeConnectConfig::baseConfigDir() { QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath(QStringLiteral("kdeconnect")); return QDir(kdeconnectConfigPath); } QStringList KdeConnectConfig::trustedDevices() { const QStringList& list = d->m_trustedDevices->childGroups(); return list; } void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) { d->m_trustedDevices->beginGroup(id); d->m_trustedDevices->setValue(QStringLiteral("name"), name); d->m_trustedDevices->setValue(QStringLiteral("type"), type); d->m_trustedDevices->endGroup(); d->m_trustedDevices->sync(); QDir().mkpath(deviceConfigDir(id).path()); } KdeConnectConfig::DeviceInfo KdeConnectConfig::getTrustedDevice(const QString& id) { d->m_trustedDevices->beginGroup(id); KdeConnectConfig::DeviceInfo info; info.deviceName = d->m_trustedDevices->value(QStringLiteral("name"), QLatin1String("unnamed")).toString(); info.deviceType = d->m_trustedDevices->value(QStringLiteral("type"), QLatin1String("unknown")).toString(); d->m_trustedDevices->endGroup(); return info; } void KdeConnectConfig::removeTrustedDevice(const QString& deviceId) { d->m_trustedDevices->remove(deviceId); d->m_trustedDevices->sync(); //We do not remove the config files. } // Utility functions to set and get a value void KdeConnectConfig::setDeviceProperty(const QString& deviceId, const QString& key, const QString& value) { d->m_trustedDevices->beginGroup(deviceId); d->m_trustedDevices->setValue(key, value); d->m_trustedDevices->endGroup(); d->m_trustedDevices->sync(); } QString KdeConnectConfig::getDeviceProperty(const QString& deviceId, const QString& key, const QString& defaultValue) { QString value; d->m_trustedDevices->beginGroup(deviceId); value = d->m_trustedDevices->value(key, defaultValue).toString(); d->m_trustedDevices->endGroup(); return value; } QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); return QDir(deviceConfigPath); } QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); return QDir(pluginConfigDir); } void KdeConnectConfig::loadPrivateKey() { QString keyPath = privateKeyPath(); QFile privKey(keyPath); bool needsToGenerateKey = false; if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) { QCA::ConvertResult result; d->m_privateKey = QCA::PrivateKey::fromPEM(QString::fromLatin1(privKey.readAll()), QCA::SecureArray(), &result); if (result != QCA::ConvertResult::ConvertGood) { qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid"; needsToGenerateKey = true; } } else { needsToGenerateKey = true; } if (needsToGenerateKey) { generatePrivateKey(keyPath); } //Extra security check if (QFile::permissions(keyPath) != strictPermissions) { qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; } } void KdeConnectConfig::loadCertificate() { QString certPath = certificatePath(); QFile cert(certPath); bool needsToGenerateCert = false; if (cert.exists() && cert.open(QIODevice::ReadOnly)) { auto loadedCerts = QSslCertificate::fromPath(certPath); if (loadedCerts.empty()) { qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid"; needsToGenerateCert = true; } else { d->m_certificate = loadedCerts.at(0); } } else { needsToGenerateCert = true; } if (needsToGenerateCert) { generateCertificate(certPath); } //Extra security check if (QFile::permissions(certPath) != strictPermissions) { qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath; } } void KdeConnectConfig::generatePrivateKey(const QString& keyPath) { qCDebug(KDECONNECT_CORE) << "Generating private key"; bool error = false; d->m_privateKey = QCA::KeyGenerator().createRSA(2048); QFile privKey(keyPath); if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { error = true; } else { privKey.setPermissions(strictPermissions); int written = privKey.write(d->m_privateKey.toPEM().toLatin1()); if (written <= 0) { error = true; } } if (error) { Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); } } void KdeConnectConfig::generateCertificate(const QString& certPath) { qCDebug(KDECONNECT_CORE) << "Generating certificate"; bool error = false; QString uuid = QUuid::createUuid().toString(); DbusHelper::filterNonExportableCharacters(uuid); qCDebug(KDECONNECT_CORE) << "My id:" << uuid; // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely. // The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable): // openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout privateKey.pem -days 3650 -out certificate.pem -subj "/O=KDE/OU=KDE Connect/CN=_e6e29ad4_2b31_4b6d_8f7a_9872dbaa9095_" QCA::CertificateOptions certificateOptions = QCA::CertificateOptions(); QDateTime startTime = QDateTime::currentDateTime().addYears(-1); QDateTime endTime = startTime.addYears(10); QCA::CertificateInfo certificateInfo; certificateInfo.insert(QCA::CommonName, uuid); certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); certificateOptions.setInfo(certificateInfo); certificateOptions.setFormat(QCA::PKCS10); certificateOptions.setSerialNumber(QCA::BigInteger(10)); certificateOptions.setValidityPeriod(startTime, endTime); d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1()); QFile cert(certPath); if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { error = true; } else { cert.setPermissions(strictPermissions); int written = cert.write(d->m_certificate.toPem()); if (written <= 0) { error = true; } } if (error) { Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); } } + +#ifdef USE_PRIVATE_DBUS +QString KdeConnectConfig::privateDBusAddressPath() +{ + return baseConfigDir().absoluteFilePath(QStringLiteral("private_dbus_address")); +} + +QString KdeConnectConfig::privateDBusAddress() +{ + if (d->m_privateDBusAddress.length() != 0) return d->m_privateDBusAddress; + + QString dbusAddressPath = privateDBusAddressPath(); + QFile dbusAddressFile(dbusAddressPath); + + if (!dbusAddressFile.open(QFile::ReadOnly | QFile::Text)) { + qCCritical(KDECONNECT_CORE) << "Private DBus enabled but error read private dbus address conf"; + exit(1); + } + + QTextStream in(&dbusAddressFile); + + qCDebug(KDECONNECT_CORE) << "Waiting for private dbus"; + + int retry = 0; + QString addr = in.readLine(); + while(addr.length() == 0 && retry < 5) { + qCDebug(KDECONNECT_CORE) << "Retry reading private DBus address after 3s"; + QThread::sleep(3); + retry ++; + addr = in.readLine(); // Read until first not empty line + } + + if (addr.length() == 0) { + qCCritical(KDECONNECT_CORE) << "Private DBus enabled but read private dbus address failed"; + exit(1); + } + + qCDebug(KDECONNECT_CORE) << "Private dbus address: " << addr; + + d->m_privateDBusAddress = addr; + + return addr; +} +#endif diff --git a/core/kdeconnectconfig.h b/core/kdeconnectconfig.h index 003215d4..fad9b623 100644 --- a/core/kdeconnectconfig.h +++ b/core/kdeconnectconfig.h @@ -1,84 +1,91 @@ /** * Copyright 2015 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 KDECONNECTCONFIG_H #define KDECONNECTCONFIG_H #include #include "kdeconnectcore_export.h" class QSslCertificate; class KDECONNECTCORE_EXPORT KdeConnectConfig { public: struct DeviceInfo { QString deviceName; QString deviceType; }; static KdeConnectConfig* instance(); /* * Our own info */ QString deviceId(); QString name(); QString deviceType(); QString privateKeyPath(); QString certificatePath(); QSslCertificate certificate(); void setName(const QString& name); /* * Trusted devices */ QStringList trustedDevices(); //list of ids void removeTrustedDevice(const QString& id); void addTrustedDevice(const QString& id, const QString& name, const QString& type); KdeConnectConfig::DeviceInfo getTrustedDevice(const QString& id); void setDeviceProperty(const QString& deviceId, const QString& name, const QString& value); QString getDeviceProperty(const QString& deviceId, const QString& name, const QString& defaultValue = QString()); /* * Paths for config files, there is no guarantee the directories already exist */ QDir baseConfigDir(); QDir deviceConfigDir(const QString& deviceId); QDir pluginConfigDir(const QString& deviceId, const QString& pluginName); //Used by KdeConnectPluginConfig +#ifdef USE_PRIVATE_DBUS + /* + * Get private DBus Address when use private DBus + */ + QString privateDBusAddressPath(); + QString privateDBusAddress(); +#endif private: KdeConnectConfig(); void loadPrivateKey(); void generatePrivateKey(const QString& path); void loadCertificate(); void generateCertificate(const QString& path); struct KdeConnectConfigPrivate* d; }; #endif diff --git a/daemon/kdeconnectd.cpp b/daemon/kdeconnectd.cpp index 43ba6618..91b8be3a 100644 --- a/daemon/kdeconnectd.cpp +++ b/daemon/kdeconnectd.cpp @@ -1,145 +1,149 @@ /** * Copyright 2014 Yuri Samoilenko * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/backends/pairinghandler.h" #include "kdeconnect-version.h" Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_DAEMON) Q_LOGGING_CATEGORY(KDECONNECT_DAEMON, "kdeconnect.daemon") class DesktopDaemon : public Daemon { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") public: DesktopDaemon(QObject* parent = nullptr) : Daemon(parent) , m_nam(nullptr) {} void askPairingConfirmation(Device* device) override { KNotification* notification = new KNotification(QStringLiteral("pairingRequest")); notification->setIconName(QStringLiteral("dialog-information")); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setText(i18n("Pairing request from %1", device->name().toHtmlEscaped())); notification->setActions(QStringList() << i18n("Accept") << i18n("Reject")); // notification->setTimeout(PairingHandler::pairingTimeoutMsec()); connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing); connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing); notification->sendEvent(); } void reportError(const QString & title, const QString & description) override { qCWarning(KDECONNECT_DAEMON) << title << ":" << description; KNotification::event(KNotification::Error, title, description); } QNetworkAccessManager* networkAccessManager() override { if (!m_nam) { m_nam = new KIO::AccessManager(this); } return m_nam; } Q_SCRIPTABLE void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) override { KNotification* notification = new KNotification(eventId); //KNotification::Persistent notification->setIconName(iconName); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(title); notification->setText(text); notification->sendEvent(); } void quit() override { QApplication::quit(); } private: QNetworkAccessManager* m_nam; }; int main(int argc, char* argv[]) { QApplication app(argc, argv); KAboutData aboutData( QStringLiteral("org.kde.kdeconnect.daemon"), i18n("KDE Connect Daemon"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Daemon"), KAboutLicense::GPL ); KAboutData::setApplicationData(aboutData); app.setQuitOnLastWindowClosed(false); +#ifdef USE_PRIVATE_DBUS + DbusHelper::launchDBusDaemon(); +#endif + QCommandLineParser parser; QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance")); parser.addOption(replaceOption); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); if (parser.isSet(replaceOption)) { auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnectd"), QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); DbusHelper::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits } -#ifndef Q_OS_MAC +#ifndef USE_PRIVATE_DBUS KDBusService dbusService(KDBusService::Unique); #endif DesktopDaemon daemon; // kdeconnectd is autostarted, so disable session management to speed up startup auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); return app.exec(); } #include "kdeconnectd.moc" diff --git a/indicator/main.cpp b/indicator/main.cpp index b95f15d0..8905a75c 100644 --- a/indicator/main.cpp +++ b/indicator/main.cpp @@ -1,150 +1,150 @@ /* * Copyright 2016 Aleix Pol Gonzalez * * 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 #include #ifdef QSYSTRAY #include #else #include #endif #include #include #include #include #include "interfaces/devicesmodel.h" #include "interfaces/dbusinterfaces.h" #include "kdeconnect-version.h" #include "deviceindicator.h" #ifdef Q_OS_MAC #include #endif #include int main(int argc, char** argv) { QApplication app(argc, argv); KAboutData about(QStringLiteral("kdeconnect-indicator"), i18n("KDE Connect Indicator"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Indicator tool"), KAboutLicense::GPL, i18n("(C) 2016 Aleix Pol Gonzalez")); KAboutData::setApplicationData(about); #ifdef Q_OS_WIN QProcess::startDetached(QStringLiteral("kdeconnectd.exe")); #endif #ifdef Q_OS_MAC // Get bundle path CFURLRef url = (CFURLRef)CFAutorelease((CFURLRef)CFBundleCopyBundleURL(CFBundleGetMainBundle())); QString basePath = QUrl::fromCFURL(url).path(); QProcess kdeconnectdProcess; kdeconnectdProcess.start(basePath + QStringLiteral("Contents/MacOS/kdeconnectd")); // Start kdeconnectd #endif -#ifndef Q_OS_MAC +#ifndef USE_PRIVATE_DBUS KDBusService dbusService(KDBusService::Unique); #endif DevicesModel model; model.setDisplayFilter(DevicesModel::Reachable | DevicesModel::Paired); QMenu* menu = new QMenu; DaemonDbusInterface iface; auto refreshMenu = [&iface, &model, &menu]() { menu->clear(); auto configure = menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure...")); QObject::connect(configure, &QAction::triggered, configure, [](){ KCMultiDialog* dialog = new KCMultiDialog; dialog->addModule(QStringLiteral("kcm_kdeconnect")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); }); for (int i=0, count = model.rowCount(); iaddMenu(indicator); } const QStringList requests = iface.pairingRequests(); if (!requests.isEmpty()) { menu->addSection(i18n("Pairing requests")); for(const auto& req: requests) { DeviceDbusInterface* dev = new DeviceDbusInterface(req, menu); auto pairMenu = menu->addMenu(dev->name()); pairMenu->addAction(i18n("Pair"), dev, &DeviceDbusInterface::acceptPairing); pairMenu->addAction(i18n("Reject"), dev, &DeviceDbusInterface::rejectPairing); } } #ifdef Q_OS_MAC // Add quit menu menu->addAction(i18n("Quit"), [](){ QCoreApplication::quit(); // Close this application }); #endif }; QObject::connect(&iface, &DaemonDbusInterface::pairingRequestsChangedProxy, &model, refreshMenu); QObject::connect(&model, &DevicesModel::rowsInserted, &model, refreshMenu); QObject::connect(&model, &DevicesModel::rowsRemoved, &model, refreshMenu); #ifdef QSYSTRAY QSystemTrayIcon systray; systray.setIcon(QIcon::fromTheme("kdeconnectindicatordark")); systray.setVisible(true); systray.setToolTip("KDE Connect"); QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { systray.setToolTip(i18np("%1 device connected", "%1 devices connected", model.rowCount())); }); systray.setContextMenu(menu); #else KStatusNotifierItem systray; systray.setIconByName(QStringLiteral("kdeconnectindicatordark")); systray.setToolTip(QStringLiteral("kdeconnect"), QStringLiteral("KDE Connect"), QStringLiteral("KDE Connect")); systray.setCategory(KStatusNotifierItem::Communications); systray.setStatus(KStatusNotifierItem::Passive); systray.setStandardActionsEnabled(false); QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { const auto count = model.rowCount(); systray.setStatus(count == 0 ? KStatusNotifierItem::Passive : KStatusNotifierItem::Active); systray.setToolTip(QStringLiteral("kdeconnect"), QStringLiteral("KDE Connect"), i18np("%1 device connected", "%1 devices connected", count)); }); systray.setContextMenu(menu); #endif refreshMenu(); app.setQuitOnLastWindowClosed(false); return app.exec(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cf385223..8d8096b5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,43 +1,46 @@ find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Test) include_directories( ${KDEConnectCore_BINARY_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/plugins/sendnotifications/ ${CMAKE_BINARY_DIR}/smsapp/ ) set(kdeconnect_libraries kdeconnectcore KF5::I18n KF5::KIOWidgets Qt5::DBus Qt5::Network Qt5::Test qca-qt5 ) set(kdeconnect_sms_libraries kdeconnectsmshelper Qt5::Test ) ecm_add_test(pluginloadtest.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(sendfiletest.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(networkpackettests.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(testsocketlinereader.cpp TEST_NAME testsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(testsslsocketlinereader.cpp TEST_NAME testsslsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(kdeconnectconfigtest.cpp TEST_NAME kdeconnectconfigtest LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(lanlinkprovidertest.cpp TEST_NAME lanlinkprovidertest LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(devicetest.cpp TEST_NAME devicetest LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(testnotificationlistener.cpp testdevice.cpp ../plugins/sendnotifications/sendnotificationsplugin.cpp ../plugins/sendnotifications/notificationslistener.cpp ../plugins/sendnotifications/notifyingapplication.cpp TEST_NAME testnotificationlistener LINK_LIBRARIES ${kdeconnect_libraries} Qt5::DBus KF5::Notifications KF5::IconThemes) if(SMSAPP_ENABLED) ecm_add_test(testsmshelper.cpp LINK_LIBRARIES ${kdeconnect_sms_libraries}) endif() +if(PRIVATE_DBUS_ENABLED) + ecm_add_test(testprivatedbus.cpp LINK_LIBRARIES ${kdeconnect_libraries}) +endif() diff --git a/tests/testprivatedbus.cpp b/tests/testprivatedbus.cpp new file mode 100644 index 00000000..2834b80e --- /dev/null +++ b/tests/testprivatedbus.cpp @@ -0,0 +1,109 @@ +/** + * Copyright 2019 Weixuan XIAO + * + * 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 "dbushelper.h" + +#include +#include +#include + +/** + * This class tests the working of private dbus in kdeconnect + */ +class PrivateDBusTest : public QObject +{ + Q_OBJECT + +public: + PrivateDBusTest() + { + DbusHelper::launchDBusDaemon(); + } + + ~PrivateDBusTest() + { + DbusHelper::closeDBusDaemon(); + } + +private Q_SLOTS: + void testConnectionWithPrivateDBus(); + void testServiceRegistrationWithPrivateDBus(); + void testMethodCallWithPrivateDBus(); +}; + +/** + * Open private DBus normally and get connection info + */ +void PrivateDBusTest::testConnectionWithPrivateDBus() +{ + QDBusConnection conn = DbusHelper::sessionBus(); + + QVERIFY2(conn.isConnected(), "Connection not established"); + QVERIFY2(conn.name() == QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME), + "DBus Connection is not the right one"); +} + +/** + * Open private DBus connection normally and register a service + */ +void PrivateDBusTest::testServiceRegistrationWithPrivateDBus() +{ + QDBusConnection conn = DbusHelper::sessionBus(); + QVERIFY2(conn.isConnected(), "DBus not connected"); + + QDBusConnectionInterface *bus = conn.interface(); + QVERIFY2(bus != nullptr, "Failed to get DBus interface"); + + QVERIFY2(bus->registerService(QStringLiteral("privatedbus.test")) == QDBusConnectionInterface::ServiceRegistered, + "Failed to register DBus Serice"); + + bus->unregisterService(QStringLiteral("privatedbus.test")); +} + +/** + * Open private DBus connection normally, call a method and get its reply + */ +void PrivateDBusTest::testMethodCallWithPrivateDBus() +{ + QDBusConnection conn = DbusHelper::sessionBus(); + QVERIFY2(conn.isConnected(), "DBus not connected"); + + /* + dbus-send --session \ + --dest=org.freedesktop.DBus \ + --type=method_call \ + --print-reply \ + /org/freedesktop/DBus \ + org.freedesktop.DBus.ListNames + */ + QDBusMessage msg = conn.call( + QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.DBus"), // Service + QStringLiteral("/org/freedesktop/DBus"), // Path + QStringLiteral("org.freedesktop.DBus"), // Interface + QStringLiteral("ListNames") // Method + ) + ); + + QVERIFY2(msg.type() == QDBusMessage::ReplyMessage, "Failed calling method on private DBus"); +} + +QTEST_MAIN(PrivateDBusTest); +#include "testprivatedbus.moc"