|
31 | 31 | #include <grp.h>
|
32 | 32 | #include <pwd.h>
|
33 | 33 | #include <err.h>
|
| 34 | +#include <stdbool.h> |
34 | 35 | #include <qubesdb-client.h>
|
| 36 | +#include <dbus/dbus.h> |
35 | 37 |
|
36 | 38 | #ifdef HAVE_PAM
|
37 | 39 | #include <security/pam_appl.h>
|
38 | 40 | #endif
|
39 | 41 |
|
40 | 42 | pid_t child_pid = 0;
|
41 | 43 |
|
| 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 | + |
42 | 271 | #ifdef HAVE_PAM
|
43 | 272 | static int pam_conv_callback(int num_msg, const struct pam_message **msg,
|
44 | 273 | struct pam_response **resp, void *appdata_ptr __attribute__((__unused__)))
|
@@ -241,6 +470,11 @@ static pid_t do_execute(char *user, char *path, char **argv)
|
241 | 470 | /* This is a copy but don't care to free as we exec later anyway. */
|
242 | 471 | env = pam_getenvlist (pamh);
|
243 | 472 |
|
| 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 | + |
244 | 478 | /* try to enter home dir, but don't abort if it fails */
|
245 | 479 | retval = chdir(pw->pw_dir);
|
246 | 480 | if (retval == -1)
|
|
0 commit comments