Skip to content

Commit 046814c

Browse files
committed
WIP: qubes-gui-runner: get environment from systemd and pass it through to the session
1 parent b1c52ac commit 046814c

File tree

5 files changed

+239
-13
lines changed

5 files changed

+239
-13
lines changed

appvm-scripts/usrbin/qubes-session

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,6 @@
2727

2828
loginctl activate "$XDG_SESSION_ID"
2929

30-
# Now import the environment from the systemd user session.
31-
# This is necessary to enable users to configure their
32-
# Qubes environment using the standard environment.d
33-
# facility. Documentation for the facility is at:
34-
# https://www.freedesktop.org/software/systemd/man/environment.d.html
35-
set -a # export all variables
36-
env=$(systemctl --user show-environment) && eval "$env" || exit
37-
set +a
38-
unset env
39-
40-
4130
if qsvc guivm-gui-agent; then
4231
if [ -e "$HOME/.xinitrc" ]; then
4332
. "$HOME/.xinitrc"

debian/control

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Build-Depends:
2626
qubesdb-dev,
2727
libltdl-dev,
2828
libunistring-dev,
29+
libdbus-1-dev,
2930
Standards-Version: 4.4.0.1
3031
Homepage: http://www.qubes-os.org/
3132
#Vcs-Git: git://git.debian.org/collab-maint/qubes-gui-agent.git

gui-agent/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
#
2121

2222
CC ?= gcc
23-
CFLAGS += -I../include/ `pkg-config --cflags vchan` -g -Wall -Wextra -Werror -fPIC \
23+
CFLAGS += -I../include/ `pkg-config --cflags vchan` \
24+
`pkg-config --cflags dbus-1` -g -Wall -Wextra -Werror -fPIC \
2425
-Wmissing-prototypes -Wstrict-prototypes -Wold-style-declaration \
2526
-Wold-style-definition
2627
OBJS = vmside.o txrx-vchan.o error.o list.o encoding.o
@@ -33,7 +34,7 @@ qubes-gui: $(OBJS)
3334
$(CC) $(LDFLAGS) -pie -g -o qubes-gui $(OBJS) \
3435
$(LIBS)
3536
qubes-gui-runuser: CFLAGS += -g -Wall -Wextra -Werror -pie -fPIC
36-
qubes-gui-runuser: LDLIBS += -lpam -lqubesdb
37+
qubes-gui-runuser: LDLIBS += -lpam -lqubesdb -ldbus-1
3738
qubes-gui-runuser: qubes-gui-runuser.c
3839
clean:
3940
rm -f qubes-gui qubes-gui-runuser ./*.o ./*~

gui-agent/qubes-gui-runuser.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,243 @@
3131
#include <grp.h>
3232
#include <pwd.h>
3333
#include <err.h>
34+
#include <stdbool.h>
3435
#include <qubesdb-client.h>
36+
#include <dbus/dbus.h>
3537

3638
#ifdef HAVE_PAM
3739
#include <security/pam_appl.h>
3840
#endif
3941

4042
pid_t child_pid = 0;
4143

44+
static void get_systemd_env_from_dbus(char ***out_env_ref)
45+
{
46+
DBusConnection *dbus_conn = NULL;
47+
DBusError error_data = { 0 };
48+
DBusMessage *env_request = NULL;
49+
const char *systemd_manager_str = "org.freedesktop.systemd1.Manager";
50+
const char *environment_str = "Environment";
51+
dbus_bool_t ret = FALSE;
52+
DBusMessage *env_reply = NULL;
53+
int reply_type = 0;
54+
DBusMessageIter reply_iter = { 0 };
55+
DBusMessageIter reply_inner_iter = { 0 };
56+
char *inner_iter_typesig = NULL;
57+
DBusMessageIter reply_arr_iter = { 0 };
58+
const char *env_val = NULL;
59+
char **env_arr = NULL;
60+
char **out_env_arr = NULL;
61+
size_t env_arr_len = 0;
62+
size_t out_env_arr_len = 0;
63+
size_t out_env_idx = 0;
64+
size_t env_idx = 0;
65+
char *out_env_eq_ptr = NULL;
66+
char *env_eq_ptr = NULL;
67+
size_t out_env_pre_eq_len = 0;
68+
size_t env_pre_eq_len = 0;
69+
bool did_override_env_var = false;
70+
int current_type = 0;
71+
72+
if (out_env_ref == NULL)
73+
errx(1, "get_systemd_env_from_dbus does not support a NULL out_env_ref argument!\n");
74+
out_env_arr = *out_env_ref;
75+
for (out_env_idx = 0; out_env_arr[out_env_idx] != NULL; out_env_idx++) {
76+
out_env_arr_len++;
77+
}
78+
/* Increment 1 to include the NULL element at the end of the array. */
79+
out_env_arr_len++;
80+
81+
/* Initialize D-Bus. */
82+
dbus_error_init(&error_data);
83+
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &error_data);
84+
if (dbus_conn == NULL) {
85+
warnx("Failed to initialize DBus, error name: '%s', error contents: '%s'\n",
86+
error_data.name,
87+
error_data.message);
88+
goto dbus_cleanup;
89+
}
90+
91+
/* dbus_bus_get sets up our process to be killed if the DBus connection
92+
* closes. We don't want that, turn that off.
93+
*/
94+
dbus_connection_set_exit_on_disconnect(dbus_conn, FALSE);
95+
96+
/* Create a DBus method call message for getting the "Environment"
97+
* property of org.freedesktop.systemd1.Manager.
98+
*/
99+
env_request = dbus_message_new_method_call("org.freedesktop.systemd1",
100+
"/org/freedesktop/systemd1",
101+
"org.freedesktop.DBus.Properties",
102+
"Get");
103+
if (env_request == NULL) {
104+
warnx("Failed to create DBus method call!\n");
105+
goto dbus_cleanup;
106+
}
107+
108+
ret = dbus_message_append_args(env_request,
109+
DBUS_TYPE_STRING, &systemd_manager_str,
110+
DBUS_TYPE_STRING, &environment_str,
111+
DBUS_TYPE_INVALID);
112+
if (ret == FALSE) {
113+
warnx("Failed to append arguments to DBus method call!\n");
114+
goto dbus_cleanup;
115+
}
116+
117+
/* Send the method call to systemd, waiting a maximum of 500 milliseconds
118+
* for a response.
119+
*/
120+
env_reply = dbus_connection_send_with_reply_and_block(dbus_conn,
121+
env_request,
122+
500,
123+
&error_data);
124+
if (env_reply == NULL) {
125+
warnx("Failed to request environment data from systemd, error name: '%s', error contents: '%s'\n",
126+
error_data.name,
127+
error_data.message);
128+
goto dbus_cleanup;
129+
}
130+
131+
/* Ensure the reply is a method call return value. */
132+
reply_type = dbus_message_get_type(env_reply);
133+
if (reply_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
134+
warnx("Did not get expected method reply from systemd!\n");
135+
goto dbus_cleanup;
136+
}
137+
138+
/* D-Bus property get methods return a Variant, which is not a basic type,
139+
* thus we have to iterate through the method return value to get to the
140+
* contents.
141+
*/
142+
ret = dbus_message_iter_init(env_reply, &reply_iter);
143+
if (ret == FALSE) {
144+
warnx("systemd returned no environment variables!\n");
145+
goto dbus_cleanup;
146+
}
147+
148+
/* Make sure we actually got a Variant in reply. If so, recurse into it so
149+
* we can look at its contents.
150+
*/
151+
if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_VARIANT) {
152+
warnx("Did not get variant container from systemd!\n");
153+
goto dbus_cleanup;
154+
}
155+
dbus_message_iter_recurse(&reply_iter, &reply_inner_iter);
156+
157+
/* Ensure the returned Variant contains a string array. The type signature
158+
* for this data type in D-Bus is "as". If we do have a string array,
159+
* recurse into it so we can iterate through it.
160+
* TODO: The D-Bus API documentation warns "Be sure you have somehow
161+
* checked that dbus_message_iter_get_arg_type() matches the type you are
162+
* expecting to recurse into. Results of this function are undefined if
163+
* there is no container to recurse into at the current iterator position.
164+
* Are we sure that dbus_message_iter_get_signature() does a sufficiently
165+
* equivalent job here?
166+
*/
167+
inner_iter_typesig = dbus_message_iter_get_signature(&reply_inner_iter);
168+
if (strcmp(inner_iter_typesig, "as") != 0) {
169+
warnx("Variant container from systemd does not contain a string array!\n");
170+
goto dbus_cleanup;
171+
}
172+
dbus_message_iter_recurse(&reply_inner_iter, &reply_arr_iter);
173+
174+
/* Walk through the elements of the string array, appending them to our
175+
* internal environment array "env_arr".
176+
* TODO: What is the expected behavior if the same variable is returned
177+
* here more than once?
178+
*/
179+
while ((current_type = dbus_message_iter_get_arg_type(&reply_arr_iter))
180+
!= DBUS_TYPE_INVALID) {
181+
if (current_type != DBUS_TYPE_STRING) {
182+
warnx("Non-string item found in string array!\n");
183+
goto dbus_cleanup;
184+
}
185+
dbus_message_iter_get_basic(&reply_arr_iter, &env_val);
186+
env_arr_len++;
187+
env_arr = reallocarray(env_arr, env_arr_len, sizeof(char *));
188+
if (env_arr == NULL)
189+
err(1, "Failed to allocate memory for environment array");
190+
env_arr[env_arr_len - 1] = strdup(env_val);
191+
if (env_arr[env_arr_len - 1] == NULL)
192+
err(1, "Failed to allocate memory for environment item");
193+
}
194+
195+
/* Merge the environment from systemd with the environment from PAM.
196+
* Prefer variables from systemd over variables from PAM.
197+
*/
198+
for (env_idx = 0; env_idx < env_arr_len; env_idx++) {
199+
env_eq_ptr = strstr(env_arr[env_idx], "=");
200+
if (env_eq_ptr == NULL)
201+
errx(1, "Environment variable without equals sign encountered!\n");
202+
env_pre_eq_len = env_eq_ptr - env_arr[env_idx];
203+
did_override_env_var = false;
204+
205+
for (out_env_idx = 0; out_env_arr[out_env_idx] != NULL;
206+
out_env_idx++) {
207+
out_env_eq_ptr = strstr(out_env_arr[out_env_idx], "=");
208+
if (out_env_eq_ptr == NULL)
209+
errx(1, "Environment variable without equals sign encountered!\n");
210+
out_env_pre_eq_len = out_env_eq_ptr - out_env_arr[out_env_idx];
211+
212+
if (out_env_pre_eq_len != env_pre_eq_len)
213+
continue;
214+
215+
if (strncmp(out_env_arr[out_env_idx], env_arr[env_idx],
216+
env_pre_eq_len) == 0) {
217+
/* According to `man pam_getenvlist`, "it is the
218+
* responsibility of the calling application to free() [the
219+
* memory allocated by pam_getenvlist()]". out_env_arr will be
220+
* a char ** created by pam_getenvlist(), thus we can safely
221+
* free items in out_env_arr.
222+
*/
223+
free(out_env_arr[out_env_idx]);
224+
225+
/* We intentionally are NOT copying the string here. Every
226+
* item in env_arr will eventually end up in out_env_arr, so
227+
* rather than copying strings from env_arr and then freeing
228+
* env_arr, we simply merge all of the pointers from env_arr
229+
* into out_env_arr, freeing anything in out_env_arr that is
230+
* overridden by something from systemd. This wastes no
231+
* memory, and is quite a bit more efficient.
232+
*/
233+
out_env_arr[out_env_idx] = env_arr[env_idx];
234+
235+
/* Signal to the outer loop that we don't need to append an
236+
* item to the environment list.
237+
*/
238+
did_override_env_var = true;
239+
break;
240+
}
241+
}
242+
243+
if (!did_override_env_var) {
244+
/* Append the variable to the list. */
245+
out_env_arr_len++;
246+
out_env_arr = reallocarray(out_env_arr, out_env_arr_len,
247+
sizeof(char *));
248+
if (out_env_arr == NULL)
249+
err(1, "Failed to allocate memory for environment item");
250+
251+
out_env_arr[out_env_arr_len - 1] = NULL;
252+
/* See above for rationale behind using assignment rather than
253+
* copying here.
254+
*/
255+
out_env_arr[out_env_arr_len - 2] = env_arr[env_idx];
256+
}
257+
}
258+
259+
dbus_cleanup:
260+
if (dbus_conn != NULL)
261+
dbus_connection_unref(dbus_conn);
262+
if (env_request != NULL)
263+
dbus_message_unref(env_request);
264+
if (env_reply != NULL)
265+
dbus_message_unref(env_reply);
266+
if (inner_iter_typesig != NULL)
267+
dbus_free(inner_iter_typesig);
268+
*out_env_ref = out_env_arr;
269+
}
270+
42271
#ifdef HAVE_PAM
43272
static int pam_conv_callback(int num_msg, const struct pam_message **msg,
44273
struct pam_response **resp, void *appdata_ptr __attribute__((__unused__)))
@@ -241,6 +470,11 @@ static pid_t do_execute(char *user, char *path, char **argv)
241470
/* This is a copy but don't care to free as we exec later anyway. */
242471
env = pam_getenvlist (pamh);
243472

473+
/* Augment the environment list from PAM with the environment from
474+
* systemd's user manager.
475+
*/
476+
get_systemd_env_from_dbus(&env);
477+
244478
/* try to enter home dir, but don't abort if it fails */
245479
retval = chdir(pw->pw_dir);
246480
if (retval == -1)

rpm_spec/gui-agent.spec.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ BuildRequires: qubes-db-devel
7676
BuildRequires: xen-devel
7777
BuildRequires: systemd-rpm-macros
7878
BuildRequires: libunistring-devel
79+
BuildRequires: dbus-devel
7980
%if 0%{?is_opensuse}
8081
# for directory ownership
8182
BuildRequires: xinit

0 commit comments

Comments
 (0)