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>
40 #include "socket-util.h"
42 #include "bus-error.h"
43 #include "formats-util.h"
44 #include "terminal-util.h"
45 #include "hostname-util.h"
47 static int parse_argv(
49 int argc, const char **argv,
57 assert(argc == 0 || argv);
59 for (i = 0; i < (unsigned) argc; i++) {
60 if (startswith(argv[i], "class=")) {
64 } else if (startswith(argv[i], "type=")) {
68 } else if (streq(argv[i], "debug")) {
72 } else if (startswith(argv[i], "debug=")) {
75 k = parse_boolean(argv[i] + 6);
77 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
82 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
88 static int get_user_data(
90 const char **ret_username,
91 struct passwd **ret_pw) {
93 const char *username = NULL;
94 struct passwd *pw = NULL;
101 r = pam_get_user(handle, &username, NULL);
102 if (r != PAM_SUCCESS) {
103 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
107 if (isempty(username)) {
108 pam_syslog(handle, LOG_ERR, "User name not valid.");
112 pw = pam_modutil_getpwnam(handle, username);
114 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
115 return PAM_USER_UNKNOWN;
119 *ret_username = username;
124 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
125 union sockaddr_union sa = {
126 .un.sun_family = AF_UNIX,
128 _cleanup_free_ char *p = NULL, *tty = NULL;
129 _cleanup_close_ int fd = -1;
136 /* We deduce the X11 socket from the display name, then use
137 * SO_PEERCRED to determine the X11 server process, ask for
138 * the controlling tty of that and if it's a VC then we know
139 * the seat and the virtual terminal. Sounds ugly, is only
142 r = socket_from_display(display, &p);
145 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
147 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
151 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
154 r = getpeercred(fd, &ucred);
158 r = get_ctty(ucred.pid, NULL, &tty);
162 v = vtnr_from_tty(tty);
170 *vtnr = (uint32_t) v;
175 static int export_legacy_dbus_address(
176 pam_handle_t *handle,
178 const char *runtime) {
181 _cleanup_free_ char *s = NULL;
184 /* skip export if kdbus is not active */
185 if (access("/sys/fs/kdbus", F_OK) < 0)
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_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 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
404 return PAM_SYSTEM_ERR;
407 r = sd_bus_message_read(reply,
418 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
419 return PAM_SESSION_ERR;
423 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
424 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
425 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
427 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
428 if (r != PAM_SUCCESS) {
429 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
433 if (original_uid == pw->pw_uid) {
434 /* Don't set $XDG_RUNTIME_DIR if the user we now
435 * authenticated for does not match the original user
436 * of the session. We do this in order not to result
437 * in privileged apps clobbering the runtime directory
440 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
441 if (r != PAM_SUCCESS) {
442 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
446 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
447 if (r != PAM_SUCCESS)
451 if (!isempty(seat)) {
452 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
453 if (r != PAM_SUCCESS) {
454 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
460 char buf[DECIMAL_STR_MAX(vtnr)];
461 sprintf(buf, "%u", vtnr);
463 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
464 if (r != PAM_SUCCESS) {
465 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
470 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
471 if (r != PAM_SUCCESS) {
472 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
476 if (session_fd >= 0) {
477 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
478 if (session_fd < 0) {
479 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
480 return PAM_SESSION_ERR;
483 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
484 if (r != PAM_SUCCESS) {
485 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
486 safe_close(session_fd);
494 _public_ PAM_EXTERN int pam_sm_close_session(
495 pam_handle_t *handle,
497 int argc, const char **argv) {
499 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
500 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
501 const void *existing = NULL;
507 /* Only release session if it wasn't pre-existing when we
508 * tried to create it */
509 pam_get_data(handle, "systemd.existing", &existing);
511 id = pam_getenv(handle, "XDG_SESSION_ID");
512 if (id && !existing) {
514 /* Before we go and close the FIFO we need to tell
515 * logind that this is a clean session shutdown, so
516 * that it doesn't just go and slaughter us
517 * immediately after closing the fd */
519 r = sd_bus_open_system(&bus);
521 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
522 return PAM_SESSION_ERR;
525 r = sd_bus_call_method(bus,
526 "org.freedesktop.login1",
527 "/org/freedesktop/login1",
528 "org.freedesktop.login1.Manager",
535 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
536 return PAM_SESSION_ERR;
540 /* Note that we are knowingly leaking the FIFO fd here. This
541 * way, logind can watch us die. If we closed it here it would
542 * not have any clue when that is completed. Given that one
543 * cannot really have multiple PAM sessions open from the same
544 * process this means we will leak one FD at max. */