File indexing completed on 2024-04-28 03:53:58

0001 /*
0002     SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
0003     SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kupdatelaunchenvironmentjob.h"
0009 
0010 #include <QDBusArgument>
0011 #include <QDBusConnection>
0012 #include <QDBusMetaType>
0013 #include <QDBusPendingReply>
0014 
0015 #include <QTimer>
0016 
0017 #include "kdbusaddons_debug.h"
0018 
0019 class KUpdateLaunchEnvironmentJobPrivate
0020 {
0021 public:
0022     explicit KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q);
0023     void monitorReply(const QDBusPendingReply<> &reply);
0024 
0025     static bool isPosixName(const QString &name);
0026     static bool isSystemdApprovedValue(const QString &value);
0027 
0028     KUpdateLaunchEnvironmentJob *q;
0029     QProcessEnvironment environment;
0030     int pendingReplies = 0;
0031 };
0032 
0033 KUpdateLaunchEnvironmentJobPrivate::KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q)
0034     : q(q)
0035 {
0036 }
0037 
0038 void KUpdateLaunchEnvironmentJobPrivate::monitorReply(const QDBusPendingReply<> &reply)
0039 {
0040     ++pendingReplies;
0041 
0042     auto *watcher = new QDBusPendingCallWatcher(reply, q);
0043     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) {
0044         watcher->deleteLater();
0045         --pendingReplies;
0046 
0047         if (pendingReplies == 0) {
0048             Q_EMIT q->finished();
0049             q->deleteLater();
0050         }
0051     });
0052 }
0053 
0054 KUpdateLaunchEnvironmentJob::KUpdateLaunchEnvironmentJob(const QProcessEnvironment &environment)
0055     : d(new KUpdateLaunchEnvironmentJobPrivate(this))
0056 {
0057     d->environment = environment;
0058     QTimer::singleShot(0, this, &KUpdateLaunchEnvironmentJob::start);
0059 }
0060 
0061 KUpdateLaunchEnvironmentJob::~KUpdateLaunchEnvironmentJob() = default;
0062 
0063 void KUpdateLaunchEnvironmentJob::start()
0064 {
0065     qDBusRegisterMetaType<QMap<QString, QString>>();
0066     QMap<QString, QString> dbusActivationEnv;
0067     QStringList systemdUpdates;
0068 
0069     for (const auto &varName : d->environment.keys()) {
0070         if (!KUpdateLaunchEnvironmentJobPrivate::isPosixName(varName)) {
0071             qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as name contains unsupported characters";
0072             continue;
0073         }
0074         const QString value = d->environment.value(varName);
0075 
0076         // plasma-session
0077         QDBusMessage plasmaSessionMsg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Startup"),
0078                                                                        QStringLiteral("/Startup"),
0079                                                                        QStringLiteral("org.kde.Startup"),
0080                                                                        QStringLiteral("updateLaunchEnv"));
0081         plasmaSessionMsg.setArguments({QVariant::fromValue(varName), QVariant::fromValue(value)});
0082         auto plasmaSessionReply = QDBusConnection::sessionBus().asyncCall(plasmaSessionMsg);
0083         d->monitorReply(plasmaSessionReply);
0084 
0085         // DBus-activation environment
0086         dbusActivationEnv.insert(varName, value);
0087 
0088         // _user_ systemd env
0089         // Systemd has stricter parsing of valid environment variables
0090         // https://github.com/systemd/systemd/issues/16704
0091         // validate here
0092         if (!KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(value)) {
0093             qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as value contains unsupported characters";
0094             continue;
0095         }
0096         const QString updateString = varName + QStringLiteral("=") + value;
0097         systemdUpdates.append(updateString);
0098     }
0099 
0100     // DBus-activation environment
0101     QDBusMessage dbusActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
0102                                                                     QStringLiteral("/org/freedesktop/DBus"),
0103                                                                     QStringLiteral("org.freedesktop.DBus"),
0104                                                                     QStringLiteral("UpdateActivationEnvironment"));
0105     dbusActivationMsg.setArguments({QVariant::fromValue(dbusActivationEnv)});
0106 
0107     auto dbusActivationReply = QDBusConnection::sessionBus().asyncCall(dbusActivationMsg);
0108     d->monitorReply(dbusActivationReply);
0109 
0110     // _user_ systemd env
0111     QDBusMessage systemdActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0112                                                                        QStringLiteral("/org/freedesktop/systemd1"),
0113                                                                        QStringLiteral("org.freedesktop.systemd1.Manager"),
0114                                                                        QStringLiteral("SetEnvironment"));
0115     systemdActivationMsg.setArguments({systemdUpdates});
0116 
0117     auto systemdActivationReply = QDBusConnection::sessionBus().asyncCall(systemdActivationMsg);
0118     d->monitorReply(systemdActivationReply);
0119 }
0120 
0121 bool KUpdateLaunchEnvironmentJobPrivate::isPosixName(const QString &name)
0122 {
0123     // Posix says characters like % should be 'tolerated', but it gives issues in practice.
0124     // https://bugzilla.redhat.com/show_bug.cgi?id=1754395
0125     // https://bugzilla.redhat.com/show_bug.cgi?id=1879216
0126     // Ensure systemd compat by only allowing alphanumerics and _ in names.
0127     bool first = true;
0128     for (const QChar c : name) {
0129         if (first && !c.isLetter() && c != QLatin1Char('_')) {
0130             return false;
0131         } else if (first) {
0132             first = false;
0133         } else if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
0134             return false;
0135         }
0136     }
0137     return !first;
0138 }
0139 
0140 bool KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(const QString &value)
0141 {
0142     // systemd code checks that a value contains no control characters except \n \t
0143     // effectively copied from systemd's string_has_cc
0144     for (const char &it : value.toLatin1()) {
0145         if (it == QLatin1Char('\n') || it == QLatin1Char('\t')) {
0146             continue;
0147         }
0148         if (it > 0 && it < ' ') {
0149             return false;
0150         }
0151         if (it == 127) {
0152             return false;
0153         }
0154     }
0155     return true;
0156 }
0157 
0158 #include "moc_kupdatelaunchenvironmentjob.cpp"