Commit c466c5db authored by Andrius Štikonas's avatar Andrius Štikonas
Browse files

Port to polkit


Co-authored-by: David Edmundson's avatarDavid Edmundson <kde@davidedmundson.co.uk>
parent efb970ba
......@@ -50,6 +50,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"
......
......@@ -103,9 +103,9 @@ void Job::emitProgress(int i)
emit progress(i);
}
void Job::updateReport(const QVariantMap& reportString)
void Job::updateReport(const QString& report)
{
m_Report->line() << reportString[QStringLiteral("report")].toString();
m_Report->line() << report;
}
Report* Job::jobStarted(Report& parent)
......
......@@ -80,7 +80,7 @@ public:
}
void emitProgress(int i);
void updateReport(const QVariantMap& reportString);
void updateReport(const QString& report);
protected:
bool copyBlocks(Report& report, CopyTarget& target, CopySource& source);
......
......@@ -13,6 +13,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)
......@@ -47,11 +50,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
)
......@@ -54,12 +54,10 @@ struct ExternalCommandPrivate
int m_ExitCode;
QByteArray m_Output;
QByteArray m_Input;
DBusThread *m_thread;
QProcess::ProcessChannelMode processChannelMode;
};
KAuth::ExecuteJob* ExternalCommand::m_job;
bool ExternalCommand::helperStarted = false;
QWidget* ExternalCommand::parent;
......@@ -75,11 +73,6 @@ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, co
d->m_Args = args;
d->m_ExitCode = -1;
d->m_Output = QByteArray();
if (!helperStarted)
if(!startHelper())
Log(Log::Level::error) << xi18nc("@info:status", "Could not obtain administrator privileges.");
d->processChannelMode = processChannelMode;
}
......@@ -139,11 +132,14 @@ bool ExternalCommand::start(int timeout)
if (cmd.isEmpty())
cmd = QStandardPaths::findExecutable(command(), { QStringLiteral("/sbin/"), QStringLiteral("/usr/sbin/"), QStringLiteral("/usr/local/sbin/") });
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
if (!interface)
return false;
bool rval = false;
QDBusPendingCall pcall = interface->start(cmd, args(), d->m_Input, d->processChannelMode);
......@@ -181,14 +177,16 @@ bool ExternalCommand::copyBlocks(const CopySource& source, CopyTarget& target)
return false;
}
// TODO KF6:Use new signal-slot syntax
connect(m_job, SIGNAL(percent(KJob*, unsigned long)), this, SLOT(emitProgress(KJob*, unsigned long)));
connect(m_job, &KAuth::ExecuteJob::newData, this, &ExternalCommand::emitReport);
auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"),
QStringLiteral("/Helper"), QDBusConnection::systemBus(), this);
interface->setTimeout(10 * 24 * 3600 * 1000); // 10 days
if (!interface)
return false;
connect(interface, &OrgKdeKpmcoreExternalcommandInterface::progress, this, &ExternalCommand::progress);
connect(interface, &OrgKdeKpmcoreExternalcommandInterface::report, this, &ExternalCommand::reportSignal);
QDBusPendingCall pcall = interface->copyblocks(source.path(), source.firstByte(), source.length(),
target.path(), target.firstByte(), blockSize);
......@@ -229,7 +227,7 @@ bool ExternalCommand::writeData(Report& commandReport, const QByteArray& buffer,
return false;
}
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
......@@ -339,58 +337,6 @@ void ExternalCommand::setExitCode(int i)
d->m_ExitCode = i;
}
bool ExternalCommand::startHelper()
{
if (!QDBusConnection::systemBus().isConnected()) {
qWarning() << QDBusConnection::systemBus().lastError().message();
return false;
}
QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus());
if (iface.isValid()) {
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;
return true;
}
void ExternalCommand::stopHelper()
{
auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"),
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();
}
......@@ -41,15 +41,6 @@ class QDBusInterface;
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.
......@@ -102,15 +93,10 @@ public:
/**< @return pointer to the Report or nullptr */
Report* report();
void emitReport(const QVariantMap& report) { emit reportSignal(report); }
// KAuth
/**< start ExternalCommand Helper */
bool startHelper();
/**< stop ExternalCommand Helper */
static void stopHelper();
/**< Sets a parent widget for the authentication dialog.
* @param p parent widget
*/
......@@ -120,10 +106,7 @@ public:
Q_SIGNALS:
void progress(int);
void reportSignal(const QVariantMap&);
public Q_SLOTS:
void emitProgress(KJob*, unsigned long percent) { emit progress(percent); }
void reportSignal(const QString&);
private:
void setExitCode(int i);
......@@ -134,7 +117,6 @@ private:
// KAuth
static KAuth::ExecuteJob *m_job;
static bool helperStarted;
static QWidget *parent;
};
......
......@@ -19,6 +19,7 @@
#include "externalcommand_whitelist.h"
#include <QtDBus>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
......@@ -27,66 +28,45 @@
#include <QVariant>
#include <KLocalizedString>
#include <PolkitQt1/Authority>
#include <PolkitQt1/Subject>
#include <polkitqt1-version.h>
/** 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 a DBus service that we monitor for changes.
* This helper runs in the background until all applications using it exit.
* If helper is not busy then it exits when the client services gets
* unregistered. 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.
* unregistered. In case the client crashes, the helper waits
* for the current job to finish before exiting, to avoid leaving partially moved data.
*
* This helper also starts another DBus interface where it listens to
* command execution requests from the application that started the helper.
*
* This helper starts DBus interface where it listens to command execution requests.
* New clients connecting to the helper have to authenticate using Polkit.
*/
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 | QDBusConnection::ExportAllSignals)) {
::exit(-1);
}
m_loop = std::make_unique<QEventLoop>();
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();
});
m_loop->exec();
reply.addData(QStringLiteral("success"), true);
if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface"))) {
::exit(-1);
}
// 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();
// 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);
return reply;
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, qApp, [this](const QString &service) {
m_serviceWatcher->removeWatchedService(service);
if (m_serviceWatcher->watchedServices().isEmpty()) {
qApp->quit();
}
});
}
/** 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
......@@ -149,6 +129,9 @@ bool ExternalCommandHelper::writeData(const QString &targetDevice, const QByteAr
// 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;
......@@ -174,13 +157,10 @@ QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const
t.start();
QVariantMap report;
report[QStringLiteral("report")] = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy,
QString reportText = xi18nc("@info:progress", "Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5.", blocksToCopy,
sourceLength, readOffset, writeOffset, copyDirection == 1 ? i18nc("direction: left", "left")
: i18nc("direction: right", "right"));
HelperSupport::progressStep(report);
Q_EMIT report(reportText);
bool rval = true;
......@@ -199,10 +179,10 @@ QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const
if (percent % 5 == 0 && t.elapsed() > 1000) {
const qint64 mibsPerSec = (blocksCopied * blockSize / 1024 / 1024) / (t.elapsed() / 1000);
const qint64 estSecsLeft = (100 - percent) * t.elapsed() / percent / 1000;
report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString());
HelperSupport::progressStep(report);
reportText = xi18nc("@info:progress", "Copying %1 MiB/second, estimated time left: %2", mibsPerSec, QTime(0, 0).addSecs(estSecsLeft).toString());
Q_EMIT report(reportText);
}
HelperSupport::progressStep(percent);
Q_EMIT progress(percent);
}
}
......@@ -212,8 +192,8 @@ QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const
const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte;
const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte;
report[QStringLiteral("report")]= xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset);
HelperSupport::progressStep(report);
reportText = xi18nc("@info:progress", "Copying remainder of block size %1 from %2 to %3.", lastBlock, lastBlockReadOffset, lastBlockWriteOffset);
Q_EMIT report(reportText);
rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock);
if (rval) {
......@@ -224,13 +204,13 @@ QVariantMap ExternalCommandHelper::copyblocks(const QString& sourceDevice, const
}
if (rval) {
HelperSupport::progressStep(100);
Q_EMIT progress(100);
bytesWritten += buffer.size();
}
}
report[QStringLiteral("report")] = xi18ncp("@info:progress argument 2 is a string such as 7 bytes (localized accordingly)", "Copying 1 block (%2) finished.", "Copying %1 blocks (%2) finished.", blocksCopied, i18np("1 byte", "%1 bytes", bytesWritten));
HelperSupport::progressStep(report);
reportText = xi18ncp("@info:progress argument 2 is a string such as 7 bytes (localized accordingly)", "Copying 1 block (%2) finished.", "Copying %1 blocks (%2) finished.", blocksCopied, i18np("1 byte", "%1 bytes", bytesWritten));
Q_EMIT report(reportText);
reply[QStringLiteral("success")] = rval;
return reply;
......@@ -238,6 +218,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/") && !targetDevice.contains(QStringLiteral("/etc/fstab")))
return false;
......@@ -245,9 +228,11 @@ bool ExternalCommandHelper::writeData(const QByteArray& buffer, const QString& t
return writeData(targetDevice, buffer, targetFirstByte);
}
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;
......@@ -261,7 +246,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;
}
......@@ -283,10 +268,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()
......@@ -305,4 +290,52 @@ void ExternalCommandHelper::onReadOutput()
*report() << QString::fromLocal8Bit(s);*/
}
KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper)
bool ExternalCommandHelper::isCallerAuthorized()
{
if (!calledFromDBus()) {
return false;
}
// Cache successful authentication requests, so that clients don't need
// to authenticate multiple times during long partitioning operations.
if (m_serviceWatcher->watchedServices().contains(message().service())) {
return true;
}
PolkitQt1::SystemBusNameSubject subject(message().service());
PolkitQt1::Authority *authority = PolkitQt1::Authority::instance();
PolkitQt1::Authority::Result result;
QEventLoop e;
connect(authority, &PolkitQt1::Authority::checkAuthorizationFinished, &e, [&e, &result](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:
// track who called into us so we can close when all callers have gone away
m_serviceWatcher->addWatchedService(message().service());
return true;
default:
sendErrorReply(QDBusError::AccessDenied);
if (m_serviceWatcher->watchedServices().isEmpty())
qApp->quit();
return false;
}
}
int main(int argc, char ** argv)
{
QCoreApplication app(argc, argv);
ExternalCommandHelper helper;
app.exec();
}
......@@ -21,40 +21,40 @@
#include <memory>
#include <unordered_set>
#include <KAuth>
#include <QEventLoop>
#include <QString>
#include <QProcess>
#include <QDBusContext>
using namespace KAuth;
class QDBusServiceWatcher;
class ExternalCommandHelper : public QObject
class ExternalCommandHelper : public QObject, public QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.externalcommand")
Q_SIGNALS:
void progress(int);
void quit();
Q_SCRIPTABLE void progress(int);
Q_SCRIPTABLE void report(QString);
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);
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);
Q_SCRIPTABLE void exit();
private:
void onReadOutput();
std::unique_ptr<QEventLoop> m_loop;
bool isCallerAuthorized();
void onReadOutput();
QProcess m_cmd;
// QByteArray output;
QDBusServiceWatcher *m_serviceWatcher = nullptr;
};
#endif
......@@ -68,6 +68,7 @@ KAboutData aboutKPMcore()
aboutData.addCredit(xi18nc("@info:credit", "Pali Rohár"), i18nc("@info:credit", "UDF support"), QStringLiteral("pali.rohar@gmail.com"));
aboutData.addCredit(xi18nc("@info:credit", "Adriaan de Groot"), i18nc("@info:credit", "Calamares maintainer"), QStringLiteral("groot@kde.org"));
aboutData.addCredit(xi18nc("@info:credit", "Caio Jordão Carvalho"), i18nc("@info:credit", "Improved SMART support"), QStringLiteral("caiojcarvalho@gmail.com"));
aboutData.addCredit(xi18nc("@info:credit", "David Edmundson"), i18nc("@info:credit", "Port from KAuth to Polkit"), QStringLiteral("kde@davidedmundson.co.uk"));
return aboutData;
}
......@@ -9,7 +9,7 @@
</policy>
<policy context="default">
<allow send_destination="org.kde.kpmcore.externalcommand"
<allow send_destination="org.kde.kpmcore.helperinterface"
send_interface="org.kde.kpmcore.externalcommand"/>
</policy>
......
# SPDX-FileCopyrightText: 2020 David Edmundson <kde@davidedmundson.co.uk>
# SPDX-License-Identifier: GPL-3.0-or-later
[D-BUS Service]
Name=org.kde.kpmcore.helperinterface
Exec=@KDE_INSTALL_FULL_LIBEXECDIR@/kpmcore_externalcommand
User=root
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment