diff --git a/src/fs/fat12.cpp b/src/fs/fat12.cpp index 8124c4a..dbad427 100644 --- a/src/fs/fat12.cpp +++ b/src/fs/fat12.cpp @@ -1,173 +1,171 @@ /************************************************************************* * Copyright (C) 2008,2009,2011 by Volker Lanz * * Copyright (C) 2017 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 "fs/fat12.h" #include "util/externalcommand.h" #include "util/capacity.h" #include "util/report.h" #include #include #include #include #include #include namespace FS { FileSystem::CommandSupportType fat12::m_GetUsed = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_GetLabel = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_SetLabel = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Create = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Grow = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Shrink = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Move = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Check = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Copy = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_Backup = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_UpdateUUID = FileSystem::cmdSupportNone; FileSystem::CommandSupportType fat12::m_GetUUID = FileSystem::cmdSupportNone; fat12::fat12(qint64 firstsector, qint64 lastsector, qint64 sectorsused, const QString& label, FileSystem::Type t) : FileSystem(firstsector, lastsector, sectorsused, label, t) { } void fat12::init() { m_Create = m_GetUsed = m_Check = findExternal(QStringLiteral("mkfs.fat"), {}, 1) ? cmdSupportFileSystem : cmdSupportNone; m_GetLabel = cmdSupportCore; m_SetLabel = findExternal(QStringLiteral("fatlabel")) ? cmdSupportFileSystem : cmdSupportNone; m_Move = cmdSupportCore; m_Copy = cmdSupportCore; m_Backup = cmdSupportCore; m_UpdateUUID = findExternal(QStringLiteral("dd")) ? cmdSupportFileSystem : cmdSupportNone; m_GetUUID = cmdSupportCore; } bool fat12::supportToolFound() const { return m_GetUsed != cmdSupportNone && m_GetLabel != cmdSupportNone && m_SetLabel != cmdSupportNone && m_Create != cmdSupportNone && m_Check != cmdSupportNone && m_UpdateUUID != cmdSupportNone && m_Copy != cmdSupportNone && m_Move != cmdSupportNone && m_Backup != cmdSupportNone && m_GetUUID != cmdSupportNone; } FileSystem::SupportTool fat12::supportToolName() const { // also, dd for updating the UUID, but let's assume it's there ;-) return SupportTool(QStringLiteral("dosfstools"), QUrl(QStringLiteral("http://www.daniel-baumann.ch/software/dosfstools/"))); } qint64 fat12::minCapacity() const { return 1 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::MiB); } qint64 fat12::maxCapacity() const { return 255 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::MiB); } int fat12::maxLabelLength() const { return 11; } QValidator* fat12::labelValidator(QObject *parent) const { QRegularExpressionValidator *m_LabelValidator = new QRegularExpressionValidator(parent); m_LabelValidator->setRegularExpression(QRegularExpression(QStringLiteral(R"(^[^\x{0000}-\x{001F}\x{007F}-\x{FFFF}*?.,;:\/\\|+=<>\[\]"]*$)"))); return m_LabelValidator; } qint64 fat12::readUsedCapacity(const QString& deviceNode) const { ExternalCommand cmd(QStringLiteral("fsck.fat"), { QStringLiteral("-n"), QStringLiteral("-v"), deviceNode }); // Exit code 1 is returned when FAT dirty bit is set if (cmd.run(-1) && (cmd.exitCode() == 0 || cmd.exitCode() == 1)) { qint64 usedClusters = -1; QRegularExpression re(QStringLiteral("files, (\\d+)/\\d+ ")); QRegularExpressionMatch reClusters = re.match(cmd.output()); if (reClusters.hasMatch()) usedClusters = reClusters.captured(1).toLongLong(); qint64 clusterSize = -1; re.setPattern(QStringLiteral("(\\d+) bytes per cluster")); QRegularExpressionMatch reClusterSize = re.match(cmd.output()); if (reClusterSize.hasMatch()) clusterSize = reClusterSize.captured(1).toLongLong(); if (usedClusters > -1 && clusterSize > -1) return usedClusters * clusterSize; } return -1; } bool fat12::writeLabel(Report& report, const QString& deviceNode, const QString& newLabel) { report.line() << xi18nc("@info:progress", "Setting label for partition %1 to %2", deviceNode, newLabel.toUpper()); ExternalCommand cmd(report, QStringLiteral("fatlabel"), { deviceNode, newLabel.toUpper() }); return cmd.run(-1) && cmd.exitCode() == 0; } bool fat12::check(Report& report, const QString& deviceNode) const { ExternalCommand cmd(report, QStringLiteral("fsck.fat"), { QStringLiteral("-a"), QStringLiteral("-w"), QStringLiteral("-v"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool fat12::create(Report& report, const QString& deviceNode) { ExternalCommand cmd(report, QStringLiteral("mkfs.fat"), { QStringLiteral("-F12"), QStringLiteral("-I"), QStringLiteral("-v"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool fat12::updateUUID(Report& report, const QString& deviceNode) const { - qint64 t = time(nullptr); + long int t = time(nullptr); char uuid[4]; for (auto &u : uuid) { u = static_cast(t & 0xff); t >>= 8; } - ExternalCommand cmd(report, QStringLiteral("dd"), { QStringLiteral("of=") + deviceNode , QStringLiteral("bs=1"), QStringLiteral("count=4"), QStringLiteral("seek=39") }); - - cmd.write(QByteArray(uuid, sizeof(uuid))); - return cmd.start(); + ExternalCommand cmd; + return cmd.writeData(report, QByteArray(uuid, sizeof(uuid)), deviceNode, 39); } } diff --git a/src/fs/fat32.cpp b/src/fs/fat32.cpp index b54e4fa..2bb6b34 100644 --- a/src/fs/fat32.cpp +++ b/src/fs/fat32.cpp @@ -1,65 +1,64 @@ /************************************************************************* * Copyright (C) 2008 by Volker Lanz * * * * 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 "fs/fat32.h" #include "util/externalcommand.h" #include "util/capacity.h" #include #include namespace FS { fat32::fat32(qint64 firstsector, qint64 lastsector, qint64 sectorsused, const QString& label) : fat16(firstsector, lastsector, sectorsused, label, FileSystem::Type::Fat32) { } qint64 fat32::minCapacity() const { return 32 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::MiB); } qint64 fat32::maxCapacity() const { return 16 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::TiB) - Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::MiB); } bool fat32::create(Report& report, const QString& deviceNode) { ExternalCommand cmd(report, QStringLiteral("mkfs.fat"), { QStringLiteral("-F32"), QStringLiteral("-I"), QStringLiteral("-v"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool fat32::updateUUID(Report& report, const QString& deviceNode) const { - qint64 t = time(nullptr); + // HACK: replace this hack with fatlabel "-i" (dosfstools 4.2) + long int t = time(nullptr); char uuid[4]; for (auto &u : uuid) { u = static_cast(t & 0xff); t >>= 8; } - // HACK: replace this hack with fatlabel "-i" (dosfstools 4.2) - ExternalCommand cmd(report, QStringLiteral("dd"), { QStringLiteral("of=") + deviceNode, QStringLiteral("bs=1"), QStringLiteral("count=4"), QStringLiteral("seek=67") }); - - cmd.write(QByteArray(uuid, sizeof(uuid))); - return cmd.start(); + ExternalCommand cmd; + return cmd.writeData(report, QByteArray(uuid, sizeof(uuid)), deviceNode, 67); } } diff --git a/src/fs/ntfs.cpp b/src/fs/ntfs.cpp index 471638a..0c08c6c 100644 --- a/src/fs/ntfs.cpp +++ b/src/fs/ntfs.cpp @@ -1,214 +1,211 @@ /************************************************************************* * Copyright (C) 2008,2009 by Volker Lanz * * Copyright (C) 2016 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 "fs/ntfs.h" #include "util/externalcommand.h" #include "util/capacity.h" #include "util/report.h" #include "util/globallog.h" #include #include #include #include #include -#include #include #include namespace FS { FileSystem::CommandSupportType ntfs::m_GetUsed = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_GetLabel = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Create = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Grow = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Shrink = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Move = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Check = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Copy = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_Backup = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_SetLabel = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_UpdateUUID = FileSystem::cmdSupportNone; FileSystem::CommandSupportType ntfs::m_GetUUID = FileSystem::cmdSupportNone; ntfs::ntfs(qint64 firstsector, qint64 lastsector, qint64 sectorsused, const QString& label) : FileSystem(firstsector, lastsector, sectorsused, label, FileSystem::Type::Ntfs) { } void ntfs::init() { m_Shrink = m_Grow = m_Check = m_GetUsed = findExternal(QStringLiteral("ntfsresize")) ? cmdSupportFileSystem : cmdSupportNone; m_GetLabel = cmdSupportCore; m_SetLabel = findExternal(QStringLiteral("ntfslabel")) ? cmdSupportFileSystem : cmdSupportNone; m_Create = findExternal(QStringLiteral("mkfs.ntfs")) ? cmdSupportFileSystem : cmdSupportNone; m_Copy = findExternal(QStringLiteral("ntfsclone")) ? cmdSupportFileSystem : cmdSupportNone; m_Backup = cmdSupportCore; m_UpdateUUID = cmdSupportCore; m_Move = (m_Check != cmdSupportNone) ? cmdSupportCore : cmdSupportNone; m_GetUUID = cmdSupportCore; } bool ntfs::supportToolFound() const { return m_GetUsed != cmdSupportNone && m_GetLabel != cmdSupportNone && m_SetLabel != cmdSupportNone && m_Create != cmdSupportNone && m_Check != cmdSupportNone && m_UpdateUUID != cmdSupportNone && m_Grow != cmdSupportNone && m_Shrink != cmdSupportNone && m_Copy != cmdSupportNone && m_Move != cmdSupportNone && m_Backup != cmdSupportNone && m_GetUUID != cmdSupportNone; } FileSystem::SupportTool ntfs::supportToolName() const { return SupportTool(QStringLiteral("ntfs-3g"), QUrl(QStringLiteral("http://www.tuxera.com/community/ntfs-3g-download/"))); } qint64 ntfs::minCapacity() const { return 2 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::MiB); } qint64 ntfs::maxCapacity() const { return 256 * Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::TiB); } int ntfs::maxLabelLength() const { return 128; } qint64 ntfs::readUsedCapacity(const QString& deviceNode) const { ExternalCommand cmd(QStringLiteral("ntfsresize"), { QStringLiteral("--info"), QStringLiteral("--force"), QStringLiteral("--no-progress-bar"), deviceNode }); if (cmd.run(-1) && cmd.exitCode() == 0) { qint64 usedBytes = -1; QRegularExpression re(QStringLiteral("resize at (\\d+) bytes")); QRegularExpressionMatch reUsedBytes = re.match(cmd.output()); if (reUsedBytes.hasMatch()) usedBytes = reUsedBytes.captured(1).toLongLong(); if (usedBytes > -1) return usedBytes; } return -1; } bool ntfs::writeLabel(Report& report, const QString& deviceNode, const QString& newLabel) { ExternalCommand writeCmd(report, QStringLiteral("ntfslabel"), { QStringLiteral("--force"), deviceNode, newLabel }, QProcess::SeparateChannels); if (!writeCmd.run(-1)) return false; return true; } bool ntfs::check(Report& report, const QString& deviceNode) const { ExternalCommand cmd(report, QStringLiteral("ntfsresize"), { QStringLiteral("--no-progress-bar"), QStringLiteral("--info"), QStringLiteral("--force"), QStringLiteral("--verbose"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool ntfs::create(Report& report, const QString& deviceNode) { ExternalCommand cmd(report, QStringLiteral("mkfs.ntfs"), { QStringLiteral("--quick"), QStringLiteral("--verbose"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool ntfs::copy(Report& report, const QString& targetDeviceNode, const QString& sourceDeviceNode) const { ExternalCommand cmd(report, QStringLiteral("ntfsclone"), { QStringLiteral("--force"), QStringLiteral("--overwrite"), targetDeviceNode, sourceDeviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool ntfs::resize(Report& report, const QString& deviceNode, qint64 length) const { QStringList args = { QStringLiteral("--no-progress-bar"), QStringLiteral("--force"), deviceNode, QStringLiteral("--size"), QString::number(length) }; QStringList dryRunArgs = args; dryRunArgs << QStringLiteral("--no-action"); ExternalCommand cmdDryRun(QStringLiteral("ntfsresize"), dryRunArgs); if (cmdDryRun.run(-1) && cmdDryRun.exitCode() == 0) { ExternalCommand cmd(report, QStringLiteral("ntfsresize"), args); return cmd.run(-1) && cmd.exitCode() == 0; } return false; } bool ntfs::updateUUID(Report& report, const QString& deviceNode) const { Q_UNUSED(report) ExternalCommand cmd(QStringLiteral("ntfslabel"), { QStringLiteral("--new-serial"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool ntfs::updateBootSector(Report& report, const QString& deviceNode) const { report.line() << xi18nc("@info:progress", "Updating boot sector for NTFS file system on partition %1.", deviceNode); qint64 n = firstSector(); char* s = reinterpret_cast(&n); #if Q_BYTE_ORDER == Q_BIG_ENDIAN std::swap(s[0], s[3]); std::swap(s[1], s[2]); #endif - ExternalCommand cmd(report, QStringLiteral("dd"), { QStringLiteral("of=") + deviceNode , QStringLiteral("bs=1"), QStringLiteral("count=4"), QStringLiteral("seek=28") }); - - cmd.write(QByteArray(s, sizeof(s))); - if (!cmd.start()) { + ExternalCommand cmd; + if (!cmd.writeData(report, QByteArray(s, sizeof(s)), deviceNode, 28)) { Log() << xi18nc("@info:progress", "Could not write new start sector to partition %1 when trying to update the NTFS boot sector.", deviceNode); return false; } // Also update backup NTFS boot sector located at the end of the partition // NOTE: this should fail if filesystem does not span the whole partition qint64 pos = (lastSector() - firstSector()) * sectorSize() + 28; ExternalCommand cmd2(report, QStringLiteral("dd"), { QStringLiteral("of=") + deviceNode , QStringLiteral("bs=1"), QStringLiteral("count=4"), QStringLiteral("seek=") + QString::number(pos) }); cmd2.write(QByteArray(s, sizeof(s))); if (!cmd2.start()) { Log() << xi18nc("@info:progress", "Could not write new start sector to partition %1 when trying to update the NTFS boot sector.", deviceNode); return false; } Log() << xi18nc("@info:progress", "Updated NTFS boot sector for partition %1 successfully.", deviceNode); return true; } } diff --git a/src/util/externalcommand.cpp b/src/util/externalcommand.cpp index 76b480c..72b1b14 100644 --- a/src/util/externalcommand.cpp +++ b/src/util/externalcommand.cpp @@ -1,433 +1,482 @@ /************************************************************************* * 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/copytargetbytearray.h" #include "core/copysourcedevice.h" #include "core/copytargetdevice.h" #include "util/globallog.h" #include "util/externalcommand.h" #include "util/report.h" #include "externalcommandhelper_interface.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 (command().isEmpty()) return false; if (report()) report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); if ( qEnvironmentVariableIsSet( "KPMCORE_DEBUG" )) qDebug() << 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; } 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 bool rval = false; QByteArray request; const quint64 nonce = interface->getNonce(); 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 = interface->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); 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 QByteArray request; const quint64 nonce = interface->getNonce(); 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); QDBusPendingCall pcall = interface->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.value()[QStringLiteral("success")].toBool(); CopyTargetByteArray *byteArrayTarget = dynamic_cast(&target); if (byteArrayTarget) byteArrayTarget->m_Array = reply.value()[QStringLiteral("targetByteArray")].toByteArray(); } setExitCode(!rval); }; connect(watcher, &QDBusPendingCallWatcher::finished, exitLoop); loop.exec(); return rval; } +bool ExternalCommand::writeData(Report& commandReport, const QByteArray& buffer, const QString& deviceNode, const quint64 firstByte) +{ + d->m_Report = commandReport.newChild(); + if (report()) + report()->setCommand(xi18nc("@info:status", "Command: %1 %2", command(), args().join(QStringLiteral(" ")))); + + bool rval = true; + + if (!QDBusConnection::systemBus().isConnected()) { + qWarning() << "Could not connect to DBus system bus"; + return false; + } + + 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 + QByteArray request; + + const quint64 nonce = interface->getNonce(); + request.setNum(nonce); + request.append(buffer); + request.append(deviceNode.toUtf8()); + request.append(QByteArray::number(firstByte)); + + QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); + + QDBusPendingCall pcall = interface->writeData(privateKey->signMessage(hash, QCA::EMSA3_Raw), nonce, + buffer, deviceNode, firstByte); + + 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::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() { auto *interface = new org::kde::kpmcore::externalcommand(QStringLiteral("org.kde.kpmcore.externalcommand"), QStringLiteral("/Helper"), QDBusConnection::systemBus()); QByteArray request; const quint64 nonce = interface->getNonce(); request.setNum(nonce); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); interface->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 35b1a81..c1abba2 100644 --- a/src/util/externalcommand.h +++ b/src/util/externalcommand.h @@ -1,144 +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 // 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. @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); + bool writeData(Report& report, const QByteArray& buffer, const QString& deviceNode, const quint64 firstByte); // same as copyBlocks but from QByteArray /**< @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 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 diff --git a/src/util/externalcommand_whitelist.h b/src/util/externalcommand_whitelist.h index 79160aa..7ae4240 100644 --- a/src/util/externalcommand_whitelist.h +++ b/src/util/externalcommand_whitelist.h @@ -1,107 +1,106 @@ /************************************************************************* * Copyright (C) 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_WHITELIST_H #define KPMCORE_EXTERNALCOMMAND_WHITELIST_H QString allowedCommands[] = { // TODO try to remove these later QStringLiteral("mv"), -QStringLiteral("dd"), // TODO no root needed QStringLiteral("lsblk"), QStringLiteral("udevadm"), //Core programs QStringLiteral("blockdev"), QStringLiteral("sfdisk"), QStringLiteral("wipefs"), QStringLiteral("lvm"), QStringLiteral("mdadm"), QStringLiteral("mount"), QStringLiteral("umount"), QStringLiteral("smartctl"), // FileSystem utilties QStringLiteral("btrfs"), QStringLiteral("mkfs.btrfs"), QStringLiteral("btrfstune"), QStringLiteral("exfatfsck"), QStringLiteral("mkfs.exfat"), QStringLiteral("exfatlabel"), QStringLiteral("dumpe2fs"), QStringLiteral("e2fsck"), QStringLiteral("mkfs.ext2"), QStringLiteral("resize2fs"), QStringLiteral("e2label"), QStringLiteral("tune2fs"), QStringLiteral("mkfs.ext3"), QStringLiteral("mkfs.ext4"), QStringLiteral("mkfs.f2fs"), QStringLiteral("fsck.f2fs"), QStringLiteral("resize.f2fs"), QStringLiteral("fsck.fat"), QStringLiteral("fatlabel"), QStringLiteral("mkfs.fat"), QStringLiteral("fatresize"), QStringLiteral("hfsck"), QStringLiteral("hformat"), QStringLiteral("fsck.hfsplus"), QStringLiteral("mkfs.hfsplus"), QStringLiteral("jfs_debugfs"), QStringLiteral("jfs_tune"), QStringLiteral("fsck.jfs"), QStringLiteral("mkfs.jfs"), QStringLiteral("mkswap"), QStringLiteral("swaplabel"), QStringLiteral("swapon"), QStringLiteral("swapoff"), QStringLiteral("cryptsetup"), QStringLiteral("dmsetup"), QStringLiteral("fsck.nilfs2"), QStringLiteral("mkfs.nilfs2"), QStringLiteral("nilfs-tune"), QStringLiteral("nilfs-resize"), QStringLiteral("ntfsresize"), QStringLiteral("mkfs.ntfs"), QStringLiteral("ntfsclone"), QStringLiteral("ntfslabel"), QStringLiteral("fsck.ocfs2"), QStringLiteral("mkfs.ocfs2"), QStringLiteral("debugfs.ocfs2"), QStringLiteral("tunefs.ocfs2"), QStringLiteral("debugfs.reiser4"), QStringLiteral("fsck.reiser4"), QStringLiteral("mkfs.reiser4"), QStringLiteral("debugreiserfs"), QStringLiteral("reiserfstune"), QStringLiteral("fsck.reiserfs"), QStringLiteral("mkfs.reiserfs"), QStringLiteral("resize_reiserfs"), QStringLiteral("mkudffs"), QStringLiteral("udfinfo"), QStringLiteral("udflabel"), QStringLiteral("xfs_db"), QStringLiteral("xfs_repair"), QStringLiteral("mkfs.xfs"), QStringLiteral("xfs_copy"), QStringLiteral("xfs_growfs"), QStringLiteral("zpool") }; #endif diff --git a/src/util/externalcommandhelper.cpp b/src/util/externalcommandhelper.cpp index 87edade..c5687cf 100644 --- a/src/util/externalcommandhelper.cpp +++ b/src/util/externalcommandhelper.cpp @@ -1,359 +1,387 @@ /************************************************************************* * Copyright (C) 2017-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 "externalcommandhelper.h" #include "externalcommand_interface.h" #include "externalcommand_whitelist.h" #include #include #include #include #include #include #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 a DBus service that we monitor for changes. * 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. * * 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) { ActionReply reply; if (!QDBusConnection::systemBus().isConnected()) { qWarning() << "Could not connect to DBus system bus"; reply.addData(QStringLiteral("success"), false); return reply; } if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.kpmcore.helperinterface"))) { qWarning() << QDBusConnection::systemBus().lastError().message(); reply.addData(QStringLiteral("success"), false); return reply; } 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_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(); }); m_loop->exec(); reply.addData(QStringLiteral("success"), true); return reply; } /** Generates cryptographic nonce * @return nonce */ quint64 ExternalCommandHelper::getNonce() { quint64 nonce = m_Generator.generate(); m_Nonces.insert(nonce); return nonce; } /** 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 @param offset offset where to begin reading @param size the number of bytes to read @return true on success */ bool ExternalCommandHelper::readData(const QString& sourceDevice, QByteArray& buffer, qint64 offset, qint64 size) { QFile device(sourceDevice); if (!device.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { qCritical() << xi18n("Could not open device %1 for reading.", sourceDevice); return false; } if (!device.seek(offset)) { qCritical() << xi18n("Could not seek position %1 on device %2.", offset, sourceDevice); return false; } buffer = device.read(size); if (size != buffer.size()) { qCritical() << xi18n("Could not read from device %1.", sourceDevice); return false; } return true; } /** Writes the data from buffer to a given device or file. @param targetDevice device or file to write to @param buffer the data that we write @param offset offset where to begin writing @return true on success */ bool ExternalCommandHelper::writeData(const QString &targetDevice, const QByteArray& buffer, qint64 offset) { QFile device(targetDevice); if (!device.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered)) { qCritical() << xi18n("Could not open device %1 for writing.", targetDevice); return false; } if (!device.seek(offset)) { qCritical() << xi18n("Could not seek position %1 on device %2.", offset, targetDevice); return false; } if (device.write(buffer) != buffer.size()) { qCritical() << xi18n("Could not write to device %1.", targetDevice); return false; } return true; } +// If targetDevice is empty then return QByteArray with data that was read from disk. QVariantMap ExternalCommandHelper::copyblocks(const QByteArray& signature, const quint64 nonce, const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize) { QVariantMap reply; reply[QStringLiteral("success")] = true; if (m_Nonces.find(nonce) != m_Nonces.end()) m_Nonces.erase( nonce ); else { reply[QStringLiteral("success")] = false; return reply; } QByteArray request; request.setNum(nonce); request.append(sourceDevice.toUtf8()); request.append(QByteArray::number(sourceFirstByte)); request.append(QByteArray::number(sourceLength)); request.append(targetDevice.toUtf8()); request.append(QByteArray::number(targetFirstByte)); request.append(QByteArray::number(blockSize)); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) { qCritical() << xi18n("Invalid cryptographic signature"); reply[QStringLiteral("success")] = false; return reply; } const qint64 blocksToCopy = sourceLength / blockSize; qint64 readOffset = sourceFirstByte; qint64 writeOffset = targetFirstByte; qint32 copyDirection = 1; if (targetFirstByte > sourceFirstByte) { readOffset = sourceFirstByte + sourceLength - blockSize; writeOffset = targetFirstByte + sourceLength - blockSize; copyDirection = -1; } const qint64 lastBlock = sourceLength % blockSize; qint64 bytesWritten = 0; qint64 blocksCopied = 0; QByteArray buffer; int percent = 0; QTime t; t.start(); QVariantMap report; report[QStringLiteral("report")] = 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); bool rval = true; - while (blocksCopied < blocksToCopy) { + while (blocksCopied < blocksToCopy && !targetDevice.isEmpty()) { if (!(rval = readData(sourceDevice, buffer, readOffset + blockSize * blocksCopied * copyDirection, blockSize))) break; if (!(rval = writeData(targetDevice, buffer, writeOffset + blockSize * blocksCopied * copyDirection))) break; bytesWritten += buffer.size(); if (++blocksCopied * 100 / blocksToCopy != percent) { percent = blocksCopied * 100 / blocksToCopy; 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); } HelperSupport::progressStep(percent); } } // copy the remainder if (rval && lastBlock > 0) { Q_ASSERT(lastBlock < blockSize); 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); rval = readData(sourceDevice, buffer, lastBlockReadOffset, lastBlock); if (rval) { if (targetDevice.isEmpty()) reply[QStringLiteral("targetByteArray")] = buffer; else rval = writeData(targetDevice, buffer, lastBlockWriteOffset); } if (rval) { HelperSupport::progressStep(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); reply[QStringLiteral("success")] = rval; return reply; } +bool ExternalCommandHelper::writeData(const QByteArray& signature, const quint64 nonce, const QByteArray buffer, const QString& targetDevice, const qint64 targetFirstByte) +{ + if (m_Nonces.find(nonce) != m_Nonces.end()) + m_Nonces.erase( nonce ); + else + return false; + + QByteArray request; + request.setNum(nonce); + request.append(buffer); + request.append(targetDevice.toUtf8()); + request.append(QByteArray::number(targetFirstByte)); + + // Do not allow using this helper for writing to arbitrary location + if ( targetDevice.left(5) != QStringLiteral("/dev/") && !targetDevice.contains(QStringLiteral("/etc/fstab"))) + return false; + + QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); + if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) { + qCritical() << xi18n("Invalid cryptographic signature"); + return false; + } + + return writeData(targetDevice, buffer, targetFirstByte); +} + + QVariantMap ExternalCommandHelper::start(const QByteArray& signature, const quint64 nonce, const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode) { QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); QVariantMap reply; reply[QStringLiteral("success")] = true; if (m_Nonces.find(nonce) != m_Nonces.end()) m_Nonces.erase( nonce ); else { reply[QStringLiteral("success")] = false; return reply; } if (command.isEmpty()) { reply[QStringLiteral("success")] = false; return reply; } QByteArray request; request.setNum(nonce); request.append(command.toUtf8()); for (const auto &argument : arguments) request.append(argument.toUtf8()); request.append(input); request.append(processChannelMode); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) { qCritical() << xi18n("Invalid cryptographic signature"); reply[QStringLiteral("success")] = false; return reply; } // Compare with command whitelist QString basename = command.mid(command.lastIndexOf(QLatin1Char('/')) + 1); if (std::find(std::begin(allowedCommands), std::end(allowedCommands), basename) == std::end(allowedCommands)) { // TODO: notify the user m_loop->exit(); reply[QStringLiteral("success")] = false; return reply; } // connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput); m_cmd.setEnvironment( { QStringLiteral("LVM_SUPPRESS_FD_WARNINGS=1") } ); m_cmd.setProcessChannelMode(static_cast(processChannelMode)); m_cmd.start(command, arguments); m_cmd.write(input); m_cmd.closeWriteChannel(); m_cmd.waitForFinished(-1); QByteArray output = m_cmd.readAllStandardOutput(); reply[QStringLiteral("output")] = output; reply[QStringLiteral("exitCode")] = m_cmd.exitCode(); return reply; } void ExternalCommandHelper::exit(const QByteArray& signature, const quint64 nonce) { QByteArray request; if (m_Nonces.find(nonce) == m_Nonces.end()) return; request.setNum(nonce); QByteArray hash = QCryptographicHash::hash(request, QCryptographicHash::Sha512); if (!m_publicKey.verifyMessage(hash, signature, QCA::EMSA3_Raw)) { qCritical() << xi18n("Invalid cryptographic signature"); return; } m_loop->exit(); QDBusConnection::systemBus().unregisterObject(QStringLiteral("/Helper")); QDBusConnection::systemBus().unregisterService(QStringLiteral("org.kde.kpmcore.helperinterface")); } void ExternalCommandHelper::onReadOutput() { // const QByteArray s = cmd.readAllStandardOutput(); // if(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; // } // output += s; // if (report()) // *report() << QString::fromLocal8Bit(s); } KAUTH_HELPER_MAIN("org.kde.kpmcore.externalcommand", ExternalCommandHelper) diff --git a/src/util/externalcommandhelper.h b/src/util/externalcommandhelper.h index 3f7959c..d73d085 100644 --- a/src/util/externalcommandhelper.h +++ b/src/util/externalcommandhelper.h @@ -1,70 +1,71 @@ /************************************************************************* * Copyright (C) 2017-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_EXTERNALCOMMANDHELPER_H #define KPMCORE_EXTERNALCOMMANDHELPER_H #include #include #include #include #include #include #include #include using namespace KAuth; class ExternalCommandHelper : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kpmcore.externalcommand") Q_SIGNALS: void progress(int); void quit(); public: bool readData(const QString& sourceDevice, QByteArray& buffer, qint64 offset, qint64 size); bool writeData(const QString& targetDevice, const QByteArray& buffer, qint64 offset); public Q_SLOTS: ActionReply init(const QVariantMap& args); Q_SCRIPTABLE quint64 getNonce(); Q_SCRIPTABLE QVariantMap start(const QByteArray& signature, const quint64 nonce, const QString& command, const QStringList& arguments, const QByteArray& input, const int processChannelMode); Q_SCRIPTABLE QVariantMap copyblocks(const QByteArray& signature, const quint64 nonce, const QString& sourceDevice, const qint64 sourceFirstByte, const qint64 sourceLength, const QString& targetDevice, const qint64 targetFirstByte, const qint64 blockSize); + Q_SCRIPTABLE bool writeData(const QByteArray& signature, const quint64 nonce, const QByteArray buffer, const QString& targetDevice, const qint64 targetFirstByte); Q_SCRIPTABLE void exit(const QByteArray& signature, const quint64 nonce); private: void onReadOutput(); std::unique_ptr m_loop; QCA::Initializer initializer; QCA::PublicKey m_publicKey; QRandomGenerator64 m_Generator; std::unordered_set m_Nonces; QString m_command; QString m_sourceDevice; QProcess m_cmd; // QByteArray output; }; #endif