File indexing completed on 2024-05-12 05:29:11

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 
0003 #include <fcntl.h>
0004 
0005 #include <filesystem>
0006 
0007 #include <QCoreApplication>
0008 #include <QDBusConnection>
0009 #include <QDBusConnectionInterface>
0010 #include <QDBusContext>
0011 #include <QDBusMetaType>
0012 #include <QDBusUnixFileDescriptor>
0013 #include <QFileInfo>
0014 #include <QProcess>
0015 #include <QTemporaryDir>
0016 
0017 #include <polkitqt1-agent-session.h>
0018 #include <polkitqt1-authority.h>
0019 
0020 #include <coredumpexcavator.h>
0021 
0022 using namespace Qt::StringLiterals;
0023 
0024 class Helper : public QObject, protected QDBusContext
0025 {
0026     Q_OBJECT
0027     Q_CLASSINFO("D-Bus Interface", "org.kde.drkonqi")
0028 
0029     static constexpr auto COREDUMP_PATH = "/var/lib/systemd/coredump/"_L1; // The path is hardcoded in systemd's coredump.c
0030     static constexpr auto ACTION_NAME = "org.kde.drkonqi.excavateFromToDirFd"_L1;
0031     static constexpr auto CORE_NAME = "core"_L1;
0032 
0033 public Q_SLOTS:
0034     QString excavateFromToDirFd(const QString &coreName, const QDBusUnixFileDescriptor &targetDirFd)
0035     {
0036         auto loopLock = std::make_shared<QEventLoopLocker>();
0037         auto tmpDir = std::make_unique<QTemporaryDir>(QDir::tempPath() + "/drkonqi-coredump-excavator"_L1);
0038         if (!tmpDir->isValid()) {
0039             qWarning() << "tmpdir not valid";
0040             sendErrorReply(QDBusError::InternalError, "Failed to create temporary directory"_L1);
0041             return {};
0042         }
0043 
0044         const QString coreFileDir = tmpDir->path();
0045         const QString coreFileTarget = tmpDir->filePath(CORE_NAME);
0046         const QString coreFile = COREDUMP_PATH + coreName;
0047 
0048         if (!isAuthorized(coreFile, coreFileTarget)) {
0049             qWarning() << "not authorized";
0050             sendErrorReply(QDBusError::AccessDenied);
0051             return {};
0052         }
0053 
0054         setDelayedReply(true);
0055 
0056         auto connection = this->connection();
0057         auto msg = message();
0058 
0059         // Keep the core secure by making it only accessible to owner.
0060         const auto targetDir = QFileInfo(coreFileTarget).path();
0061         std::filesystem::permissions(targetDir.toStdString(), std::filesystem::perms::owner_all, std::filesystem::perm_options::replace);
0062 
0063         auto excavator = new CoredumpExcavator(this);
0064         connect(excavator,
0065                 &CoredumpExcavator::excavated,
0066                 this,
0067                 [msg, tmpDir = std::move(tmpDir), loopLock, connection, excavator, coreFileDir, coreFileTarget, targetDirFd](int exitCode) mutable {
0068                     excavator->deleteLater();
0069 
0070                     int sourceDirFd = open(qUtf8Printable(coreFileDir), O_RDONLY | O_CLOEXEC | O_DIRECTORY);
0071                     if (sourceDirFd < 0) {
0072                         int err = errno;
0073                         QString errString = u"Failed to open coreFileDir(%1): %2"_s.arg(coreFileDir, QString::fromUtf8(strerror(err)));
0074                         connection.send(msg.createErrorReply(QDBusError::InternalError, errString));
0075                         return;
0076                     }
0077                     auto closeFd = qScopeGuard([sourceDirFd] {
0078                         close(sourceDirFd);
0079                     });
0080 
0081                     if (renameat(sourceDirFd, qUtf8Printable(CORE_NAME), targetDirFd.fileDescriptor(), qUtf8Printable(CORE_NAME)) != 0) {
0082                         int err = errno;
0083                         QString errString = u"Failed to rename between directory fds: %1"_s.arg(QString::fromUtf8(strerror(err)));
0084                         connection.send(msg.createErrorReply(QDBusError::InternalError, errString));
0085                         return;
0086                     }
0087 
0088                     auto reply = msg.createReply() << (exitCode == 0 ? CORE_NAME : QString());
0089                     connection.send(reply);
0090                 });
0091 
0092         excavator->excavateFromTo(coreFile, coreFileTarget);
0093 
0094         return {};
0095     }
0096 
0097 private:
0098     bool isAuthorized(const QString &coreName, const QString &coreFileTarget)
0099     {
0100         auto authority = PolkitQt1::Authority::instance();
0101         auto result = authority->checkAuthorizationSyncWithDetails(ACTION_NAME,
0102                                                                    PolkitQt1::SystemBusNameSubject(message().service()),
0103                                                                    PolkitQt1::Authority::AllowUserInteraction,
0104                                                                    {
0105                                                                        {"coreFile"_L1, coreName}, //
0106                                                                        {"coreFileTarget"_L1, coreFileTarget}, //
0107                                                                    });
0108 
0109         if (authority->hasError()) {
0110             qWarning() << authority->lastError() << authority->errorDetails();
0111             authority->clearError();
0112             return false;
0113         }
0114 
0115         switch (result) {
0116         case PolkitQt1::Authority::Yes:
0117             return true;
0118         case PolkitQt1::Authority::Unknown:
0119         case PolkitQt1::Authority::No:
0120         case PolkitQt1::Authority::Challenge:
0121             break;
0122         }
0123         return false;
0124     }
0125 };
0126 
0127 int main(int argc, char *argv[])
0128 {
0129     QCoreApplication app(argc, argv);
0130     app.setQuitLockEnabled(true);
0131 
0132     Helper helper;
0133 
0134     if (!QDBusConnection::systemBus().registerObject(QStringLiteral("/"), &helper, QDBusConnection::ExportAllSlots)) {
0135         qWarning() << "Failed to register the daemon object" << QDBusConnection::systemBus().lastError().message();
0136         return 1;
0137     }
0138     if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.drkonqi"))) {
0139         qWarning() << "Failed to register the service" << QDBusConnection::systemBus().lastError().message();
0140         return 1;
0141     }
0142 
0143     return app.exec();
0144 }
0145 
0146 #include "main.moc"