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"