1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
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.
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.
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/>.
27 #include <sys/capability.h>
29 #include <security/pam_modules.h>
30 #include <security/_pam_macros.h>
31 #include <security/pam_modutil.h>
32 #include <security/pam_ext.h>
33 #include <security/pam_misc.h>
41 #include "socket-util.h"
43 #include "bus-error.h"
45 static int parse_argv(
47 int argc, const char **argv,
55 assert(argc == 0 || argv);
57 for (i = 0; i < (unsigned) argc; i++) {
58 if (startswith(argv[i], "class=")) {
62 } else if (startswith(argv[i], "type=")) {
66 } else if (streq(argv[i], "debug")) {
70 } else if (startswith(argv[i], "debug=")) {
73 k = parse_boolean(argv[i] + 6);
75 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
80 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
86 static int get_user_data(
88 const char **ret_username,
89 struct passwd **ret_pw) {
91 const char *username = NULL;
92 struct passwd *pw = NULL;
99 r = pam_get_user(handle, &username, NULL);
100 if (r != PAM_SUCCESS) {
101 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
105 if (isempty(username)) {
106 pam_syslog(handle, LOG_ERR, "User name not valid.");
110 pw = pam_modutil_getpwnam(handle, username);
112 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
113 return PAM_USER_UNKNOWN;
117 *ret_username = username;
122 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
123 union sockaddr_union sa = {
124 .un.sun_family = AF_UNIX,
126 _cleanup_free_ char *p = NULL, *tty = NULL;
127 _cleanup_close_ int fd = -1;
134 /* We deduce the X11 socket from the display name, then use
135 * SO_PEERCRED to determine the X11 server process, ask for
136 * the controlling tty of that and if it's a VC then we know
137 * the seat and the virtual terminal. Sounds ugly, is only
140 r = socket_from_display(display, &p);
143 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
145 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
149 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
152 r = getpeercred(fd, &ucred);
156 r = get_ctty(ucred.pid, NULL, &tty);
160 v = vtnr_from_tty(tty);
168 *vtnr = (uint32_t) v;
173 static int export_legacy_dbus_address(
174 pam_handle_t *handle,
176 const char *runtime) {
179 _cleanup_free_ char *s = NULL;
182 /* skip export if kdbus is not active */
183 if (access("/sys/fs/kdbus", F_OK) < 0)
186 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
187 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
191 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
192 if (r != PAM_SUCCESS) {
193 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
200 _public_ PAM_EXTERN int pam_sm_open_session(
201 pam_handle_t *handle,
203 int argc, const char **argv) {
205 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
206 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
208 *username, *id, *object_path, *runtime_path,
210 *tty = NULL, *display = NULL,
211 *remote_user = NULL, *remote_host = NULL,
213 *type = NULL, *class = NULL,
214 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
215 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
216 int session_fd = -1, existing, r;
217 bool debug = false, remote;
224 /* Make this a NOP on non-logind systems */
225 if (!logind_running())
228 if (parse_argv(handle,
233 return PAM_SESSION_ERR;
236 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
238 r = get_user_data(handle, &username, &pw);
239 if (r != PAM_SUCCESS) {
240 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
244 /* Make sure we don't enter a loop by talking to
245 * systemd-logind when it is actually waiting for the
246 * background to finish start-up. If the service is
247 * "systemd-user" we simply set XDG_RUNTIME_DIR and
250 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
251 if (streq_ptr(service, "systemd-user")) {
252 _cleanup_free_ char *p = NULL, *rt = NULL;
254 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
257 r = parse_env_file(p, NEWLINE,
260 if (r < 0 && r != -ENOENT)
261 return PAM_SESSION_ERR;
264 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
265 if (r != PAM_SUCCESS) {
266 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
270 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
271 if (r != PAM_SUCCESS)
278 /* Otherwise, we ask logind to create a session for us */
280 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
281 pam_get_item(handle, PAM_TTY, (const void**) &tty);
282 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
283 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
285 seat = pam_getenv(handle, "XDG_SEAT");
287 seat = getenv("XDG_SEAT");
289 cvtnr = pam_getenv(handle, "XDG_VTNR");
291 cvtnr = getenv("XDG_VTNR");
293 type = pam_getenv(handle, "XDG_SESSION_TYPE");
295 type = getenv("XDG_SESSION_TYPE");
299 class = pam_getenv(handle, "XDG_SESSION_CLASS");
301 class = getenv("XDG_SESSION_CLASS");
305 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
306 if (isempty(desktop))
307 desktop = getenv("XDG_SESSION_DESKTOP");
311 if (strchr(tty, ':')) {
312 /* A tty with a colon is usually an X11 display,
313 * placed there to show up in utmp. We rearrange
314 * things and don't pretend that an X display was a
317 if (isempty(display))
320 } else if (streq(tty, "cron")) {
321 /* cron has been setting PAM_TTY to "cron" for a very
322 * long time and it probably shouldn't stop doing that
323 * for compatibility reasons. */
324 type = "unspecified";
325 class = "background";
327 } else if (streq(tty, "ssh")) {
328 /* ssh has been setting PAM_TTY to "ssh" for a very
329 * long time and probably shouldn't stop doing that
330 * for compatibility reasons. */
336 /* If this fails vtnr will be 0, that's intended */
338 safe_atou32(cvtnr, &vtnr);
340 if (!isempty(display) && !vtnr) {
342 get_seat_from_display(display, &seat, &vtnr);
343 else if (streq(seat, "seat0"))
344 get_seat_from_display(display, NULL, &vtnr);
347 if (seat && !streq(seat, "seat0") && vtnr != 0) {
348 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %d for %s which is not seat0", vtnr, seat);
353 type = !isempty(display) ? "x11" :
354 !isempty(tty) ? "tty" : "unspecified";
357 class = streq(type, "unspecified") ? "background" : "user";
359 remote = !isempty(remote_host) && !is_localhost(remote_host);
361 /* Talk to logind over the message bus */
363 r = sd_bus_open_system(&bus);
365 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
366 return PAM_SESSION_ERR;
370 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
371 "uid=%u pid=%u service=%s type=%s class=%s desktop=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
372 pw->pw_uid, getpid(),
374 type, class, strempty(desktop),
375 strempty(seat), vtnr, strempty(tty), strempty(display),
376 yes_no(remote), strempty(remote_user), strempty(remote_host));
378 r = sd_bus_call_method(bus,
379 "org.freedesktop.login1",
380 "/org/freedesktop/login1",
381 "org.freedesktop.login1.Manager",
385 "uusssssussbssa(sv)",
386 (uint32_t) pw->pw_uid,
401 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
402 return PAM_SYSTEM_ERR;
405 r = sd_bus_message_read(reply,
416 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
417 return PAM_SESSION_ERR;
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);
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.");
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
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.");
444 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
445 if (r != PAM_SUCCESS)
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.");
458 char buf[DECIMAL_STR_MAX(vtnr)];
459 sprintf(buf, "%u", vtnr);
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.");
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.");
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;
481 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
482 if (r != PAM_SUCCESS) {
483 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
484 safe_close(session_fd);
492 _public_ PAM_EXTERN int pam_sm_close_session(
493 pam_handle_t *handle,
495 int argc, const char **argv) {
497 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
498 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
499 const void *existing = NULL;
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);
509 id = pam_getenv(handle, "XDG_SESSION_ID");
510 if (id && !existing) {
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 */
517 r = sd_bus_open_system(&bus);
519 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
520 return PAM_SESSION_ERR;
523 r = sd_bus_call_method(bus,
524 "org.freedesktop.login1",
525 "/org/freedesktop/login1",
526 "org.freedesktop.login1.Manager",
533 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
534 return PAM_SESSION_ERR;
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. */