File indexing completed on 2024-04-28 11:35:28

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 "updatelaunchenvironmentjob.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 UpdateLaunchEnvironmentJobPrivate
0020 {
0021 public:
0022     explicit UpdateLaunchEnvironmentJobPrivate(UpdateLaunchEnvironmentJob *q);
0023     void monitorReply(const QDBusPendingReply<> &reply);
0024 
0025     static bool isPosixName(const QString &name);
0026     static bool isSystemdApprovedValue(const QString &value);
0027 
0028     UpdateLaunchEnvironmentJob *q;
0029     QProcessEnvironment environment;
0030     int pendingReplies = 0;
0031 };
0032 
0033 UpdateLaunchEnvironmentJobPrivate::UpdateLaunchEnvironmentJobPrivate(UpdateLaunchEnvironmentJob *q)
0034     : q(q)
0035 {
0036 }
0037 
0038 void UpdateLaunchEnvironmentJobPrivate::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 // KF6 TODO: add K-prefix to class name
0055 UpdateLaunchEnvironmentJob::UpdateLaunchEnvironmentJob(const QProcessEnvironment &environment)
0056     : d(new UpdateLaunchEnvironmentJobPrivate(this))
0057 {
0058     d->environment = environment;
0059     QTimer::singleShot(0, this, &UpdateLaunchEnvironmentJob::start);
0060 }
0061 
0062 UpdateLaunchEnvironmentJob::~UpdateLaunchEnvironmentJob() = default;
0063 
0064 void UpdateLaunchEnvironmentJob::start()
0065 {
0066     qDBusRegisterMetaType<QMap<QString, QString>>();
0067     QMap<QString, QString> dbusActivationEnv;
0068     QStringList systemdUpdates;
0069 
0070     for (const auto &varName : d->environment.keys()) {
0071         if (!UpdateLaunchEnvironmentJobPrivate::isPosixName(varName)) {
0072             qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as name contains unsupported characters";
0073             continue;
0074         }
0075         const QString value = d->environment.value(varName);
0076 
0077         // KLauncher; remove this in KF6 (by then KInit will be gone)
0078         QDBusMessage klauncherMsg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"),
0079                                                                    QStringLiteral("/KLauncher"),
0080                                                                    QStringLiteral("org.kde.KLauncher"),
0081                                                                    QStringLiteral("setLaunchEnv"));
0082         klauncherMsg.setArguments({QVariant::fromValue(varName), QVariant::fromValue(value)});
0083         auto klauncherReply = QDBusConnection::sessionBus().asyncCall(klauncherMsg);
0084         d->monitorReply(klauncherReply);
0085 
0086         // plasma-session
0087         QDBusMessage plasmaSessionMsg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Startup"),
0088                                                                        QStringLiteral("/Startup"),
0089                                                                        QStringLiteral("org.kde.Startup"),
0090                                                                        QStringLiteral("updateLaunchEnv"));
0091         plasmaSessionMsg.setArguments({QVariant::fromValue(varName), QVariant::fromValue(value)});
0092         auto plasmaSessionReply = QDBusConnection::sessionBus().asyncCall(plasmaSessionMsg);
0093         d->monitorReply(plasmaSessionReply);
0094 
0095         // DBus-activation environment
0096         dbusActivationEnv.insert(varName, value);
0097 
0098         // _user_ systemd env
0099         // Systemd has stricter parsing of valid environment variables
0100         // https://github.com/systemd/systemd/issues/16704
0101         // validate here
0102         if (!UpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(value)) {
0103             qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as value contains unsupported characters";
0104             continue;
0105         }
0106         const QString updateString = varName + QStringLiteral("=") + value;
0107         systemdUpdates.append(updateString);
0108     }
0109 
0110     // DBus-activation environment
0111     QDBusMessage dbusActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
0112                                                                     QStringLiteral("/org/freedesktop/DBus"),
0113                                                                     QStringLiteral("org.freedesktop.DBus"),
0114                                                                     QStringLiteral("UpdateActivationEnvironment"));
0115     dbusActivationMsg.setArguments({QVariant::fromValue(dbusActivationEnv)});
0116 
0117     auto dbusActivationReply = QDBusConnection::sessionBus().asyncCall(dbusActivationMsg);
0118     d->monitorReply(dbusActivationReply);
0119 
0120     // _user_ systemd env
0121     QDBusMessage systemdActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0122                                                                        QStringLiteral("/org/freedesktop/systemd1"),
0123                                                                        QStringLiteral("org.freedesktop.systemd1.Manager"),
0124                                                                        QStringLiteral("SetEnvironment"));
0125     systemdActivationMsg.setArguments({systemdUpdates});
0126 
0127     auto systemdActivationReply = QDBusConnection::sessionBus().asyncCall(systemdActivationMsg);
0128     d->monitorReply(systemdActivationReply);
0129 }
0130 
0131 bool UpdateLaunchEnvironmentJobPrivate::isPosixName(const QString &name)
0132 {
0133     // Posix says characters like % should be 'tolerated', but it gives issues in practice.
0134     // https://bugzilla.redhat.com/show_bug.cgi?id=1754395
0135     // https://bugzilla.redhat.com/show_bug.cgi?id=1879216
0136     // Ensure systemd compat by only allowing alphanumerics and _ in names.
0137     bool first = true;
0138     for (const QChar c : name) {
0139         if (first && !c.isLetter() && c != QLatin1Char('_')) {
0140             return false;
0141         } else if (first) {
0142             first = false;
0143         } else if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
0144             return false;
0145         }
0146     }
0147     return !first;
0148 }
0149 
0150 bool UpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(const QString &value)
0151 {
0152     // systemd code checks that a value contains no control characters except \n \t
0153     // effectively copied from systemd's string_has_cc
0154     for (const char &it : value.toLatin1()) {
0155         if (it == QLatin1Char('\n') || it == QLatin1Char('\t')) {
0156             continue;
0157         }
0158         if (it > 0 && it < ' ') {
0159             return false;
0160         }
0161         if (it == 127) {
0162             return false;
0163         }
0164     }
0165     return true;
0166 }
0167 
0168 #include "moc_updatelaunchenvironmentjob.cpp"