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 ? username : pw->pw_name;
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("/dev/kdbus", F_OK) < 0)
186 if (asprintf(&s, KERNEL_USER_BUS_FMT ";" UNIX_USER_BUS_FMT,
188 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
192 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
193 if (r != PAM_SUCCESS) {
194 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
201 _public_ PAM_EXTERN int pam_sm_open_session(
202 pam_handle_t *handle,
204 int argc, const char **argv) {
206 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
207 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
209 *username, *id, *object_path, *runtime_path,
211 *tty = NULL, *display = NULL,
212 *remote_user = NULL, *remote_host = NULL,
214 *type = NULL, *class = NULL,
215 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
216 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
217 int session_fd = -1, existing, r;
218 bool debug = false, remote;
225 /* Make this a NOP on non-logind systems */
226 if (!logind_running())
229 if (parse_argv(handle,
234 return PAM_SESSION_ERR;
237 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
239 r = get_user_data(handle, &username, &pw);
240 if (r != PAM_SUCCESS) {
241 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
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
251 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
252 if (streq_ptr(service, "systemd-user")) {
253 _cleanup_free_ char *p = NULL, *rt = NULL;
255 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
258 r = parse_env_file(p, NEWLINE,
261 if (r < 0 && r != -ENOENT)
262 return PAM_SESSION_ERR;
265 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
266 if (r != PAM_SUCCESS) {
267 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
271 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
272 if (r != PAM_SUCCESS)
279 /* Otherwise, we ask logind to create a session for us */
281 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
282 pam_get_item(handle, PAM_TTY, (const void**) &tty);
283 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
284 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
286 seat = pam_getenv(handle, "XDG_SEAT");
288 seat = getenv("XDG_SEAT");
290 cvtnr = pam_getenv(handle, "XDG_VTNR");
292 cvtnr = getenv("XDG_VTNR");
294 type = pam_getenv(handle, "XDG_SESSION_TYPE");
296 type = getenv("XDG_SESSION_TYPE");
300 class = pam_getenv(handle, "XDG_SESSION_CLASS");
302 class = getenv("XDG_SESSION_CLASS");
306 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
307 if (isempty(desktop))
308 desktop = getenv("XDG_SESSION_DESKTOP");
312 if (strchr(tty, ':')) {
313 /* A tty with a colon is usually an X11 display,
314 * placed there to show up in utmp. We rearrange
315 * things and don't pretend that an X display was a
318 if (isempty(display))
321 } else if (streq(tty, "cron")) {
322 /* cron has been setting PAM_TTY to "cron" for a very
323 * long time and it probably shouldn't stop doing that
324 * for compatibility reasons. */
325 type = "unspecified";
326 class = "background";
328 } else if (streq(tty, "ssh")) {
329 /* ssh has been setting PAM_TTY to "ssh" for a very
330 * long time and probably shouldn't stop doing that
331 * for compatibility reasons. */
337 /* If this fails vtnr will be 0, that's intended */
339 safe_atou32(cvtnr, &vtnr);
341 if (!isempty(display) && !vtnr) {
343 get_seat_from_display(display, &seat, &vtnr);
344 else if (streq(seat, "seat0"))
345 get_seat_from_display(display, NULL, &vtnr);
348 if (seat && !streq(seat, "seat0") && vtnr != 0) {
349 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %d for %s which is not seat0", vtnr, seat);
354 type = !isempty(display) ? "x11" :
355 !isempty(tty) ? "tty" : "unspecified";
358 class = streq(type, "unspecified") ? "background" : "user";
360 remote = !isempty(remote_host) && !is_localhost(remote_host);
362 /* Talk to logind over the message bus */
364 r = sd_bus_open_system(&bus);
366 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
367 return PAM_SESSION_ERR;
371 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
372 "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",
373 pw->pw_uid, getpid(),
375 type, class, strempty(desktop),
376 strempty(seat), vtnr, strempty(tty), strempty(display),
377 yes_no(remote), strempty(remote_user), strempty(remote_host));
379 r = sd_bus_call_method(bus,
380 "org.freedesktop.login1",
381 "/org/freedesktop/login1",
382 "org.freedesktop.login1.Manager",
386 "uusssssussbssa(sv)",
387 (uint32_t) pw->pw_uid,
402 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
403 return PAM_SYSTEM_ERR;
406 r = sd_bus_message_read(reply,
417 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
418 return PAM_SESSION_ERR;
422 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
423 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
424 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
426 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
427 if (r != PAM_SUCCESS) {
428 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
432 if (original_uid == pw->pw_uid) {
433 /* Don't set $XDG_RUNTIME_DIR if the user we now
434 * authenticated for does not match the original user
435 * of the session. We do this in order not to result
436 * in privileged apps clobbering the runtime directory
439 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
440 if (r != PAM_SUCCESS) {
441 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
445 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
446 if (r != PAM_SUCCESS)
450 if (!isempty(seat)) {
451 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
452 if (r != PAM_SUCCESS) {
453 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
459 char buf[DECIMAL_STR_MAX(vtnr)];
460 sprintf(buf, "%u", vtnr);
462 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
463 if (r != PAM_SUCCESS) {
464 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
469 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
470 if (r != PAM_SUCCESS) {
471 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
475 if (session_fd >= 0) {
476 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
477 if (session_fd < 0) {
478 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
479 return PAM_SESSION_ERR;
482 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
483 if (r != PAM_SUCCESS) {
484 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
485 safe_close(session_fd);
493 _public_ PAM_EXTERN int pam_sm_close_session(
494 pam_handle_t *handle,
496 int argc, const char **argv) {
498 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
499 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
500 const void *existing = NULL;
506 /* Only release session if it wasn't pre-existing when we
507 * tried to create it */
508 pam_get_data(handle, "systemd.existing", &existing);
510 id = pam_getenv(handle, "XDG_SESSION_ID");
511 if (id && !existing) {
513 /* Before we go and close the FIFO we need to tell
514 * logind that this is a clean session shutdown, so
515 * that it doesn't just go and slaughter us
516 * immediately after closing the fd */
518 r = sd_bus_open_system(&bus);
520 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
521 return PAM_SESSION_ERR;
524 r = sd_bus_call_method(bus,
525 "org.freedesktop.login1",
526 "/org/freedesktop/login1",
527 "org.freedesktop.login1.Manager",
534 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
535 return PAM_SESSION_ERR;
539 /* Note that we are knowingly leaking the FIFO fd here. This
540 * way, logind can watch us die. If we closed it here it would
541 * not have any clue when that is completed. Given that one
542 * cannot really have multiple PAM sessions open from the same
543 * process this means we will leak one FD at max. */