diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index ceb4848..86e10f3 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -1,443 +1,437 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * * * * 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 3 of * * the License, or (at your option) any later version. * * * * 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 "backend/corebackendmanager.h" #include "core/device.h" #include "core/copysource.h" #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 #include #include #include #include #include #include #include #include #include #include #include struct ExternalCommandPrivate { Report *m_Report; QString m_Command; QStringList m_Args; int m_ExitCode; QByteArray m_Output; QByteArray m_Input; DBusThread *m_thread; QProcess::ProcessChannelMode processChannelMode; }; KAuth::ExecuteJob* ExternalCommand::m_job; QCA::PrivateKey* ExternalCommand::privateKey; QCA::Initializer* ExternalCommand::init; bool ExternalCommand::helperStarted = false; QWidget* ExternalCommand::parent; /** Creates a new ExternalCommand instance without Report. @param cmd the command to run @param args the arguments to pass to the command */ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) : d(std::make_unique()) { d->m_Report = nullptr; d->m_Command = cmd; 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; } /** Creates a new ExternalCommand instance with Report. @param report the Report to write output to. @param cmd the command to run @param args the arguments to pass to the command */ ExternalCommand::ExternalCommand(Report& report, const QString& cmd, const QStringList& args, const QProcess::ProcessChannelMode processChannelMode) : d(std::make_unique()) { d->m_Report = report.newChild(); d->m_Command = cmd; d->m_Args = args; d->m_ExitCode = -1; d->m_Output = QByteArray(); d->processChannelMode = processChannelMode; } ExternalCommand::~ExternalCommand() { } // void ExternalCommand::setup() // { // connect(this, qOverload(&QProcess::finished), this, &ExternalCommand::onFinished); // connect(this, &ExternalCommand::readyReadStandardOutput, this, &ExternalCommand::onReadOutput); // } /** Executes the external command. @param timeout timeout to wait for the process to start @return true on success */ bool ExternalCommand::start(int timeout) { Q_UNUSED(timeout) if (report()) { report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); } QString cmd = QStandardPaths::findExecutable(command()); if (cmd.isEmpty()) cmd = QStandardPaths::findExecutable(command(), { QStringLiteral("/sbin/"), QStringLiteral("/usr/sbin/"), QStringLiteral("/usr/local/sbin/") }); if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus system bus"; return false; } QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); iface.setTimeout(10 * 24 * 3600 * 1000); // 10 days bool rval = false; if (iface.isValid()) { QByteArray request; const quint64 nonce = getNonce(iface); request.setNum(nonce); request.append(cmd.toUtf8()); for (const auto &argument : qAsConst(d->m_Args)) request.append(argument.toUtf8()); request.append(d->m_Input); request.append(d->processChannelMode); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); QDBusPendingCall pcall = iface.asyncCall(QStringLiteral("start"), privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce, cmd, args(), d->m_Input, d->processChannelMode); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QEventLoop loop; auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { loop.exit(); if (watcher->isError()) qWarning() << watcher->error(); else { QDBusPendingReply reply = *watcher; d->m_Output = reply.value()[QStringLiteral("output")].toByteArray(); setExitCode(reply.value()[QStringLiteral("exitCode")].toInt()); rval = reply.value()[QStringLiteral("success")].toBool(); } }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); } return rval; } bool ExternalCommand::copyBlocks(CopySource& source, CopyTarget& target) { bool rval = true; const qint64 blockSize = 10 * 1024 * 1024; // number of bytes per block to copy if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus system bus"; 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); QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); iface.setTimeout(10 * 24 * 3600 * 1000); // 10 days if (iface.isValid()) { QByteArray request; const quint64 nonce = getNonce(iface); request.setNum(nonce); request.append(source.path().toUtf8()); request.append(QByteArray::number(source.firstByte())); request.append(QByteArray::number(source.length())); request.append(target.path().toUtf8()); request.append(QByteArray::number(target.firstByte())); request.append(QByteArray::number(blockSize)); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); // Use asynchronous DBus calls, so that we can process reports and progress QDBusPendingCall pcall = iface.asyncCall(QStringLiteral("copyblocks"), privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce, source.path(), source.firstByte(), source.length(), target.path(), target.firstByte(), blockSize); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QEventLoop loop; auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { loop.exit(); if (watcher->isError()) { qWarning() << watcher->error(); } else { QDBusPendingReply reply = *watcher; rval = reply.argumentAt<0>(); } setExitCode(!rval); }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); } return rval; } bool ExternalCommand::write(const QByteArray& input) { d->m_Input = input; return true; } /** Runs the command. @param timeout timeout to use for waiting when starting and when waiting for the process to finish @return true on success */ bool ExternalCommand::run(int timeout) { return start(timeout) /* && exitStatus() == 0*/; } void ExternalCommand::onReadOutput() { // const QByteArray s = readAllStandardOutput(); // // if(m_Output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems // if (report()) // report()->line() << xi18nc("@info:status", "(Command is printing too much output)"); // return; // } // // m_Output += s; // // if (report()) // *report() << QString::fromLocal8Bit(s); } -void ExternalCommand::onFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - Q_UNUSED(exitStatus) - setExitCode(exitCode); -} - void ExternalCommand::setCommand(const QString& cmd) { d->m_Command = cmd; } const QString& ExternalCommand::command() const { return d->m_Command; } const QStringList& ExternalCommand::args() const { return d->m_Args; } void ExternalCommand::addArg(const QString& s) { d->m_Args << s; } void ExternalCommand::setArgs(const QStringList& args) { d->m_Args = args; } int ExternalCommand::exitCode() const { return d->m_ExitCode; } const QString ExternalCommand::output() const { return QString::fromLocal8Bit(d->m_Output); } const QByteArray& ExternalCommand::rawOutput() const { return d->m_Output; } Report* ExternalCommand::report() { return d->m_Report; } void ExternalCommand::setExitCode(int i) { d->m_ExitCode = i; } bool ExternalCommand::startHelper() { if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus session bus"; 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(); init = new QCA::Initializer; // Generate RSA key pair for signing external command requests if (!QCA::isSupported("pkey") || !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA)) { qCritical() << xi18n("QCA does not support RSA."); return false; } privateKey = new QCA::PrivateKey; *privateKey = QCA::KeyGenerator().createRSA(4096); if(privateKey->isNull()) { qCritical() << xi18n("Failed to make private RSA key."); return false; } if (!privateKey->canSign()) { qCritical() << xi18n("Generated key cannot be used for signatures."); return false; } QCA::PublicKey pubkey = privateKey->toPublicKey(); 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; arguments.insert(QStringLiteral("pubkey"), pubkey.toDER()); 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() { QDBusInterface iface(QStringLiteral("org.kde.kpmcore.helperinterface"), QStringLiteral("/Helper"), QStringLiteral("org.kde.kpmcore.externalcommand"), QDBusConnection::systemBus()); if (iface.isValid()) { QByteArray request; const quint64 nonce = getNonce(iface); request.setNum(nonce); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); iface.call(QStringLiteral("exit"), privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce); } delete privateKey; delete init; } quint64 ExternalCommand::getNonce(QDBusInterface& iface) { QDBusPendingCall pcall = iface.asyncCall(QStringLiteral("getNonce")); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall); QEventLoop loop; unsigned long long rval = 0; auto exitLoop = [&] (QDBusPendingCallWatcher *watcher) { loop.exit(); if (watcher->isError()) qWarning() << watcher->error(); else { QDBusPendingReply reply = *watcher; rval = reply; } }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); return rval; } 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 85577be..14b7c2f 100644 --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -1,146 +1,145 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * Copyright (C) 2016-2018 by Andrius Štikonas * * * * 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 3 of * * the License, or (at your option) any later version. * * * * 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 KPMCORE_EXTERNALCOMMAND_H #define KPMCORE_EXTERNALCOMMAND_H #include "util/libpartitionmanagerexport.h" #include #include #include #include #include #include #include #include class KJob; namespace KAuth { class ExecuteJob; } namespace QCA { class PrivateKey; class Initializer; } class Report; class CopySource; class CopyTarget; class QDBusInterface; 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. @author Volker Lanz @author Andrius Štikonas */ class LIBKPMCORE_EXPORT ExternalCommand : public QObject { Q_OBJECT Q_DISABLE_COPY(ExternalCommand) public: explicit ExternalCommand(const QString& cmd = QString(), const QStringList& args = QStringList(), const QProcess::ProcessChannelMode processChannelMode = QProcess::MergedChannels); explicit ExternalCommand(Report& report, const QString& cmd = QString(), const QStringList& args = QStringList(), const QProcess::ProcessChannelMode processChannelMode = QProcess::MergedChannels); ~ExternalCommand(); public: bool copyBlocks(CopySource& source, CopyTarget& target); /**< @param cmd the command to run */ void setCommand(const QString& cmd); /**< @return the command to run */ const QString& command() const; /**< @return the arguments */ const QStringList& args() const; /**< @param s the argument to add */ void addArg(const QString& s); /**< @param args the new arguments */ void setArgs(const QStringList& args); bool write(const QByteArray& input); /**< @param input the input for the program */ bool startCopyBlocks(); bool start(int timeout = 30000); bool run(int timeout = 30000); /**< @return the exit code */ int exitCode() const; /**< @return the command output */ const QString output() const; /**< @return the command output */ const QByteArray& rawOutput() const; /**< @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 */ static void setParentWidget(QWidget *p) { parent = p; } Q_SIGNALS: void progress(int); void reportSignal(const QVariantMap&); public Q_SLOTS: void emitProgress(KJob*, unsigned long percent) { emit progress(percent); }; private: void setExitCode(int i); - void onFinished(int exitCode, QProcess::ExitStatus exitStatus); void onReadOutput(); static quint64 getNonce(QDBusInterface& iface); private: std::unique_ptr d; // KAuth static quint64 m_Nonce; static KAuth::ExecuteJob *m_job; static QCA::Initializer *init; static QCA::PrivateKey *privateKey; static bool helperStarted; static QWidget *parent; }; #endif