1/*
2 SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
3 SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kupdatelaunchenvironmentjob.h"
9
10#include <QDBusArgument>
11#include <QDBusConnection>
12#include <QDBusMetaType>
13#include <QDBusPendingReply>
14
15#include <QTimer>
16
17#include "kdbusaddons_debug.h"
18
19class KUpdateLaunchEnvironmentJobPrivate
20{
21public:
22 explicit KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q);
23 void monitorReply(const QDBusPendingReply<> &reply);
24
25 static bool isPosixName(const QString &name);
26 static bool isSystemdApprovedValue(const QString &value);
27
28 KUpdateLaunchEnvironmentJob *q;
29 QProcessEnvironment environment;
30 int pendingReplies = 0;
31};
32
33KUpdateLaunchEnvironmentJobPrivate::KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q)
34 : q(q)
35{
36}
37
38void KUpdateLaunchEnvironmentJobPrivate::monitorReply(const QDBusPendingReply<> &reply)
39{
40 ++pendingReplies;
41
42 auto *watcher = new QDBusPendingCallWatcher(reply, q);
43 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) {
44 watcher->deleteLater();
45 --pendingReplies;
46
47 if (pendingReplies == 0) {
48 Q_EMIT q->finished();
49 q->deleteLater();
50 }
51 });
52}
53
54KUpdateLaunchEnvironmentJob::KUpdateLaunchEnvironmentJob(const QProcessEnvironment &environment)
55 : d(new KUpdateLaunchEnvironmentJobPrivate(this))
56{
57 d->environment = environment;
58 QTimer::singleShot(0, this, &KUpdateLaunchEnvironmentJob::start);
59}
60
61KUpdateLaunchEnvironmentJob::~KUpdateLaunchEnvironmentJob() = default;
62
63void KUpdateLaunchEnvironmentJob::start()
64{
65 qDBusRegisterMetaType<QMap<QString, QString>>();
66 QMap<QString, QString> dbusActivationEnv;
67 QStringList systemdUpdates;
68
69 for (const auto &varName : d->environment.keys()) {
70 if (!KUpdateLaunchEnvironmentJobPrivate::isPosixName(varName)) {
71 qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as name contains unsupported characters";
72 continue;
73 }
74 const QString value = d->environment.value(varName);
75
76 // plasma-session
77 QDBusMessage plasmaSessionMsg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Startup"),
78 QStringLiteral("/Startup"),
79 QStringLiteral("org.kde.Startup"),
80 QStringLiteral("updateLaunchEnv"));
81 plasmaSessionMsg.setArguments({QVariant::fromValue(varName), QVariant::fromValue(value)});
82 auto plasmaSessionReply = QDBusConnection::sessionBus().asyncCall(plasmaSessionMsg);
83 d->monitorReply(plasmaSessionReply);
84
85 // DBus-activation environment
86 dbusActivationEnv.insert(varName, value);
87
88 // _user_ systemd env
89 // Systemd has stricter parsing of valid environment variables
90 // https://github.com/systemd/systemd/issues/16704
91 // validate here
92 if (!KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(value)) {
93 qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as value contains unsupported characters";
94 continue;
95 }
96 const QString updateString = varName + QStringLiteral("=") + value;
97 systemdUpdates.append(updateString);
98 }
99
100 // DBus-activation environment
101 QDBusMessage dbusActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
102 QStringLiteral("/org/freedesktop/DBus"),
103 QStringLiteral("org.freedesktop.DBus"),
104 QStringLiteral("UpdateActivationEnvironment"));
105 dbusActivationMsg.setArguments({QVariant::fromValue(dbusActivationEnv)});
106
107 auto dbusActivationReply = QDBusConnection::sessionBus().asyncCall(dbusActivationMsg);
108 d->monitorReply(dbusActivationReply);
109
110 // _user_ systemd env
111 QDBusMessage systemdActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
112 QStringLiteral("/org/freedesktop/systemd1"),
113 QStringLiteral("org.freedesktop.systemd1.Manager"),
114 QStringLiteral("SetEnvironment"));
115 systemdActivationMsg.setArguments({systemdUpdates});
116
117 auto systemdActivationReply = QDBusConnection::sessionBus().asyncCall(systemdActivationMsg);
118 d->monitorReply(systemdActivationReply);
119}
120
121bool KUpdateLaunchEnvironmentJobPrivate::isPosixName(const QString &name)
122{
123 // Posix says characters like % should be 'tolerated', but it gives issues in practice.
124 // https://bugzilla.redhat.com/show_bug.cgi?id=1754395
125 // https://bugzilla.redhat.com/show_bug.cgi?id=1879216
126 // Ensure systemd compat by only allowing alphanumerics and _ in names.
127 bool first = true;
128 for (const QChar c : name) {
129 if (first && !c.isLetter() && c != QLatin1Char('_')) {
130 return false;
131 } else if (first) {
132 first = false;
133 } else if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
134 return false;
135 }
136 }
137 return !first;
138}
139
140bool KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(const QString &value)
141{
142 // systemd code checks that a value contains no control characters except \n \t
143 // effectively copied from systemd's string_has_cc
144 for (const char &it : value.toLatin1()) {
145 if (it == QLatin1Char('\n') || it == QLatin1Char('\t')) {
146 continue;
147 }
148 if (it > 0 && it < ' ') {
149 return false;
150 }
151 if (it == 127) {
152 return false;
153 }
154 }
155 return true;
156}
157
158#include "moc_kupdatelaunchenvironmentjob.cpp"
159

source code of kdbusaddons/src/kupdatelaunchenvironmentjob.cpp