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>
39 #include "dbus-common.h"
41 #include "socket-util.h"
44 static int parse_argv(pam_handle_t *handle,
45 int argc, const char **argv,
47 char ***reset_controllers,
49 char ***kill_only_users,
50 char ***kill_exclude_users,
57 assert(argc == 0 || argv);
59 for (i = 0; i < (unsigned) argc; i++) {
62 if (startswith(argv[i], "kill-session-processes=")) {
63 if ((k = parse_boolean(argv[i] + 23)) < 0) {
64 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument.");
71 } else if (startswith(argv[i], "kill-session=")) {
72 /* As compatibility for old versions */
74 if ((k = parse_boolean(argv[i] + 13)) < 0) {
75 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument.");
82 } else if (startswith(argv[i], "controllers=")) {
87 if (!(l = strv_split(argv[i] + 12, ","))) {
88 pam_syslog(handle, LOG_ERR, "Out of memory.");
92 strv_free(*controllers);
96 } else if (startswith(argv[i], "reset-controllers=")) {
98 if (reset_controllers) {
101 if (!(l = strv_split(argv[i] + 18, ","))) {
102 pam_syslog(handle, LOG_ERR, "Out of memory.");
106 strv_free(*reset_controllers);
107 *reset_controllers = l;
110 } else if (startswith(argv[i], "kill-only-users=")) {
112 if (kill_only_users) {
115 if (!(l = strv_split(argv[i] + 16, ","))) {
116 pam_syslog(handle, LOG_ERR, "Out of memory.");
120 strv_free(*kill_only_users);
121 *kill_only_users = l;
124 } else if (startswith(argv[i], "kill-exclude-users=")) {
126 if (kill_exclude_users) {
129 if (!(l = strv_split(argv[i] + 19, ","))) {
130 pam_syslog(handle, LOG_ERR, "Out of memory.");
134 strv_free(*kill_exclude_users);
135 *kill_exclude_users = l;
138 } else if (startswith(argv[i], "class=")) {
141 *class = argv[i] + 6;
143 } else if (startswith(argv[i], "debug=")) {
144 if ((k = parse_boolean(argv[i] + 6)) < 0) {
145 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
152 } else if (startswith(argv[i], "create-session=") ||
153 startswith(argv[i], "kill-user=")) {
155 pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
158 pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
166 static int get_user_data(
167 pam_handle_t *handle,
168 const char **ret_username,
169 struct passwd **ret_pw) {
171 const char *username = NULL;
172 struct passwd *pw = NULL;
177 assert(ret_username);
180 r = audit_loginuid_from_pid(0, &uid);
182 pw = pam_modutil_getpwuid(handle, uid);
184 r = pam_get_user(handle, &username, NULL);
185 if (r != PAM_SUCCESS) {
186 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
190 if (isempty(username)) {
191 pam_syslog(handle, LOG_ERR, "User name not valid.");
195 pw = pam_modutil_getpwnam(handle, username);
199 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
200 return PAM_USER_UNKNOWN;
204 *ret_username = username ? username : pw->pw_name;
209 static bool check_user_lists(
210 pam_handle_t *handle,
212 char **kill_only_users,
213 char **kill_exclude_users) {
215 const char *name = NULL;
221 name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
225 pw = pam_modutil_getpwuid(handle, uid);
230 STRV_FOREACH(l, kill_exclude_users) {
233 if (parse_uid(*l, &u) >= 0)
237 if (name && streq(name, *l))
241 if (strv_isempty(kill_only_users))
244 STRV_FOREACH(l, kill_only_users) {
247 if (parse_uid(*l, &u) >= 0)
251 if (name && streq(name, *l))
258 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
262 union sockaddr_union sa;
271 /* We deduce the X11 socket from the display name, then use
272 * SO_PEERCRED to determine the X11 server process, ask for
273 * the controlling tty of that and if it's a VC then we know
274 * the seat and the virtual terminal. Sounds ugly, is only
277 r = socket_from_display(display, &p);
281 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
288 sa.un.sun_family = AF_UNIX;
289 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
292 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
293 close_nointr_nofail(fd);
298 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
299 close_nointr_nofail(fd);
304 r = get_ctty(ucred.pid, NULL, &tty);
308 v = vtnr_from_tty(tty);
318 *vtnr = (uint32_t) v;
323 _public_ PAM_EXTERN int pam_sm_open_session(
324 pam_handle_t *handle,
326 int argc, const char **argv) {
329 bool kill_processes = false, debug = false;
330 const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL;
331 char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
334 DBusMessageIter iter;
337 DBusConnection *bus = NULL;
338 DBusMessage *m = NULL, *reply = NULL;
339 dbus_bool_t remote, existing;
345 dbus_error_init(&error);
347 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
349 /* Make this a NOP on non-logind systems */
350 if (!logind_running())
353 if (parse_argv(handle,
355 &controllers, &reset_controllers,
356 &kill_processes, &kill_only_users, &kill_exclude_users,
357 &class_pam, &debug) < 0) {
362 r = get_user_data(handle, &username, &pw);
363 if (r != PAM_SUCCESS)
366 /* Make sure we don't enter a loop by talking to
367 * systemd-logind when it is actually waiting for the
368 * background to finish start-up. If the service is
369 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
372 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
373 if (streq_ptr(service, "systemd-shared")) {
376 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
381 r = parse_env_file(p, NEWLINE,
386 if (r < 0 && r != -ENOENT) {
393 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
396 if (r != PAM_SUCCESS) {
397 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
407 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
409 dbus_connection_set_change_sigpipe(FALSE);
411 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
413 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
418 m = dbus_message_new_method_call(
419 "org.freedesktop.login1",
420 "/org/freedesktop/login1",
421 "org.freedesktop.login1.Manager",
424 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
432 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
433 pam_get_item(handle, PAM_TTY, (const void**) &tty);
434 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
435 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
437 seat = pam_getenv(handle, "XDG_SEAT");
439 seat = getenv("XDG_SEAT");
441 cvtnr = pam_getenv(handle, "XDG_VTNR");
443 cvtnr = getenv("XDG_VTNR");
445 service = strempty(service);
447 display = strempty(display);
448 remote_user = strempty(remote_user);
449 remote_host = strempty(remote_host);
450 seat = strempty(seat);
452 if (strchr(tty, ':')) {
453 /* A tty with a colon is usually an X11 display, place
454 * there to show up in utmp. We rearrange things and
455 * don't pretend that an X display was a tty */
457 if (isempty(display))
460 } else if (streq(tty, "cron")) {
461 /* cron has been setting PAM_TTY to "cron" for a very
462 * long time and it probably shouldn't stop doing that
463 * for compatibility reasons. */
465 type = "unspecified";
466 } else if (streq(tty, "ssh")) {
467 /* ssh has been setting PAM_TTY to "ssh" for a very
468 * long time and probably shouldn't stop doing that
469 * for compatibility reasons. */
474 /* If this fails vtnr will be 0, that's intended */
476 safe_atou32(cvtnr, &vtnr);
478 if (!isempty(display) && vtnr <= 0) {
480 get_seat_from_display(display, &seat, &vtnr);
481 else if (streq(seat, "seat0"))
482 get_seat_from_display(display, NULL, &vtnr);
486 type = !isempty(display) ? "x11" :
487 !isempty(tty) ? "tty" : "unspecified";
489 class = pam_getenv(handle, "XDG_SESSION_CLASS");
491 class = getenv("XDG_SESSION_CLASS");
497 remote = !isempty(remote_host) &&
498 !streq(remote_host, "localhost") &&
499 !streq(remote_host, "localhost.localdomain");
501 if (!dbus_message_append_args(m,
502 DBUS_TYPE_UINT32, &uid,
503 DBUS_TYPE_UINT32, &pid,
504 DBUS_TYPE_STRING, &service,
505 DBUS_TYPE_STRING, &type,
506 DBUS_TYPE_STRING, &class,
507 DBUS_TYPE_STRING, &seat,
508 DBUS_TYPE_UINT32, &vtnr,
509 DBUS_TYPE_STRING, &tty,
510 DBUS_TYPE_STRING, &display,
511 DBUS_TYPE_BOOLEAN, &remote,
512 DBUS_TYPE_STRING, &remote_user,
513 DBUS_TYPE_STRING, &remote_host,
514 DBUS_TYPE_INVALID)) {
515 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
520 dbus_message_iter_init_append(m, &iter);
522 r = bus_append_strv_iter(&iter, controllers);
524 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
529 r = bus_append_strv_iter(&iter, reset_controllers);
531 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
537 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
538 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
544 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
545 "uid=%u pid=%u service=%s type=%s class=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
546 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
548 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
550 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
555 if (!dbus_message_get_args(reply, &error,
556 DBUS_TYPE_STRING, &id,
557 DBUS_TYPE_OBJECT_PATH, &object_path,
558 DBUS_TYPE_STRING, &runtime_path,
559 DBUS_TYPE_UNIX_FD, &session_fd,
560 DBUS_TYPE_STRING, &seat,
561 DBUS_TYPE_UINT32, &vtnr,
562 DBUS_TYPE_BOOLEAN, &existing,
563 DBUS_TYPE_INVALID)) {
564 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
570 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
571 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
572 id, object_path, runtime_path, session_fd, seat, vtnr);
574 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
575 if (r != PAM_SUCCESS) {
576 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
580 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
581 if (r != PAM_SUCCESS) {
582 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
586 if (!isempty(seat)) {
587 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
588 if (r != PAM_SUCCESS) {
589 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
596 snprintf(buf, sizeof(buf), "%u", vtnr);
599 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
600 if (r != PAM_SUCCESS) {
601 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
606 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
607 if (r != PAM_SUCCESS) {
608 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
612 if (session_fd >= 0) {
613 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
614 if (r != PAM_SUCCESS) {
615 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
625 strv_free(controllers);
626 strv_free(reset_controllers);
627 strv_free(kill_only_users);
628 strv_free(kill_exclude_users);
630 dbus_error_free(&error);
633 dbus_connection_close(bus);
634 dbus_connection_unref(bus);
638 dbus_message_unref(m);
641 dbus_message_unref(reply);
644 close_nointr_nofail(session_fd);
649 _public_ PAM_EXTERN int pam_sm_close_session(
650 pam_handle_t *handle,
652 int argc, const char **argv) {
654 const void *p = NULL, *existing = NULL;
656 DBusConnection *bus = NULL;
657 DBusMessage *m = NULL, *reply = NULL;
663 dbus_error_init(&error);
665 /* Only release session if it wasn't pre-existing when we
666 * tried to create it */
667 pam_get_data(handle, "systemd.existing", &existing);
669 id = pam_getenv(handle, "XDG_SESSION_ID");
670 if (id && !existing) {
672 /* Before we go and close the FIFO we need to tell
673 * logind that this is a clean session shutdown, so
674 * that it doesn't just go and slaughter us
675 * immediately after closing the fd */
677 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
679 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
684 m = dbus_message_new_method_call(
685 "org.freedesktop.login1",
686 "/org/freedesktop/login1",
687 "org.freedesktop.login1.Manager",
690 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
695 if (!dbus_message_append_args(m,
696 DBUS_TYPE_STRING, &id,
697 DBUS_TYPE_INVALID)) {
698 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
703 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
705 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
714 pam_get_data(handle, "systemd.session-fd", &p);
716 close_nointr(PTR_TO_INT(p) - 1);
718 dbus_error_free(&error);
721 dbus_connection_close(bus);
722 dbus_connection_unref(bus);
726 dbus_message_unref(m);
729 dbus_message_unref(reply);