diff --git a/CMakeLists.txt b/CMakeLists.txt index 4408e97..48537de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ include(KDECompilerSettings NO_POLICY_SCOPE) include(FeatureSummary) include(GenerateExportHeader) include(ECMSetupVersion) +include(ECMConfiguredInstall) ecm_setup_version(${VERSION} VARIABLE_PREFIX KPMCORE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kpmcore_version.h" diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index fab7a70..cb45508 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -23,6 +23,9 @@ qt5_generate_dbus_interface( OPTIONS -a ) + +find_package(PolkitQt5-1 REQUIRED) + qt5_add_dbus_interface(ApplicationInterface_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${application_interface_xml} externalcommand_interface) qt5_add_dbus_interface(HelperInterface_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${helper_interface_xml} externalcommandhelper_interface) @@ -57,11 +60,15 @@ target_link_libraries(kpmcore_externalcommand Qt5::DBus KF5::AuthCore KF5::I18n + PolkitQt5-1::Core ) -install(TARGETS kpmcore_externalcommand DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) +install(TARGETS kpmcore_externalcommand DESTINATION ${KDE_INSTALL_LIBEXECDIR}) install( FILES util/org.kde.kpmcore.helperinterface.conf DESTINATION ${KDE_INSTALL_DBUSDIR}/system.d ) install( FILES util/org.kde.kpmcore.applicationinterface.conf DESTINATION ${KDE_INSTALL_DBUSDIR}/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) +ecm_install_configured_files( + INPUT util/org.kde.kpmcore.helperinterface.service.in + DESTINATION ${KDE_INSTALL_DBUSDIR}/system-services +) diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index ef3de03..99170a1 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -49,7 +49,6 @@ struct ExternalCommandPrivate int m_ExitCode; QByteArray m_Output; QByteArray m_Input; - DBusThread *m_thread; QProcess::ProcessChannelMode processChannelMode; }; @@ -71,9 +70,9 @@ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, co d->m_ExitCode = -1; d->m_Output = QByteArray(); - if (!helperStarted) - if(!startHelper()) - Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges."); +// if (!helperStarted) +// if(!startHelper()) +// Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges."); d->processChannelMode = processChannelMode; } @@ -129,6 +128,8 @@ bool ExternalCommand::start(int timeout) if (cmd.isEmpty()) cmd = QStandardPaths::findExecutable(command(), { QStringLiteral("/sbin/"), QStringLiteral("/usr/sbin/"), QStringLiteral("/usr/local/sbin/") }); + qDebug() << "start"; + auto interface = helperInterface(); if (!interface) return false; @@ -231,7 +232,7 @@ OrgKdeKpmcoreExternalcommandInterface* ExternalCommand::helperInterface() return nullptr; } - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QDBusConnection::systemBus(), this); interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days return interface; @@ -355,46 +356,14 @@ bool ExternalCommand::startHelper() exit(0); } - d->m_thread = new DBusThread; - d->m_thread->start(); - - KAuth::Action action = KAuth::Action(QStringLiteral("org.kde.kpmcore.externalcommand.init")); - action.setHelperId(QStringLiteral("org.kde.kpmcore.externalcommand")); - action.setTimeout(10 * 24 * 3600 * 1000); // 10 days - action.setParentWidget(parent); - QVariantMap arguments; - action.setArguments(arguments); - m_job = action.execute(); - m_job->start(); - - // Wait until ExternalCommand Helper is ready (helper sends newData signal just before it enters event loop) - QEventLoop loop; - auto exitLoop = [&] () { loop.exit(); }; - auto conn = QObject::connect(m_job, &KAuth::ExecuteJob::newData, exitLoop); - QObject::connect(m_job, &KJob::finished, [=] () { if(m_job->error()) exitLoop(); } ); - loop.exec(); - QObject::disconnect(conn); - - helperStarted = true; + qDebug() <<"starting helper"; return true; } void ExternalCommand::stopHelper() { - auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), + auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QDBusConnection::systemBus()); interface->exit(); } - -void DBusThread::run() -{ - if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.applicationinterface")) || - !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 8bbdaa4..02b5537 100644 --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -36,15 +36,6 @@ class OrgKdeKpmcoreExternalcommandInterface; struct ExternalCommandPrivate; -class DBusThread : public QThread -{ - Q_OBJECT - // We register on DBus so the helper can monitor us and terminate if we - // terminate. - Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.applicationinterface") - void run() override; -}; - /** An external command. Runs an external command as a child process. diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp index b84fc7c..fe445ae 100644 --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -13,6 +13,7 @@ #include "externalcommand_whitelist.h" #include + #include #include #include @@ -21,6 +22,10 @@ #include #include +#include +#include + +#include /** Initialize ExternalCommandHelper Daemon and prepare DBus interface * @@ -35,52 +40,34 @@ * This helper also starts another DBus interface where it listens to * command execution requests from the application that started the helper. * + * + * DAVE - this all needs updating + * */ -ActionReply ExternalCommandHelper::init(const QVariantMap& args) + +ExternalCommandHelper::ExternalCommandHelper() { - Q_UNUSED(args) - - ActionReply reply; - - if (!QDBusConnection::systemBus().isConnected() || !QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface")) || - !QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { - qWarning() << QDBusConnection::systemBus().lastError().message(); - reply.addData(QStringLiteral("success"), false); - - // Also end the application loop started by KAuth's main() code. Our loop - // exits when our client disappears. Without client we have no reason to - // live. - qApp->quit(); - - return reply; + if (!QDBusConnection::systemBus().registerObject(QStringLiteral("/Helper"), this, QDBusConnection::ExportAllSlots)) { + ::exit(-1); } - - m_loop = std::make_unique(); - HelperSupport::progressStep(QVariantMap()); - // End the loop and return only once the client is done using us. - auto serviceWatcher = - new QDBusServiceWatcher(QStringLiteral("org.kde.kpmcore.applicationinterface"), - QDBusConnection::systemBus(), - QDBusServiceWatcher::WatchForUnregistration, - this); - connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, - [this]() { - m_loop->exit(); + if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface"))) { + ::exit(-1); + } + + // we know this service must be registered already as DBus policy blocks calls from anyone else + m_serviceWatcher = new QDBusServiceWatcher(this); + m_serviceWatcher->setConnection(QDBusConnection ::systemBus()); + m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, qApp, [this](const QString &service) { + m_serviceWatcher->removeWatchedService(service); + if (m_serviceWatcher->watchedServices().isEmpty()) { + qApp->quit(); + } }); - - m_loop->exec(); - reply.addData(QStringLiteral("success"), true); - - // Also end the application loop started by KAuth's main() code. Our loop - // exits when our client disappears. Without client we have no reason to - // live. - qApp->quit(); - - return reply; } - /** Reads the given number of bytes from the sourceDevice into the given buffer. @param sourceDevice device or file to read from @param buffer buffer to store the bytes read in @@ -148,6 +135,9 @@ bool ExternalCommandHelper::writeData(const QString &targetDevice, const QByteAr */ bool ExternalCommandHelper::createFile(const QString &filePath, const QByteArray& fileContents) { + if (!isCallerAuthorized()) { + return false; + } QFile device(filePath); auto flags = QIODevice::WriteOnly | QIODevice::Unbuffered; @@ -167,6 +157,9 @@ bool ExternalCommandHelper::createFile(const QString &filePath, const QByteArray // If targetDevice is empty then return QByteArray with data that was read from disk. QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) { + if (!isCallerAuthorized()) { + return QVariantMap(); + } QVariantMap reply; reply[QStringLiteral("success")] = true; @@ -256,6 +249,9 @@ QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte) { + if (!isCallerAuthorized()) { + return false; + } // Do not allow using this helper for writing to arbitrary location if ( targetDevice.left(5) != QStringLiteral("/dev/") ) return false; @@ -265,6 +261,9 @@ bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& t bool ExternalCommandHelper::createFile(const QByteArray& fileContents, const QString& filePath) { + if (!isCallerAuthorized()) { + return false; + } // Do not allow using this helper for writing to arbitrary location if ( !filePath.contains(QStringLiteral("/etc/fstab")) ) return false; @@ -274,6 +273,9 @@ bool ExternalCommandHelper::createFile(const QByteArray& fileContents, const QSt QVariantMap ExternalCommandHelper::start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode) { + if (!isCallerAuthorized()) { + return QVariantMap(); + } QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); QVariantMap reply; reply[QStringLiteral("success")] = true; @@ -287,7 +289,7 @@ QVariantMap ExternalCommandHelper::start(const QString& command, const QStringLi QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { qInfo() << command <<" command is not one of the whitelisted command"; - m_loop->exit(); + qApp->quit(); reply[QStringLiteral("success")] = false; return reply; } @@ -309,10 +311,10 @@ QVariantMap ExternalCommandHelper::start(const QString& command, const QStringLi void ExternalCommandHelper::exit() { - m_loop->exit(); - - QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); - QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); + if (!isCallerAuthorized()) { + return; + } + qApp->quit(); } void ExternalCommandHelper::onReadOutput() @@ -331,4 +333,48 @@ void ExternalCommandHelper::onReadOutput() *report() << QString::fromLocal8Bit(s);*/ } -KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper) +bool ExternalCommandHelper::isCallerAuthorized() +{ + if (!calledFromDBus()) { + return false; + } + + // track who called into us so we can close when all callers have gone away + // this has to happen before authorisation as anyone could have activated us + m_serviceWatcher->addWatchedService(message().service()); + + PolkitQt1::SystemBusNameSubject subject(message().service()); + PolkitQt1::Authority *authority = PolkitQt1::Authority::instance(); + + PolkitQt1::Authority::Result result; + QEventLoop e; + connect(authority, &PolkitQt1::Authority::checkAuthorizationFinished, [&result, &e](PolkitQt1::Authority::Result _result) { + result = _result; + e.quit(); + }); + + authority->checkAuthorization(QStringLiteral("org.kde.kpmcore.externalcommand.init"), subject, PolkitQt1::Authority::AllowUserInteraction); + e.exec(); + + if (authority->hasError()) { + qDebug() << "Encountered error while checking authorization, error code:" << authority->lastError() << authority->errorDetails(); + authority->clearError(); + } + + switch (result) { + case PolkitQt1::Authority::Yes: + return true; + default: + sendErrorReply(QDBusError::AccessDenied); + return false; + } +} + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + ExternalCommandHelper helper; + app.exec(); +} + +#include "externalcommandhelper.moc" diff --git a/src/util/externalcommandhelper.h b/src/util/externalcommandhelper.h index b910cf1..6a452b6 100644 --- a/src/util/externalcommandhelper.h +++ b/src/util/externalcommandhelper.h @@ -18,10 +18,13 @@ #include #include #include +#include + +class QDBusServiceWatcher; using namespace KAuth; -class ExternalCommandHelper : public QObject +class ExternalCommandHelper : public QObject, public QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.externalcommand") @@ -31,12 +34,12 @@ Q_SIGNALS: void quit(); public: + ExternalCommandHelper(); bool readData(const QString& sourceDevice, QByteArray& buffer, const qint64 offset, const qint64 size); bool writeData(const QString& targetDevice, const QByteArray& buffer, const qint64 offset); bool createFile(const QString& filePath, const QByteArray& fileContents); public Q_SLOTS: - ActionReply init(const QVariantMap& args); Q_SCRIPTABLE QVariantMap start(const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode); Q_SCRIPTABLE QVariantMap copyblocks(const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize); Q_SCRIPTABLE bool writeData(const QByteArray& buffer, const QString& targetDevice, const qint64 targetFirstByte); @@ -44,11 +47,12 @@ public Q_SLOTS: Q_SCRIPTABLE void exit(); private: - void onReadOutput(); - std::unique_ptr m_loop; + bool isCallerAuthorized(); + + void onReadOutput(); QProcess m_cmd; -// QByteArray output; + QDBusServiceWatcher *m_serviceWatcher = nullptr; }; #endif diff --git a/src/util/org.kde.kpmcore.helperinterface.conf b/src/util/org.kde.kpmcore.helperinterface.conf index 990dadf..069015e 100644 --- a/src/util/org.kde.kpmcore.helperinterface.conf +++ b/src/util/org.kde.kpmcore.helperinterface.conf @@ -9,7 +9,7 @@ - diff --git a/src/util/org.kde.kpmcore.helperinterface.service.in b/src/util/org.kde.kpmcore.helperinterface.service.in new file mode 100644 index 0000000..e448fe1 --- /dev/null +++ b/src/util/org.kde.kpmcore.helperinterface.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.kde.kpmcore.helperinterface +Exec=@KDE_INSTALL_FULL_LIBEXECDIR@/kpmcore_externalcommand +User=root