]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #14036 from keszybz/systectl-add-logs-and-watchdogs
[thirdparty/systemd.git] / src / login / pam_systemd.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <endian.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <pwd.h>
7 #include <security/_pam_macros.h>
8 #include <security/pam_ext.h>
9 #include <security/pam_misc.h>
10 #include <security/pam_modules.h>
11 #include <security/pam_modutil.h>
12 #include <sys/file.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16
17 #include "alloc-util.h"
18 #include "audit-util.h"
19 #include "bus-common-errors.h"
20 #include "bus-error.h"
21 #include "bus-internal.h"
22 #include "bus-util.h"
23 #include "cgroup-setup.h"
24 #include "errno-util.h"
25 #include "fd-util.h"
26 #include "fileio.h"
27 #include "format-util.h"
28 #include "hostname-util.h"
29 #include "login-util.h"
30 #include "macro.h"
31 #include "parse-util.h"
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "socket-util.h"
35 #include "stdio-util.h"
36 #include "strv.h"
37 #include "terminal-util.h"
38
39 #define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
40
41 static int parse_argv(
42 pam_handle_t *handle,
43 int argc, const char **argv,
44 const char **class,
45 const char **type,
46 const char **desktop,
47 bool *debug) {
48
49 unsigned i;
50
51 assert(argc >= 0);
52 assert(argc == 0 || argv);
53
54 for (i = 0; i < (unsigned) argc; i++) {
55 const char *p;
56
57 if ((p = startswith(argv[i], "class="))) {
58 if (class)
59 *class = p;
60
61 } else if ((p = startswith(argv[i], "type="))) {
62 if (type)
63 *type = p;
64
65 } else if ((p = startswith(argv[i], "desktop="))) {
66 if (desktop)
67 *desktop = p;
68
69 } else if (streq(argv[i], "debug")) {
70 if (debug)
71 *debug = true;
72
73 } else if ((p = startswith(argv[i], "debug="))) {
74 int k;
75
76 k = parse_boolean(p);
77 if (k < 0)
78 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p);
79 else if (debug)
80 *debug = k;
81
82 } else
83 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
84 }
85
86 return 0;
87 }
88
89 static int get_user_data(
90 pam_handle_t *handle,
91 const char **ret_username,
92 struct passwd **ret_pw) {
93
94 const char *username = NULL;
95 struct passwd *pw = NULL;
96 int r;
97
98 assert(handle);
99 assert(ret_username);
100 assert(ret_pw);
101
102 r = pam_get_user(handle, &username, NULL);
103 if (r != PAM_SUCCESS) {
104 pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
105 return r;
106 }
107
108 if (isempty(username)) {
109 pam_syslog(handle, LOG_ERR, "User name not valid.");
110 return PAM_AUTH_ERR;
111 }
112
113 pw = pam_modutil_getpwnam(handle, username);
114 if (!pw) {
115 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
116 return PAM_USER_UNKNOWN;
117 }
118
119 *ret_pw = pw;
120 *ret_username = username;
121
122 return PAM_SUCCESS;
123 }
124
125 static bool display_is_local(const char *display) {
126 assert(display);
127
128 return
129 display[0] == ':' &&
130 display[1] >= '0' &&
131 display[1] <= '9';
132 }
133
134 static int socket_from_display(const char *display, char **path) {
135 size_t k;
136 char *f, *c;
137
138 assert(display);
139 assert(path);
140
141 if (!display_is_local(display))
142 return -EINVAL;
143
144 k = strspn(display+1, "0123456789");
145
146 f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1);
147 if (!f)
148 return -ENOMEM;
149
150 c = stpcpy(f, "/tmp/.X11-unix/X");
151 memcpy(c, display+1, k);
152 c[k] = 0;
153
154 *path = f;
155
156 return 0;
157 }
158
159 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
160 union sockaddr_union sa = {};
161 _cleanup_free_ char *p = NULL, *tty = NULL;
162 _cleanup_close_ int fd = -1;
163 struct ucred ucred;
164 int v, r, salen;
165
166 assert(display);
167 assert(vtnr);
168
169 /* We deduce the X11 socket from the display name, then use
170 * SO_PEERCRED to determine the X11 server process, ask for
171 * the controlling tty of that and if it's a VC then we know
172 * the seat and the virtual terminal. Sounds ugly, is only
173 * semi-ugly. */
174
175 r = socket_from_display(display, &p);
176 if (r < 0)
177 return r;
178 salen = sockaddr_un_set_path(&sa.un, p);
179 if (salen < 0)
180 return salen;
181
182 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
183 if (fd < 0)
184 return -errno;
185
186 if (connect(fd, &sa.sa, salen) < 0)
187 return -errno;
188
189 r = getpeercred(fd, &ucred);
190 if (r < 0)
191 return r;
192
193 r = get_ctty(ucred.pid, NULL, &tty);
194 if (r < 0)
195 return r;
196
197 v = vtnr_from_tty(tty);
198 if (v < 0)
199 return v;
200 else if (v == 0)
201 return -ENOENT;
202
203 if (seat)
204 *seat = "seat0";
205 *vtnr = (uint32_t) v;
206
207 return 0;
208 }
209
210 static int export_legacy_dbus_address(
211 pam_handle_t *handle,
212 uid_t uid,
213 const char *runtime) {
214
215 const char *s;
216 _cleanup_free_ char *t = NULL;
217 int r = PAM_BUF_ERR;
218
219 /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect
220 * correctly to the bus without it. This setting matches what dbus.socket does for the user
221 * session using 'systemctl --user set-environment'. We want to have the same configuration
222 * in processes started from the PAM session.
223 *
224 * The setting of the address is guarded by the access() check because it is also possible to compile
225 * dbus without --enable-user-session, in which case this socket is not used, and
226 * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access()
227 * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we
228 * expect the socket to be present by the time we do this check, so we can just as well check once
229 * here. */
230
231 s = strjoina(runtime, "/bus");
232 if (access(s, F_OK) < 0)
233 return PAM_SUCCESS;
234
235 if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
236 goto error;
237
238 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0);
239 if (r != PAM_SUCCESS)
240 goto error;
241
242 return PAM_SUCCESS;
243
244 error:
245 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
246 return r;
247 }
248
249 static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
250 uint64_t val;
251 int r;
252
253 if (isempty(limit))
254 return 0;
255
256 if (streq(limit, "infinity")) {
257 r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", (uint64_t)-1);
258 if (r < 0) {
259 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
260 return r;
261 }
262 } else {
263 r = parse_permille(limit);
264 if (r >= 0) {
265 r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", (uint32_t) (((uint64_t) r * UINT32_MAX) / 1000U));
266 if (r < 0) {
267 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
268 return r;
269 }
270 } else {
271 r = parse_size(limit, 1024, &val);
272 if (r >= 0) {
273 r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
274 if (r < 0) {
275 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
276 return r;
277 }
278 } else
279 pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max: %s, ignoring.", limit);
280 }
281 }
282
283 return 0;
284 }
285
286 static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
287 usec_t val;
288 int r;
289
290 /* No need to parse "infinity" here, it will be set by default later in scope_init() */
291 if (isempty(limit) || streq(limit, "infinity"))
292 return 0;
293
294 r = parse_sec(limit, &val);
295 if (r >= 0) {
296 r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
297 if (r < 0) {
298 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
299 return r;
300 }
301 } else
302 pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit);
303
304 return 0;
305 }
306
307 static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
308 uint64_t val;
309 int r;
310
311 /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */
312 if (isempty(limit) || streq(limit, "infinity"))
313 return 0;
314
315 r = safe_atou64(limit, &val);
316 if (r >= 0) {
317 r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val);
318 if (r < 0) {
319 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
320 return r;
321 }
322 } else
323 pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max: %s, ignoring.", limit);
324
325 return 0;
326 }
327
328 static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit, const char *field) {
329 uint64_t val;
330 int r;
331
332 if (isempty(limit))
333 return 0;
334
335 r = cg_weight_parse(limit, &val);
336 if (r >= 0) {
337 r = sd_bus_message_append(m, "(sv)", field, "t", val);
338 if (r < 0) {
339 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
340 return r;
341 }
342 } else if (streq(field, "CPUWeight"))
343 pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight: %s, ignoring.", limit);
344 else
345 pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight: %s, ignoring.", limit);
346
347 return 0;
348 }
349
350 static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) {
351 const char *v;
352
353 assert(handle);
354 assert(key);
355
356 /* Looks for an environment variable, preferably in the environment block associated with the
357 * specified PAM handle, falling back to the process' block instead. Why check both? Because we want
358 * to permit configuration of session properties from unit files that invoke PAM services, so that
359 * PAM services don't have to be reworked to set systemd-specific properties, but these properties
360 * can still be set from the unit file Environment= block. */
361
362 v = pam_getenv(handle, key);
363 if (!isempty(v))
364 return v;
365
366 /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally
367 * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they
368 * currently don't (to be precise, they clean up the environment they pass to their children, but
369 * not their own environ[]). */
370 v = secure_getenv(key);
371 if (!isempty(v))
372 return v;
373
374 return fallback;
375 }
376
377 static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
378 int r;
379
380 assert(handle);
381 assert(key);
382
383 /* Updates the environment, but only if there's actually a value set. Also, log about errors */
384
385 if (isempty(value))
386 return PAM_SUCCESS;
387
388 r = pam_misc_setenv(handle, key, value, 0);
389 if (r != PAM_SUCCESS)
390 pam_syslog(handle, LOG_ERR, "Failed to set environment variable %s: %s", key, pam_strerror(handle, r));
391
392 return r;
393 }
394
395 static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) {
396 struct stat st;
397
398 assert(handle);
399 assert(path);
400
401 /* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set
402 * up properly for us. */
403
404 if (lstat(path, &st) < 0) {
405 pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno));
406 goto fail;
407 }
408
409 if (!S_ISDIR(st.st_mode)) {
410 pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path);
411 goto fail;
412 }
413
414 if (st.st_uid != uid) {
415 pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid);
416 goto fail;
417 }
418
419 return true;
420
421 fail:
422 pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order.");
423 return false;
424 }
425
426 _public_ PAM_EXTERN int pam_sm_open_session(
427 pam_handle_t *handle,
428 int flags,
429 int argc, const char **argv) {
430
431 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
432 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
433 const char
434 *username, *id, *object_path, *runtime_path,
435 *service = NULL,
436 *tty = NULL, *display = NULL,
437 *remote_user = NULL, *remote_host = NULL,
438 *seat = NULL,
439 *type = NULL, *class = NULL,
440 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
441 *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
442 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
443 int session_fd = -1, existing, r;
444 bool debug = false, remote;
445 struct passwd *pw;
446 uint32_t vtnr = 0;
447 uid_t original_uid;
448
449 assert(handle);
450
451 /* Make this a NOP on non-logind systems */
452 if (!logind_running())
453 return PAM_SUCCESS;
454
455 if (parse_argv(handle,
456 argc, argv,
457 &class_pam,
458 &type_pam,
459 &desktop_pam,
460 &debug) < 0)
461 return PAM_SESSION_ERR;
462
463 if (debug)
464 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
465
466 r = get_user_data(handle, &username, &pw);
467 if (r != PAM_SUCCESS)
468 return r;
469
470 /* Make sure we don't enter a loop by talking to
471 * systemd-logind when it is actually waiting for the
472 * background to finish start-up. If the service is
473 * "systemd-user" we simply set XDG_RUNTIME_DIR and
474 * leave. */
475
476 (void) pam_get_item(handle, PAM_SERVICE, (const void**) &service);
477 if (streq_ptr(service, "systemd-user")) {
478 char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
479
480 xsprintf(rt, "/run/user/"UID_FMT, pw->pw_uid);
481 if (validate_runtime_directory(handle, rt, pw->pw_uid)) {
482 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
483 if (r != PAM_SUCCESS) {
484 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
485 return r;
486 }
487 }
488
489 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
490 if (r != PAM_SUCCESS)
491 return r;
492
493 return PAM_SUCCESS;
494 }
495
496 /* Otherwise, we ask logind to create a session for us */
497
498 (void) pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
499 (void) pam_get_item(handle, PAM_TTY, (const void**) &tty);
500 (void) pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
501 (void) pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
502
503 seat = getenv_harder(handle, "XDG_SEAT", NULL);
504 cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
505 type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
506 class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
507 desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
508
509 tty = strempty(tty);
510
511 if (strchr(tty, ':')) {
512 /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
513 * and don't pretend that an X display was a tty. */
514 if (isempty(display))
515 display = tty;
516 tty = NULL;
517
518 } else if (streq(tty, "cron")) {
519 /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
520 * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
521 * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
522 * off processes.) */
523 type = "unspecified";
524 class = "background";
525 tty = NULL;
526
527 } else if (streq(tty, "ssh")) {
528 /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
529 * details look for "PAM_TTY_KLUDGE" in the openssh sources). */
530 type ="tty";
531 class = "user";
532 tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
533 * associated with a pty — won't be tracked by their tty in logind. This is because ssh
534 * does the PAM session registration early for new connections, and registers a pty only
535 * much later (this is because it doesn't know yet if it needs one at all, as whether to
536 * register a pty or not is negotiated much later in the protocol). */
537
538 } else
539 /* Chop off leading /dev prefix that some clients specify, but others do not. */
540 tty = skip_dev_prefix(tty);
541
542 /* If this fails vtnr will be 0, that's intended */
543 if (!isempty(cvtnr))
544 (void) safe_atou32(cvtnr, &vtnr);
545
546 if (!isempty(display) && !vtnr) {
547 if (isempty(seat))
548 (void) get_seat_from_display(display, &seat, &vtnr);
549 else if (streq(seat, "seat0"))
550 (void) get_seat_from_display(display, NULL, &vtnr);
551 }
552
553 if (seat && !streq(seat, "seat0") && vtnr != 0) {
554 if (debug)
555 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
556 vtnr = 0;
557 }
558
559 if (isempty(type))
560 type = !isempty(display) ? "x11" :
561 !isempty(tty) ? "tty" : "unspecified";
562
563 if (isempty(class))
564 class = streq(type, "unspecified") ? "background" : "user";
565
566 remote = !isempty(remote_host) && !is_localhost(remote_host);
567
568 (void) pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max);
569 (void) pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max);
570 (void) pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight);
571 (void) pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight);
572 (void) pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec);
573
574 /* Talk to logind over the message bus */
575
576 r = sd_bus_open_system(&bus);
577 if (r < 0) {
578 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror_safe(r));
579 return PAM_SESSION_ERR;
580 }
581
582 if (debug) {
583 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
584 "uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
585 pw->pw_uid, getpid_cached(),
586 strempty(service),
587 type, class, strempty(desktop),
588 strempty(seat), vtnr, strempty(tty), strempty(display),
589 yes_no(remote), strempty(remote_user), strempty(remote_host));
590 pam_syslog(handle, LOG_DEBUG, "Session limits: "
591 "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
592 strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
593 }
594
595 r = sd_bus_message_new_method_call(
596 bus,
597 &m,
598 "org.freedesktop.login1",
599 "/org/freedesktop/login1",
600 "org.freedesktop.login1.Manager",
601 "CreateSession");
602 if (r < 0) {
603 pam_syslog(handle, LOG_ERR, "Failed to create CreateSession method call: %s", strerror_safe(r));
604 return PAM_SESSION_ERR;
605 }
606
607 r = sd_bus_message_append(m, "uusssssussbss",
608 (uint32_t) pw->pw_uid,
609 0,
610 service,
611 type,
612 class,
613 desktop,
614 seat,
615 vtnr,
616 tty,
617 display,
618 remote,
619 remote_user,
620 remote_host);
621 if (r < 0) {
622 pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
623 return PAM_SESSION_ERR;
624 }
625
626 r = sd_bus_message_open_container(m, 'a', "(sv)");
627 if (r < 0) {
628 pam_syslog(handle, LOG_ERR, "Failed to open message container: %s", strerror_safe(r));
629 return PAM_SYSTEM_ERR;
630 }
631
632 r = append_session_memory_max(handle, m, memory_max);
633 if (r < 0)
634 return PAM_SESSION_ERR;
635
636 r = append_session_runtime_max_sec(handle, m, runtime_max_sec);
637 if (r < 0)
638 return PAM_SESSION_ERR;
639
640 r = append_session_tasks_max(handle, m, tasks_max);
641 if (r < 0)
642 return PAM_SESSION_ERR;
643
644 r = append_session_cg_weight(handle, m, cpu_weight, "CPUWeight");
645 if (r < 0)
646 return PAM_SESSION_ERR;
647
648 r = append_session_cg_weight(handle, m, io_weight, "IOWeight");
649 if (r < 0)
650 return PAM_SESSION_ERR;
651
652 r = sd_bus_message_close_container(m);
653 if (r < 0) {
654 pam_syslog(handle, LOG_ERR, "Failed to close message container: %s", strerror_safe(r));
655 return PAM_SYSTEM_ERR;
656 }
657
658 r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
659 if (r < 0) {
660 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
661 if (debug)
662 pam_syslog(handle, LOG_DEBUG, "Not creating session: %s", bus_error_message(&error, r));
663 return PAM_SUCCESS;
664 } else {
665 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
666 return PAM_SESSION_ERR;
667 }
668 }
669
670 r = sd_bus_message_read(reply,
671 "soshusub",
672 &id,
673 &object_path,
674 &runtime_path,
675 &session_fd,
676 &original_uid,
677 &seat,
678 &vtnr,
679 &existing);
680 if (r < 0) {
681 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror_safe(r));
682 return PAM_SESSION_ERR;
683 }
684
685 if (debug)
686 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
687 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
688 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
689
690 r = update_environment(handle, "XDG_SESSION_ID", id);
691 if (r != PAM_SUCCESS)
692 return r;
693
694 if (original_uid == pw->pw_uid) {
695 /* Don't set $XDG_RUNTIME_DIR if the user we now
696 * authenticated for does not match the original user
697 * of the session. We do this in order not to result
698 * in privileged apps clobbering the runtime directory
699 * unnecessarily. */
700
701 if (validate_runtime_directory(handle, runtime_path, pw->pw_uid)) {
702 r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
703 if (r != PAM_SUCCESS)
704 return r;
705 }
706
707 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
708 if (r != PAM_SUCCESS)
709 return r;
710 }
711
712 /* Most likely we got the session/type/class from environment variables, but might have gotten the data
713 * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
714 * data is inherited into the session processes, and programs can rely on them to be initialized. */
715
716 r = update_environment(handle, "XDG_SESSION_TYPE", type);
717 if (r != PAM_SUCCESS)
718 return r;
719
720 r = update_environment(handle, "XDG_SESSION_CLASS", class);
721 if (r != PAM_SUCCESS)
722 return r;
723
724 r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop);
725 if (r != PAM_SUCCESS)
726 return r;
727
728 r = update_environment(handle, "XDG_SEAT", seat);
729 if (r != PAM_SUCCESS)
730 return r;
731
732 if (vtnr > 0) {
733 char buf[DECIMAL_STR_MAX(vtnr)];
734 sprintf(buf, "%u", vtnr);
735
736 r = update_environment(handle, "XDG_VTNR", buf);
737 if (r != PAM_SUCCESS)
738 return r;
739 }
740
741 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
742 if (r != PAM_SUCCESS) {
743 pam_syslog(handle, LOG_ERR, "Failed to install existing flag: %s", pam_strerror(handle, r));
744 return r;
745 }
746
747 if (session_fd >= 0) {
748 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
749 if (session_fd < 0) {
750 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
751 return PAM_SESSION_ERR;
752 }
753
754 r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL);
755 if (r != PAM_SUCCESS) {
756 pam_syslog(handle, LOG_ERR, "Failed to install session fd: %s", pam_strerror(handle, r));
757 safe_close(session_fd);
758 return r;
759 }
760 }
761
762 return PAM_SUCCESS;
763 }
764
765 _public_ PAM_EXTERN int pam_sm_close_session(
766 pam_handle_t *handle,
767 int flags,
768 int argc, const char **argv) {
769
770 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
771 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
772 const void *existing = NULL;
773 const char *id;
774 int r;
775
776 assert(handle);
777
778 /* Only release session if it wasn't pre-existing when we
779 * tried to create it */
780 (void) pam_get_data(handle, "systemd.existing", &existing);
781
782 id = pam_getenv(handle, "XDG_SESSION_ID");
783 if (id && !existing) {
784
785 /* Before we go and close the FIFO we need to tell
786 * logind that this is a clean session shutdown, so
787 * that it doesn't just go and slaughter us
788 * immediately after closing the fd */
789
790 r = sd_bus_open_system(&bus);
791 if (r < 0) {
792 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror_safe(r));
793 return PAM_SESSION_ERR;
794 }
795
796 r = sd_bus_call_method(bus,
797 "org.freedesktop.login1",
798 "/org/freedesktop/login1",
799 "org.freedesktop.login1.Manager",
800 "ReleaseSession",
801 &error,
802 NULL,
803 "s",
804 id);
805 if (r < 0) {
806 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
807 return PAM_SESSION_ERR;
808 }
809 }
810
811 /* Note that we are knowingly leaking the FIFO fd here. This
812 * way, logind can watch us die. If we closed it here it would
813 * not have any clue when that is completed. Given that one
814 * cannot really have multiple PAM sessions open from the same
815 * process this means we will leak one FD at max. */
816
817 return PAM_SUCCESS;
818 }