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