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;
334 dbus_bool_t remote, existing;
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_BOOLEAN, &existing,
547 DBUS_TYPE_INVALID)) {
548 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
554 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
555 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
556 id, object_path, runtime_path, session_fd, seat, vtnr);
558 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
559 if (r != PAM_SUCCESS) {
560 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
564 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
565 if (r != PAM_SUCCESS) {
566 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
570 if (!isempty(seat)) {
571 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
572 if (r != PAM_SUCCESS) {
573 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
580 snprintf(buf, sizeof(buf), "%u", vtnr);
583 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
584 if (r != PAM_SUCCESS) {
585 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
590 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
591 if (r != PAM_SUCCESS) {
592 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
596 if (session_fd >= 0) {
597 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
598 if (r != PAM_SUCCESS) {
599 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
609 strv_free(controllers);
610 strv_free(reset_controllers);
611 strv_free(kill_only_users);
612 strv_free(kill_exclude_users);
614 dbus_error_free(&error);
617 dbus_connection_close(bus);
618 dbus_connection_unref(bus);
622 dbus_message_unref(m);
625 dbus_message_unref(reply);
628 close_nointr_nofail(session_fd);
633 _public_ PAM_EXTERN int pam_sm_close_session(
634 pam_handle_t *handle,
636 int argc, const char **argv) {
638 const void *p = NULL, *existing = NULL;
640 DBusConnection *bus = NULL;
641 DBusMessage *m = NULL, *reply = NULL;
647 dbus_error_init(&error);
649 /* Only release session if it wasn't pre-existing when we
650 * tried to create it */
651 pam_get_data(handle, "systemd.existing", &existing);
653 id = pam_getenv(handle, "XDG_SESSION_ID");
654 if (id && !existing) {
656 /* Before we go and close the FIFO we need to tell
657 * logind that this is a clean session shutdown, so
658 * that it doesn't just go and slaughter us
659 * immediately after closing the fd */
661 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
663 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
668 m = dbus_message_new_method_call(
669 "org.freedesktop.login1",
670 "/org/freedesktop/login1",
671 "org.freedesktop.login1.Manager",
674 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
679 if (!dbus_message_append_args(m,
680 DBUS_TYPE_STRING, &id,
681 DBUS_TYPE_INVALID)) {
682 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
687 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
689 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
698 pam_get_data(handle, "systemd.session-fd", &p);
700 close_nointr(PTR_TO_INT(p) - 1);
702 dbus_error_free(&error);
705 dbus_connection_close(bus);
706 dbus_connection_unref(bus);
710 dbus_message_unref(m);
713 dbus_message_unref(reply);