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