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>
35 #include <systemd/sd-daemon.h>
41 #include "dbus-common.h"
43 #include "socket-util.h"
45 static int parse_argv(pam_handle_t *handle,
46 int argc, const char **argv,
48 char ***reset_controllers,
50 char ***kill_only_users,
51 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], "debug=")) {
139 if ((k = parse_boolean(argv[i] + 6)) < 0) {
140 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
147 } else if (startswith(argv[i], "create-session=") ||
148 startswith(argv[i], "kill-user=")) {
150 pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
153 pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
161 static int get_user_data(
162 pam_handle_t *handle,
163 const char **ret_username,
164 struct passwd **ret_pw) {
166 const char *username = NULL;
167 struct passwd *pw = NULL;
172 assert(ret_username);
175 r = audit_loginuid_from_pid(0, &uid);
177 pw = pam_modutil_getpwuid(handle, uid);
179 r = pam_get_user(handle, &username, NULL);
180 if (r != PAM_SUCCESS) {
181 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
185 if (isempty(username)) {
186 pam_syslog(handle, LOG_ERR, "User name not valid.");
190 pw = pam_modutil_getpwnam(handle, username);
194 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
195 return PAM_USER_UNKNOWN;
199 *ret_username = username ? username : pw->pw_name;
204 static bool check_user_lists(
205 pam_handle_t *handle,
207 char **kill_only_users,
208 char **kill_exclude_users) {
210 const char *name = NULL;
216 name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
220 pw = pam_modutil_getpwuid(handle, uid);
225 STRV_FOREACH(l, kill_exclude_users) {
228 if (parse_uid(*l, &u) >= 0)
232 if (name && streq(name, *l))
236 if (strv_isempty(kill_only_users))
239 STRV_FOREACH(l, kill_only_users) {
242 if (parse_uid(*l, &u) >= 0)
246 if (name && streq(name, *l))
253 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
257 union sockaddr_union sa;
266 /* We deduce the X11 socket from the display name, then use
267 * SO_PEERCRED to determine the X11 server process, ask for
268 * the controlling tty of that and if it's a VC then we know
269 * the seat and the virtual terminal. Sounds ugly, is only
272 r = socket_from_display(display, &p);
276 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
283 sa.un.sun_family = AF_UNIX;
284 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
287 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
288 close_nointr_nofail(fd);
293 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
294 close_nointr_nofail(fd);
299 r = get_ctty(ucred.pid, NULL, &tty);
303 v = vtnr_from_tty(tty);
313 *vtnr = (uint32_t) v;
318 _public_ PAM_EXTERN int pam_sm_open_session(
319 pam_handle_t *handle,
321 int argc, const char **argv) {
324 bool kill_processes = false, debug = false;
325 const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type, *class, *cvtnr = NULL;
326 char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
329 DBusMessageIter iter;
332 DBusConnection *bus = NULL;
333 DBusMessage *m = NULL, *reply = NULL;
340 dbus_error_init(&error);
342 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
344 /* Make this a NOP on non-systemd systems */
345 if (sd_booted() <= 0)
348 if (parse_argv(handle,
350 &controllers, &reset_controllers,
351 &kill_processes, &kill_only_users, &kill_exclude_users,
357 r = get_user_data(handle, &username, &pw);
358 if (r != PAM_SUCCESS)
361 /* Make sure we don't enter a loop by talking to
362 * systemd-logind when it is actually waiting for the
363 * background to finish start-up. If the service is
364 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
367 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
368 if (streq_ptr(service, "systemd-shared")) {
371 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
376 r = parse_env_file(p, NEWLINE,
381 if (r < 0 && r != -ENOENT) {
388 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
391 if (r != PAM_SUCCESS) {
392 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
402 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
404 dbus_connection_set_change_sigpipe(FALSE);
406 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
408 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
413 m = dbus_message_new_method_call(
414 "org.freedesktop.login1",
415 "/org/freedesktop/login1",
416 "org.freedesktop.login1.Manager",
419 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
427 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
428 pam_get_item(handle, PAM_TTY, (const void**) &tty);
429 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
430 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
432 seat = pam_getenv(handle, "XDG_SEAT");
434 seat = getenv("XDG_SEAT");
436 cvtnr = pam_getenv(handle, "XDG_VTNR");
438 cvtnr = getenv("XDG_VTNR");
440 service = strempty(service);
442 display = strempty(display);
443 remote_user = strempty(remote_user);
444 remote_host = strempty(remote_host);
445 seat = strempty(seat);
447 if (strchr(tty, ':')) {
448 /* A tty with a colon is usually an X11 display, place
449 * there to show up in utmp. We rearrange things and
450 * don't pretend that an X display was a tty */
452 if (isempty(display))
455 } else if (streq(tty, "cron")) {
456 /* cron has been setting PAM_TTY to "cron" for a very long time
457 * and it cannot stop doing that for compatibility reasons. */
461 /* If this fails vtnr will be 0, that's intended */
463 safe_atou32(cvtnr, &vtnr);
465 if (!isempty(display) && vtnr <= 0) {
467 get_seat_from_display(display, &seat, &vtnr);
468 else if (streq(seat, "seat0"))
469 get_seat_from_display(display, NULL, &vtnr);
472 type = !isempty(display) ? "x11" :
473 !isempty(tty) ? "tty" : "unspecified";
475 class = pam_getenv(handle, "XDG_SESSION_CLASS");
477 class = getenv("XDG_SESSION_CLASS");
481 remote = !isempty(remote_host) &&
482 !streq(remote_host, "localhost") &&
483 !streq(remote_host, "localhost.localdomain");
485 if (!dbus_message_append_args(m,
486 DBUS_TYPE_UINT32, &uid,
487 DBUS_TYPE_UINT32, &pid,
488 DBUS_TYPE_STRING, &service,
489 DBUS_TYPE_STRING, &type,
490 DBUS_TYPE_STRING, &class,
491 DBUS_TYPE_STRING, &seat,
492 DBUS_TYPE_UINT32, &vtnr,
493 DBUS_TYPE_STRING, &tty,
494 DBUS_TYPE_STRING, &display,
495 DBUS_TYPE_BOOLEAN, &remote,
496 DBUS_TYPE_STRING, &remote_user,
497 DBUS_TYPE_STRING, &remote_host,
498 DBUS_TYPE_INVALID)) {
499 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
504 dbus_message_iter_init_append(m, &iter);
506 r = bus_append_strv_iter(&iter, controllers);
508 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
513 r = bus_append_strv_iter(&iter, reset_controllers);
515 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
521 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
522 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
528 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
529 "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",
530 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
532 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
534 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
539 if (!dbus_message_get_args(reply, &error,
540 DBUS_TYPE_STRING, &id,
541 DBUS_TYPE_OBJECT_PATH, &object_path,
542 DBUS_TYPE_STRING, &runtime_path,
543 DBUS_TYPE_UNIX_FD, &session_fd,
544 DBUS_TYPE_STRING, &seat,
545 DBUS_TYPE_UINT32, &vtnr,
546 DBUS_TYPE_INVALID)) {
547 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
553 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
554 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
555 id, object_path, runtime_path, session_fd, seat, vtnr);
557 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
558 if (r != PAM_SUCCESS) {
559 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
563 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
564 if (r != PAM_SUCCESS) {
565 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
569 if (!isempty(seat)) {
570 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
571 if (r != PAM_SUCCESS) {
572 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
579 snprintf(buf, sizeof(buf), "%u", vtnr);
582 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
583 if (r != PAM_SUCCESS) {
584 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
589 if (session_fd >= 0) {
590 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
591 if (r != PAM_SUCCESS) {
592 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
602 strv_free(controllers);
603 strv_free(reset_controllers);
604 strv_free(kill_only_users);
605 strv_free(kill_exclude_users);
607 dbus_error_free(&error);
610 dbus_connection_close(bus);
611 dbus_connection_unref(bus);
615 dbus_message_unref(m);
618 dbus_message_unref(reply);
621 close_nointr_nofail(session_fd);
626 _public_ PAM_EXTERN int pam_sm_close_session(
627 pam_handle_t *handle,
629 int argc, const char **argv) {
631 const void *p = NULL;
633 DBusConnection *bus = NULL;
634 DBusMessage *m = NULL, *reply = NULL;
640 dbus_error_init(&error);
642 id = pam_getenv(handle, "XDG_SESSION_ID");
645 /* Before we go and close the FIFO we need to tell
646 * logind that this is a clean session shutdown, so
647 * that it doesn't just go and slaughter us
648 * immediately after closing the fd */
650 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
652 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
657 m = dbus_message_new_method_call(
658 "org.freedesktop.login1",
659 "/org/freedesktop/login1",
660 "org.freedesktop.login1.Manager",
663 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
668 if (!dbus_message_append_args(m,
669 DBUS_TYPE_STRING, &id,
670 DBUS_TYPE_INVALID)) {
671 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
676 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
678 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
687 pam_get_data(handle, "systemd.session-fd", &p);
689 close_nointr(PTR_TO_INT(p) - 1);
691 dbus_error_free(&error);
694 dbus_connection_close(bus);
695 dbus_connection_unref(bus);
699 dbus_message_unref(m);
702 dbus_message_unref(reply);