1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <security/_pam_macros.h>
26 #include <security/pam_ext.h>
27 #include <security/pam_misc.h>
28 #include <security/pam_modules.h>
29 #include <security/pam_modutil.h>
32 #include "alloc-util.h"
33 #include "audit-util.h"
34 #include "bus-common-errors.h"
35 #include "bus-error.h"
40 #include "format-util.h"
41 #include "hostname-util.h"
42 #include "login-util.h"
44 #include "parse-util.h"
45 #include "process-util.h"
46 #include "socket-util.h"
48 #include "terminal-util.h"
50 #include "path-util.h"
52 static int parse_argv(
54 int argc, const char **argv,
62 assert(argc == 0 || argv);
64 for (i = 0; i < (unsigned) argc; i++) {
65 if (startswith(argv[i], "class=")) {
69 } else if (startswith(argv[i], "type=")) {
73 } else if (streq(argv[i], "debug")) {
77 } else if (startswith(argv[i], "debug=")) {
80 k = parse_boolean(argv[i] + 6);
82 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
87 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
93 static int get_user_data(
95 const char **ret_username,
96 struct passwd **ret_pw) {
98 const char *username = NULL;
99 struct passwd *pw = NULL;
103 assert(ret_username);
106 r = pam_get_user(handle, &username, NULL);
107 if (r != PAM_SUCCESS) {
108 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
112 if (isempty(username)) {
113 pam_syslog(handle, LOG_ERR, "User name not valid.");
117 pw = pam_modutil_getpwnam(handle, username);
119 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
120 return PAM_USER_UNKNOWN;
124 *ret_username = username;
129 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
130 union sockaddr_union sa = {
131 .un.sun_family = AF_UNIX,
133 _cleanup_free_ char *p = NULL, *tty = NULL;
134 _cleanup_close_ int fd = -1;
141 /* We deduce the X11 socket from the display name, then use
142 * SO_PEERCRED to determine the X11 server process, ask for
143 * the controlling tty of that and if it's a VC then we know
144 * the seat and the virtual terminal. Sounds ugly, is only
147 r = socket_from_display(display, &p);
150 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
152 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
156 if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
159 r = getpeercred(fd, &ucred);
163 r = get_ctty(ucred.pid, NULL, &tty);
167 v = vtnr_from_tty(tty);
175 *vtnr = (uint32_t) v;
180 static int export_legacy_dbus_address(
181 pam_handle_t *handle,
183 const char *runtime) {
185 _cleanup_free_ char *s = NULL;
188 /* FIXME: We *really* should move the access() check into the
189 * daemons that spawn dbus-daemon, instead of forcing
190 * DBUS_SESSION_BUS_ADDRESS= here. */
192 s = strjoin(runtime, "/bus");
196 if (access(s, F_OK) < 0)
200 if (asprintf(&s, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
203 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
204 if (r != PAM_SUCCESS)
210 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
214 _public_ PAM_EXTERN int pam_sm_open_session(
215 pam_handle_t *handle,
217 int argc, const char **argv) {
219 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
220 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
222 *username, *id, *object_path, *runtime_path,
224 *tty = NULL, *display = NULL,
225 *remote_user = NULL, *remote_host = NULL,
227 *type = NULL, *class = NULL,
228 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
229 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
230 int session_fd = -1, existing, r;
231 bool debug = false, remote;
238 #if 0 /// with elogind, it is always a "logind system".
239 /* Make this a NOP on non-logind systems */
240 if (!logind_running())
244 if (parse_argv(handle,
249 return PAM_SESSION_ERR;
252 pam_syslog(handle, LOG_DEBUG, "pam-elogind initializing");
254 r = get_user_data(handle, &username, &pw);
255 if (r != PAM_SUCCESS) {
256 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
260 /* Make sure we don't enter a loop by talking to
261 * elogind when it is actually waiting for the
262 * background to finish start-up. If the service is
263 * "elogind-user" we simply set XDG_RUNTIME_DIR and
266 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
267 if (streq_ptr(service, "elogind-user")) {
268 _cleanup_free_ char *rt = NULL;
270 if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
273 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
274 if (r != PAM_SUCCESS) {
275 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
279 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
280 if (r != PAM_SUCCESS)
286 /* Otherwise, we ask logind to create a session for us */
288 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
289 pam_get_item(handle, PAM_TTY, (const void**) &tty);
290 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
291 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
293 seat = pam_getenv(handle, "XDG_SEAT");
295 seat = getenv("XDG_SEAT");
297 cvtnr = pam_getenv(handle, "XDG_VTNR");
299 cvtnr = getenv("XDG_VTNR");
301 type = pam_getenv(handle, "XDG_SESSION_TYPE");
303 type = getenv("XDG_SESSION_TYPE");
307 class = pam_getenv(handle, "XDG_SESSION_CLASS");
309 class = getenv("XDG_SESSION_CLASS");
313 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
314 if (isempty(desktop))
315 desktop = getenv("XDG_SESSION_DESKTOP");
319 if (strchr(tty, ':')) {
320 /* A tty with a colon is usually an X11 display,
321 * placed there to show up in utmp. We rearrange
322 * things and don't pretend that an X display was a
325 if (isempty(display))
328 } else if (streq(tty, "cron")) {
329 /* cron has been setting PAM_TTY to "cron" for a very
330 * long time and it probably shouldn't stop doing that
331 * for compatibility reasons. */
332 type = "unspecified";
333 class = "background";
335 } else if (streq(tty, "ssh")) {
336 /* ssh has been setting PAM_TTY to "ssh" for a very
337 * long time and probably shouldn't stop doing that
338 * for compatibility reasons. */
343 /* Chop off leading /dev prefix that some clients specify, but others do not. */
344 tty = skip_dev_prefix(tty);
346 /* If this fails vtnr will be 0, that's intended */
348 (void) safe_atou32(cvtnr, &vtnr);
350 if (!isempty(display) && !vtnr) {
352 get_seat_from_display(display, &seat, &vtnr);
353 else if (streq(seat, "seat0"))
354 get_seat_from_display(display, NULL, &vtnr);
357 if (seat && !streq(seat, "seat0") && vtnr != 0) {
358 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
363 type = !isempty(display) ? "x11" :
364 !isempty(tty) ? "tty" : "unspecified";
367 class = streq(type, "unspecified") ? "background" : "user";
369 remote = !isempty(remote_host) && !is_localhost(remote_host);
371 /* Talk to logind over the message bus */
373 r = sd_bus_open_system(&bus);
375 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
376 return PAM_SESSION_ERR;
380 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
381 "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",
382 pw->pw_uid, getpid_cached(),
384 type, class, strempty(desktop),
385 strempty(seat), vtnr, strempty(tty), strempty(display),
386 yes_no(remote), strempty(remote_user), strempty(remote_host));
388 r = sd_bus_call_method(bus,
389 "org.freedesktop.login1",
390 "/org/freedesktop/login1",
391 "org.freedesktop.login1.Manager",
395 "uusssssussbssa(sv)",
396 (uint32_t) pw->pw_uid,
397 (uint32_t) getpid_cached(),
411 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
412 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
415 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
416 return PAM_SYSTEM_ERR;
420 r = sd_bus_message_read(reply,
431 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
432 return PAM_SESSION_ERR;
436 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
437 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
438 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
440 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
441 if (r != PAM_SUCCESS) {
442 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
446 if (original_uid == pw->pw_uid) {
447 /* Don't set $XDG_RUNTIME_DIR if the user we now
448 * authenticated for does not match the original user
449 * of the session. We do this in order not to result
450 * in privileged apps clobbering the runtime directory
453 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
454 if (r != PAM_SUCCESS) {
455 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
459 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
460 if (r != PAM_SUCCESS)
464 if (!isempty(seat)) {
465 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
466 if (r != PAM_SUCCESS) {
467 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
473 char buf[DECIMAL_STR_MAX(vtnr)];
474 sprintf(buf, "%u", vtnr);
476 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
477 if (r != PAM_SUCCESS) {
478 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
483 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
484 if (r != PAM_SUCCESS) {
485 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
489 if (session_fd >= 0) {
490 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
491 if (session_fd < 0) {
492 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
493 return PAM_SESSION_ERR;
496 r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL);
497 if (r != PAM_SUCCESS) {
498 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
499 safe_close(session_fd);
507 _public_ PAM_EXTERN int pam_sm_close_session(
508 pam_handle_t *handle,
510 int argc, const char **argv) {
512 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
513 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
514 const void *existing = NULL;
520 /* Only release session if it wasn't pre-existing when we
521 * tried to create it */
522 pam_get_data(handle, "systemd.existing", &existing);
524 id = pam_getenv(handle, "XDG_SESSION_ID");
525 if (id && !existing) {
527 /* Before we go and close the FIFO we need to tell
528 * logind that this is a clean session shutdown, so
529 * that it doesn't just go and slaughter us
530 * immediately after closing the fd */
532 r = sd_bus_open_system(&bus);
534 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
535 return PAM_SESSION_ERR;
538 r = sd_bus_call_method(bus,
539 "org.freedesktop.login1",
540 "/org/freedesktop/login1",
541 "org.freedesktop.login1.Manager",
548 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
549 return PAM_SESSION_ERR;
553 /* Note that we are knowingly leaking the FIFO fd here. This
554 * way, logind can watch us die. If we closed it here it would
555 * not have any clue when that is completed. Given that one
556 * cannot really have multiple PAM sessions open from the same
557 * process this means we will leak one FD at max. */