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/>.
28 #include <security/pam_modules.h>
29 #include <security/_pam_macros.h>
30 #include <security/pam_modutil.h>
31 #include <security/pam_ext.h>
32 #include <security/pam_misc.h>
34 #include "bus-common-errors.h"
41 #include "socket-util.h"
43 #include "bus-error.h"
44 #include "formats-util.h"
45 #include "terminal-util.h"
46 #include "hostname-util.h"
48 static int parse_argv(
50 int argc, const char **argv,
58 assert(argc == 0 || argv);
60 for (i = 0; i < (unsigned) argc; i++) {
61 if (startswith(argv[i], "class=")) {
65 } else if (startswith(argv[i], "type=")) {
69 } else if (streq(argv[i], "debug")) {
73 } else if (startswith(argv[i], "debug=")) {
76 k = parse_boolean(argv[i] + 6);
78 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
83 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
89 static int get_user_data(
91 const char **ret_username,
92 struct passwd **ret_pw) {
94 const char *username = NULL;
95 struct passwd *pw = NULL;
102 r = pam_get_user(handle, &username, NULL);
103 if (r != PAM_SUCCESS) {
104 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
108 if (isempty(username)) {
109 pam_syslog(handle, LOG_ERR, "User name not valid.");
113 pw = pam_modutil_getpwnam(handle, username);
115 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
116 return PAM_USER_UNKNOWN;
120 *ret_username = username;
125 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
126 union sockaddr_union sa = {
127 .un.sun_family = AF_UNIX,
129 _cleanup_free_ char *p = NULL, *tty = NULL;
130 _cleanup_close_ int fd = -1;
137 /* We deduce the X11 socket from the display name, then use
138 * SO_PEERCRED to determine the X11 server process, ask for
139 * the controlling tty of that and if it's a VC then we know
140 * the seat and the virtual terminal. Sounds ugly, is only
143 r = socket_from_display(display, &p);
146 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
148 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
152 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
155 r = getpeercred(fd, &ucred);
159 r = get_ctty(ucred.pid, NULL, &tty);
163 v = vtnr_from_tty(tty);
171 *vtnr = (uint32_t) v;
176 static int export_legacy_dbus_address(
177 pam_handle_t *handle,
179 const char *runtime) {
181 _cleanup_free_ char *s = NULL;
184 /* skip export if kdbus is not active */
185 if (!is_kdbus_available())
188 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
189 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
193 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
194 if (r != PAM_SUCCESS) {
195 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
202 _public_ PAM_EXTERN int pam_sm_open_session(
203 pam_handle_t *handle,
205 int argc, const char **argv) {
207 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
208 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
210 *username, *id, *object_path, *runtime_path,
212 *tty = NULL, *display = NULL,
213 *remote_user = NULL, *remote_host = NULL,
215 *type = NULL, *class = NULL,
216 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
217 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
218 int session_fd = -1, existing, r;
219 bool debug = false, remote;
226 /* Make this a NOP on non-logind systems */
227 if (!logind_running())
230 if (parse_argv(handle,
235 return PAM_SESSION_ERR;
238 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
240 r = get_user_data(handle, &username, &pw);
241 if (r != PAM_SUCCESS) {
242 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
246 /* Make sure we don't enter a loop by talking to
247 * logind when it is actually waiting for the
248 * background to finish start-up. If the service is
249 * "systemd-user" we simply set XDG_RUNTIME_DIR and
252 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
253 if (streq_ptr(service, "systemd-user")) {
254 _cleanup_free_ char *p = NULL, *rt = NULL;
256 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
259 r = parse_env_file(p, NEWLINE,
262 if (r < 0 && r != -ENOENT)
263 return PAM_SESSION_ERR;
266 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
267 if (r != PAM_SUCCESS) {
268 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
272 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
273 if (r != PAM_SUCCESS)
280 /* Otherwise, we ask logind to create a session for us */
282 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
283 pam_get_item(handle, PAM_TTY, (const void**) &tty);
284 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
285 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
287 seat = pam_getenv(handle, "XDG_SEAT");
289 seat = getenv("XDG_SEAT");
291 cvtnr = pam_getenv(handle, "XDG_VTNR");
293 cvtnr = getenv("XDG_VTNR");
295 type = pam_getenv(handle, "XDG_SESSION_TYPE");
297 type = getenv("XDG_SESSION_TYPE");
301 class = pam_getenv(handle, "XDG_SESSION_CLASS");
303 class = getenv("XDG_SESSION_CLASS");
307 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
308 if (isempty(desktop))
309 desktop = getenv("XDG_SESSION_DESKTOP");
313 if (strchr(tty, ':')) {
314 /* A tty with a colon is usually an X11 display,
315 * placed there to show up in utmp. We rearrange
316 * things and don't pretend that an X display was a
319 if (isempty(display))
322 } else if (streq(tty, "cron")) {
323 /* cron has been setting PAM_TTY to "cron" for a very
324 * long time and it probably shouldn't stop doing that
325 * for compatibility reasons. */
326 type = "unspecified";
327 class = "background";
329 } else if (streq(tty, "ssh")) {
330 /* ssh has been setting PAM_TTY to "ssh" for a very
331 * long time and probably shouldn't stop doing that
332 * for compatibility reasons. */
338 /* If this fails vtnr will be 0, that's intended */
340 (void) safe_atou32(cvtnr, &vtnr);
342 if (!isempty(display) && !vtnr) {
344 get_seat_from_display(display, &seat, &vtnr);
345 else if (streq(seat, "seat0"))
346 get_seat_from_display(display, NULL, &vtnr);
349 if (seat && !streq(seat, "seat0") && vtnr != 0) {
350 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
355 type = !isempty(display) ? "x11" :
356 !isempty(tty) ? "tty" : "unspecified";
359 class = streq(type, "unspecified") ? "background" : "user";
361 remote = !isempty(remote_host) && !is_localhost(remote_host);
363 /* Talk to logind over the message bus */
365 r = sd_bus_open_system(&bus);
367 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
368 return PAM_SESSION_ERR;
372 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
373 "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",
374 pw->pw_uid, getpid(),
376 type, class, strempty(desktop),
377 strempty(seat), vtnr, strempty(tty), strempty(display),
378 yes_no(remote), strempty(remote_user), strempty(remote_host));
380 r = sd_bus_call_method(bus,
381 "org.freedesktop.login1",
382 "/org/freedesktop/login1",
383 "org.freedesktop.login1.Manager",
387 "uusssssussbssa(sv)",
388 (uint32_t) pw->pw_uid,
403 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
404 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
407 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
408 return PAM_SYSTEM_ERR;
412 r = sd_bus_message_read(reply,
423 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
424 return PAM_SESSION_ERR;
428 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
429 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
430 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
432 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
433 if (r != PAM_SUCCESS) {
434 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
438 if (original_uid == pw->pw_uid) {
439 /* Don't set $XDG_RUNTIME_DIR if the user we now
440 * authenticated for does not match the original user
441 * of the session. We do this in order not to result
442 * in privileged apps clobbering the runtime directory
445 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
446 if (r != PAM_SUCCESS) {
447 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
451 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
452 if (r != PAM_SUCCESS)
456 if (!isempty(seat)) {
457 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
458 if (r != PAM_SUCCESS) {
459 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
465 char buf[DECIMAL_STR_MAX(vtnr)];
466 sprintf(buf, "%u", vtnr);
468 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
469 if (r != PAM_SUCCESS) {
470 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
475 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
476 if (r != PAM_SUCCESS) {
477 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
481 if (session_fd >= 0) {
482 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
483 if (session_fd < 0) {
484 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
485 return PAM_SESSION_ERR;
488 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
489 if (r != PAM_SUCCESS) {
490 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
491 safe_close(session_fd);
499 _public_ PAM_EXTERN int pam_sm_close_session(
500 pam_handle_t *handle,
502 int argc, const char **argv) {
504 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
505 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
506 const void *existing = NULL;
512 /* Only release session if it wasn't pre-existing when we
513 * tried to create it */
514 pam_get_data(handle, "systemd.existing", &existing);
516 id = pam_getenv(handle, "XDG_SESSION_ID");
517 if (id && !existing) {
519 /* Before we go and close the FIFO we need to tell
520 * logind that this is a clean session shutdown, so
521 * that it doesn't just go and slaughter us
522 * immediately after closing the fd */
524 r = sd_bus_open_system(&bus);
526 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
527 return PAM_SESSION_ERR;
530 r = sd_bus_call_method(bus,
531 "org.freedesktop.login1",
532 "/org/freedesktop/login1",
533 "org.freedesktop.login1.Manager",
540 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
541 return PAM_SESSION_ERR;
545 /* Note that we are knowingly leaking the FIFO fd here. This
546 * way, logind can watch us die. If we closed it here it would
547 * not have any clue when that is completed. Given that one
548 * cannot really have multiple PAM sessions open from the same
549 * process this means we will leak one FD at max. */