File indexing completed on 2024-04-28 16:54:33

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