]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #8575 from keszybz/non-absolute-paths
[thirdparty/systemd.git] / src / login / pam_systemd.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <endian.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <pwd.h>
12 #include <security/_pam_macros.h>
13 #include <security/pam_ext.h>
14 #include <security/pam_misc.h>
15 #include <security/pam_modules.h>
16 #include <security/pam_modutil.h>
17 #include <sys/file.h>
18
19 #include "alloc-util.h"
20 #include "audit-util.h"
21 #include "bus-common-errors.h"
22 #include "bus-error.h"
23 #include "bus-util.h"
24 #include "def.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 "process-util.h"
33 #include "socket-util.h"
34 #include "strv.h"
35 #include "terminal-util.h"
36 #include "util.h"
37 #include "path-util.h"
38
39 static int parse_argv(
40 pam_handle_t *handle,
41 int argc, const char **argv,
42 const char **class,
43 const char **type,
44 bool *debug) {
45
46 unsigned i;
47
48 assert(argc >= 0);
49 assert(argc == 0 || argv);
50
51 for (i = 0; i < (unsigned) argc; i++) {
52 if (startswith(argv[i], "class=")) {
53 if (class)
54 *class = argv[i] + 6;
55
56 } else if (startswith(argv[i], "type=")) {
57 if (type)
58 *type = argv[i] + 5;
59
60 } else if (streq(argv[i], "debug")) {
61 if (debug)
62 *debug = true;
63
64 } else if (startswith(argv[i], "debug=")) {
65 int k;
66
67 k = parse_boolean(argv[i] + 6);
68 if (k < 0)
69 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
70 else if (debug)
71 *debug = k;
72
73 } else
74 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
75 }
76
77 return 0;
78 }
79
80 static int get_user_data(
81 pam_handle_t *handle,
82 const char **ret_username,
83 struct passwd **ret_pw) {
84
85 const char *username = NULL;
86 struct passwd *pw = NULL;
87 int r;
88
89 assert(handle);
90 assert(ret_username);
91 assert(ret_pw);
92
93 r = pam_get_user(handle, &username, NULL);
94 if (r != PAM_SUCCESS) {
95 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
96 return r;
97 }
98
99 if (isempty(username)) {
100 pam_syslog(handle, LOG_ERR, "User name not valid.");
101 return PAM_AUTH_ERR;
102 }
103
104 pw = pam_modutil_getpwnam(handle, username);
105 if (!pw) {
106 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
107 return PAM_USER_UNKNOWN;
108 }
109
110 *ret_pw = pw;
111 *ret_username = username;
112
113 return PAM_SUCCESS;
114 }
115
116 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
117 union sockaddr_union sa = {
118 .un.sun_family = AF_UNIX,
119 };
120 _cleanup_free_ char *p = NULL, *tty = NULL;
121 _cleanup_close_ int fd = -1;
122 struct ucred ucred;
123 int v, r;
124
125 assert(display);
126 assert(vtnr);
127
128 /* We deduce the X11 socket from the display name, then use
129 * SO_PEERCRED to determine the X11 server process, ask for
130 * the controlling tty of that and if it's a VC then we know
131 * the seat and the virtual terminal. Sounds ugly, is only
132 * semi-ugly. */
133
134 r = socket_from_display(display, &p);
135 if (r < 0)
136 return r;
137 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
138
139 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
140 if (fd < 0)
141 return -errno;
142
143 if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
144 return -errno;
145
146 r = getpeercred(fd, &ucred);
147 if (r < 0)
148 return r;
149
150 r = get_ctty(ucred.pid, NULL, &tty);
151 if (r < 0)
152 return r;
153
154 v = vtnr_from_tty(tty);
155 if (v < 0)
156 return v;
157 else if (v == 0)
158 return -ENOENT;
159
160 if (seat)
161 *seat = "seat0";
162 *vtnr = (uint32_t) v;
163
164 return 0;
165 }
166
167 static int export_legacy_dbus_address(
168 pam_handle_t *handle,
169 uid_t uid,
170 const char *runtime) {
171
172 _cleanup_free_ char *s = NULL;
173 int r = PAM_BUF_ERR;
174
175 /* FIXME: We *really* should move the access() check into the
176 * daemons that spawn dbus-daemon, instead of forcing
177 * DBUS_SESSION_BUS_ADDRESS= here. */
178
179 s = strjoin(runtime, "/bus");
180 if (!s)
181 goto error;
182
183 if (access(s, F_OK) < 0)
184 return PAM_SUCCESS;
185
186 s = mfree(s);
187 if (asprintf(&s, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
188 goto error;
189
190 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
191 if (r != PAM_SUCCESS)
192 goto error;
193
194 return PAM_SUCCESS;
195
196 error:
197 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
198 return r;
199 }
200
201 _public_ PAM_EXTERN int pam_sm_open_session(
202 pam_handle_t *handle,
203 int flags,
204 int argc, const char **argv) {
205
206 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
207 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
208 const char
209 *username, *id, *object_path, *runtime_path,
210 *service = NULL,
211 *tty = NULL, *display = NULL,
212 *remote_user = NULL, *remote_host = NULL,
213 *seat = NULL,
214 *type = NULL, *class = NULL,
215 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
216 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
217 int session_fd = -1, existing, r;
218 bool debug = false, remote;
219 struct passwd *pw;
220 uint32_t vtnr = 0;
221 uid_t original_uid;
222
223 assert(handle);
224
225 /* Make this a NOP on non-logind systems */
226 if (!logind_running())
227 return PAM_SUCCESS;
228
229 if (parse_argv(handle,
230 argc, argv,
231 &class_pam,
232 &type_pam,
233 &debug) < 0)
234 return PAM_SESSION_ERR;
235
236 if (debug)
237 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
238
239 r = get_user_data(handle, &username, &pw);
240 if (r != PAM_SUCCESS) {
241 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
242 return r;
243 }
244
245 /* Make sure we don't enter a loop by talking to
246 * systemd-logind when it is actually waiting for the
247 * background to finish start-up. If the service is
248 * "systemd-user" we simply set XDG_RUNTIME_DIR and
249 * leave. */
250
251 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
252 if (streq_ptr(service, "systemd-user")) {
253 _cleanup_free_ char *rt = NULL;
254
255 if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
256 return PAM_BUF_ERR;
257
258 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
259 if (r != PAM_SUCCESS) {
260 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
261 return r;
262 }
263
264 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
265 if (r != PAM_SUCCESS)
266 return r;
267
268 return PAM_SUCCESS;
269 }
270
271 /* Otherwise, we ask logind to create a session for us */
272
273 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
274 pam_get_item(handle, PAM_TTY, (const void**) &tty);
275 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
276 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
277
278 seat = pam_getenv(handle, "XDG_SEAT");
279 if (isempty(seat))
280 seat = getenv("XDG_SEAT");
281
282 cvtnr = pam_getenv(handle, "XDG_VTNR");
283 if (isempty(cvtnr))
284 cvtnr = getenv("XDG_VTNR");
285
286 type = pam_getenv(handle, "XDG_SESSION_TYPE");
287 if (isempty(type))
288 type = getenv("XDG_SESSION_TYPE");
289 if (isempty(type))
290 type = type_pam;
291
292 class = pam_getenv(handle, "XDG_SESSION_CLASS");
293 if (isempty(class))
294 class = getenv("XDG_SESSION_CLASS");
295 if (isempty(class))
296 class = class_pam;
297
298 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
299 if (isempty(desktop))
300 desktop = getenv("XDG_SESSION_DESKTOP");
301
302 tty = strempty(tty);
303
304 if (strchr(tty, ':')) {
305 /* A tty with a colon is usually an X11 display,
306 * placed there to show up in utmp. We rearrange
307 * things and don't pretend that an X display was a
308 * tty. */
309
310 if (isempty(display))
311 display = tty;
312 tty = NULL;
313 } else if (streq(tty, "cron")) {
314 /* cron has been setting PAM_TTY to "cron" for a very
315 * long time and it probably shouldn't stop doing that
316 * for compatibility reasons. */
317 type = "unspecified";
318 class = "background";
319 tty = NULL;
320 } else if (streq(tty, "ssh")) {
321 /* ssh has been setting PAM_TTY to "ssh" for a very
322 * long time and probably shouldn't stop doing that
323 * for compatibility reasons. */
324 type ="tty";
325 class = "user";
326 tty = NULL;
327 } else
328 /* Chop off leading /dev prefix that some clients specify, but others do not. */
329 tty = skip_dev_prefix(tty);
330
331 /* If this fails vtnr will be 0, that's intended */
332 if (!isempty(cvtnr))
333 (void) safe_atou32(cvtnr, &vtnr);
334
335 if (!isempty(display) && !vtnr) {
336 if (isempty(seat))
337 get_seat_from_display(display, &seat, &vtnr);
338 else if (streq(seat, "seat0"))
339 get_seat_from_display(display, NULL, &vtnr);
340 }
341
342 if (seat && !streq(seat, "seat0") && vtnr != 0) {
343 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
344 vtnr = 0;
345 }
346
347 if (isempty(type))
348 type = !isempty(display) ? "x11" :
349 !isempty(tty) ? "tty" : "unspecified";
350
351 if (isempty(class))
352 class = streq(type, "unspecified") ? "background" : "user";
353
354 remote = !isempty(remote_host) && !is_localhost(remote_host);
355
356 /* Talk to logind over the message bus */
357
358 r = sd_bus_open_system(&bus);
359 if (r < 0) {
360 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
361 return PAM_SESSION_ERR;
362 }
363
364 if (debug)
365 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
366 "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",
367 pw->pw_uid, getpid_cached(),
368 strempty(service),
369 type, class, strempty(desktop),
370 strempty(seat), vtnr, strempty(tty), strempty(display),
371 yes_no(remote), strempty(remote_user), strempty(remote_host));
372
373 r = sd_bus_call_method(bus,
374 "org.freedesktop.login1",
375 "/org/freedesktop/login1",
376 "org.freedesktop.login1.Manager",
377 "CreateSession",
378 &error,
379 &reply,
380 "uusssssussbssa(sv)",
381 (uint32_t) pw->pw_uid,
382 (uint32_t) getpid_cached(),
383 service,
384 type,
385 class,
386 desktop,
387 seat,
388 vtnr,
389 tty,
390 display,
391 remote,
392 remote_user,
393 remote_host,
394 0);
395 if (r < 0) {
396 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
397 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
398 return PAM_SUCCESS;
399 } else {
400 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
401 return PAM_SYSTEM_ERR;
402 }
403 }
404
405 r = sd_bus_message_read(reply,
406 "soshusub",
407 &id,
408 &object_path,
409 &runtime_path,
410 &session_fd,
411 &original_uid,
412 &seat,
413 &vtnr,
414 &existing);
415 if (r < 0) {
416 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
417 return PAM_SESSION_ERR;
418 }
419
420 if (debug)
421 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
422 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
423 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
424
425 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
426 if (r != PAM_SUCCESS) {
427 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
428 return r;
429 }
430
431 if (original_uid == pw->pw_uid) {
432 /* Don't set $XDG_RUNTIME_DIR if the user we now
433 * authenticated for does not match the original user
434 * of the session. We do this in order not to result
435 * in privileged apps clobbering the runtime directory
436 * unnecessarily. */
437
438 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
439 if (r != PAM_SUCCESS) {
440 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
441 return r;
442 }
443
444 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
445 if (r != PAM_SUCCESS)
446 return r;
447 }
448
449 if (!isempty(seat)) {
450 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
451 if (r != PAM_SUCCESS) {
452 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
453 return r;
454 }
455 }
456
457 if (vtnr > 0) {
458 char buf[DECIMAL_STR_MAX(vtnr)];
459 sprintf(buf, "%u", vtnr);
460
461 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
462 if (r != PAM_SUCCESS) {
463 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
464 return r;
465 }
466 }
467
468 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
469 if (r != PAM_SUCCESS) {
470 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
471 return r;
472 }
473
474 if (session_fd >= 0) {
475 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
476 if (session_fd < 0) {
477 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
478 return PAM_SESSION_ERR;
479 }
480
481 r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL);
482 if (r != PAM_SUCCESS) {
483 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
484 safe_close(session_fd);
485 return r;
486 }
487 }
488
489 return PAM_SUCCESS;
490 }
491
492 _public_ PAM_EXTERN int pam_sm_close_session(
493 pam_handle_t *handle,
494 int flags,
495 int argc, const char **argv) {
496
497 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
498 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
499 const void *existing = NULL;
500 const char *id;
501 int r;
502
503 assert(handle);
504
505 /* Only release session if it wasn't pre-existing when we
506 * tried to create it */
507 pam_get_data(handle, "systemd.existing", &existing);
508
509 id = pam_getenv(handle, "XDG_SESSION_ID");
510 if (id && !existing) {
511
512 /* Before we go and close the FIFO we need to tell
513 * logind that this is a clean session shutdown, so
514 * that it doesn't just go and slaughter us
515 * immediately after closing the fd */
516
517 r = sd_bus_open_system(&bus);
518 if (r < 0) {
519 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
520 return PAM_SESSION_ERR;
521 }
522
523 r = sd_bus_call_method(bus,
524 "org.freedesktop.login1",
525 "/org/freedesktop/login1",
526 "org.freedesktop.login1.Manager",
527 "ReleaseSession",
528 &error,
529 NULL,
530 "s",
531 id);
532 if (r < 0) {
533 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
534 return PAM_SESSION_ERR;
535 }
536 }
537
538 /* Note that we are knowingly leaking the FIFO fd here. This
539 * way, logind can watch us die. If we closed it here it would
540 * not have any clue when that is completed. Given that one
541 * cannot really have multiple PAM sessions open from the same
542 * process this means we will leak one FD at max. */
543
544 return PAM_SUCCESS;
545 }