diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 6e3abb2..4d1fd5a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -34,6 +34,7 @@ target_link_libraries(kpmcore_externalcommand install(TARGETS kpmcore_externalcommand DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) install( FILES util/org.kde.kpmcore.helperinterface.conf DESTINATION ${SYSCONF_INSTALL_DIR}/dbus-1/system.d ) +install( FILES util/org.kde.kpmcore.applicationinterface.conf DESTINATION ${SYSCONF_INSTALL_DIR}/dbus-1/system.d ) kauth_install_helper_files(kpmcore_externalcommand org.kde.kpmcore.externalcommand root) kauth_install_actions(org.kde.kpmcore.externalcommand util/org.kde.kpmcore.externalcommand.actions) diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index a165913..716e43b 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -22,10 +22,12 @@ #include "core/copytarget.h" #include "core/copysourcedevice.h" #include "core/copytargetdevice.h" +#include "util/globallog.h" #include "util/externalcommand.h" #include "util/report.h" #include +#include #include #include #include @@ -51,6 +53,7 @@ struct ExternalCommandPrivate int m_ExitCode; QByteArray m_Output; QByteArray m_Input; + DBusThread *m_thread; }; unsigned int ExternalCommand::counter = 0; @@ -75,7 +78,8 @@ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, co d->m_Output = QByteArray(); if (!helperStarted) - startHelper(); + if(!startHelper()) + Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges."); setup(processChannelMode); } @@ -346,6 +350,13 @@ void ExternalCommand::setExitCode(int i) bool ExternalCommand::startHelper() { + if (!QDBusConnection::systemBus().isConnected()) { + qWarning() << "Could not connect to DBus session bus"; + return false; + } + d->m_thread = new DBusThread; + d->m_thread->start(); + init = new QCA::Initializer; // Generate RSA key pair for signing external command requests if (!QCA::isSupported("pkey") || !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA)) { @@ -402,3 +413,18 @@ void ExternalCommand::stopHelper() delete privateKey; delete init; } + +void DBusThread::run() +{ + if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface"))) { + qWarning() << QDBusConnection::systemBus().lastError().message(); + return; + } + if (!QDBusConnection::systemBus().registerObject(QStringLiteral("/Application"), this, QDBusConnection::ExportAllSlots)) { + qWarning() << QDBusConnection::systemBus().lastError().message(); + return; + } + + QEventLoop loop; + loop.exec(); +} diff --git a/src/util/externalcommand.h b/src/util/externalcommand.h index b260b10..069640b 100644 --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,16 @@ class CopySource; class CopyTarget; struct ExternalCommandPrivate; +class DBusThread : public QThread +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.ping") + void run() override; + +public Q_SLOTS: + Q_SCRIPTABLE void ping() {return;}; +}; + /** An external command. Runs an external command as a child process. @@ -94,7 +105,7 @@ public: // KAuth /**< start ExternalCommand Helper */ - static bool startHelper(); + bool startHelper(); /**< stop ExternalCommand Helper */ static void stopHelper(); diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp index 123e300..61f0212 100644 --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -27,6 +27,18 @@ #include /** Initialize ExternalCommandHelper Daemon and prepare DBus interface + * + * KAuth helper runs in the background until application exits. + * To avoid forever running helper in case of application crash + * ExternalCommand class opens DBus interface that we ping. + * If helper is not busy than it exits when ping fails. Otherwise, + * we wait for the current job to finish before exiting, so even in case + * of main application crash, we do not leave partially moved data. + * + * This helper also starts another DBus interface where it listens to + * command execution requests from the application that started the helper. + * These requests are validated using public key cryptography, to prevent + * other unprivileged applications from gaining root privileges. */ ActionReply ExternalCommandHelper::init(const QVariantMap& args) { @@ -36,17 +48,42 @@ ActionReply ExternalCommandHelper::init(const QVariantMap& args) reply.addData(QStringLiteral("success"), false); return reply; } - m_publicKey = QCA::PublicKey::fromDER(args[QStringLiteral("pubkey")].toByteArray()); - m_Counter = 0; if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface"))) { qWarning() << QDBusConnection::systemBus().lastError().message(); reply.addData(QStringLiteral("success"), false); return reply; } - QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots); + if (!QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { + qWarning() << QDBusConnection::systemBus().lastError().message(); + reply.addData(QStringLiteral("success"), false); + return reply; + } + + m_publicKey = QCA::PublicKey::fromDER(args[QStringLiteral("pubkey")].toByteArray()); + m_Counter = 0; HelperSupport::progressStep(QVariantMap()); + auto timeout = [this] () { + QDBusInterface iface(QStringLiteral("org.kde.kpmcore.applicationinterface"), + QStringLiteral("/Application"), + QStringLiteral("org.kde.kpmcore.ping"), + QDBusConnection::systemBus()); + iface.setTimeout(5000); // 5 seconds; + auto pcall = iface.asyncCall(QStringLiteral("ping")); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { + if (watcher->isError()) { + qWarning() << watcher->error(); + m_loop.exit(); + } + }; + connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); + }; + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, timeout); + timer->start(5000); // 5 seconds m_loop.exec(); reply.addData(QStringLiteral("success"), true); diff --git a/src/util/org.kde.kpmcore.applicationinterface.conf b/src/util/org.kde.kpmcore.applicationinterface.conf new file mode 100644 index 0000000..59113ff --- /dev/null +++ b/src/util/org.kde.kpmcore.applicationinterface.conf @@ -0,0 +1,14 @@ + + + + + + + + + + +