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,
58 assert(argc == 0 || argv);
60 for (i = 0; i < (unsigned) argc; i++) {
63 if (startswith(argv[i], "kill-session-processes=")) {
64 if ((k = parse_boolean(argv[i] + 23)) < 0) {
65 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument.");
72 } else if (startswith(argv[i], "kill-session=")) {
73 /* As compatibility for old versions */
75 if ((k = parse_boolean(argv[i] + 13)) < 0) {
76 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument.");
83 } else if (startswith(argv[i], "controllers=")) {
88 if (!(l = strv_split(argv[i] + 12, ","))) {
89 pam_syslog(handle, LOG_ERR, "Out of memory.");
93 strv_free(*controllers);
97 } else if (startswith(argv[i], "reset-controllers=")) {
99 if (reset_controllers) {
102 if (!(l = strv_split(argv[i] + 18, ","))) {
103 pam_syslog(handle, LOG_ERR, "Out of memory.");
107 strv_free(*reset_controllers);
108 *reset_controllers = l;
111 } else if (startswith(argv[i], "kill-only-users=")) {
113 if (kill_only_users) {
116 if (!(l = strv_split(argv[i] + 16, ","))) {
117 pam_syslog(handle, LOG_ERR, "Out of memory.");
121 strv_free(*kill_only_users);
122 *kill_only_users = l;
125 } else if (startswith(argv[i], "kill-exclude-users=")) {
127 if (kill_exclude_users) {
130 if (!(l = strv_split(argv[i] + 19, ","))) {
131 pam_syslog(handle, LOG_ERR, "Out of memory.");
135 strv_free(*kill_exclude_users);
136 *kill_exclude_users = l;
139 } else if (startswith(argv[i], "class=")) {
142 *class = argv[i] + 6;
144 } else if (startswith(argv[i], "debug=")) {
145 if ((k = parse_boolean(argv[i] + 6)) < 0) {
146 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
153 } else if (startswith(argv[i], "create-session=") ||
154 startswith(argv[i], "kill-user=")) {
156 pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
159 pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
167 static int get_user_data(
168 pam_handle_t *handle,
169 const char **ret_username,
170 struct passwd **ret_pw) {
172 const char *username = NULL;
173 struct passwd *pw = NULL;
178 assert(ret_username);
181 r = audit_loginuid_from_pid(0, &uid);
183 pw = pam_modutil_getpwuid(handle, uid);
185 r = pam_get_user(handle, &username, NULL);
186 if (r != PAM_SUCCESS) {
187 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
191 if (isempty(username)) {
192 pam_syslog(handle, LOG_ERR, "User name not valid.");
196 pw = pam_modutil_getpwnam(handle, username);
200 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
201 return PAM_USER_UNKNOWN;
205 *ret_username = username ? username : pw->pw_name;
210 static bool check_user_lists(
211 pam_handle_t *handle,
213 char **kill_only_users,
214 char **kill_exclude_users) {
216 const char *name = NULL;
222 name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
226 pw = pam_modutil_getpwuid(handle, uid);
231 STRV_FOREACH(l, kill_exclude_users) {
234 if (parse_uid(*l, &u) >= 0)
238 if (name && streq(name, *l))
242 if (strv_isempty(kill_only_users))
245 STRV_FOREACH(l, kill_only_users) {
248 if (parse_uid(*l, &u) >= 0)
252 if (name && streq(name, *l))
259 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
263 union sockaddr_union sa;
272 /* We deduce the X11 socket from the display name, then use
273 * SO_PEERCRED to determine the X11 server process, ask for
274 * the controlling tty of that and if it's a VC then we know
275 * the seat and the virtual terminal. Sounds ugly, is only
278 r = socket_from_display(display, &p);
282 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
289 sa.un.sun_family = AF_UNIX;
290 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
293 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
294 close_nointr_nofail(fd);
299 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
300 close_nointr_nofail(fd);
305 r = get_ctty(ucred.pid, NULL, &tty);
309 v = vtnr_from_tty(tty);
319 *vtnr = (uint32_t) v;
324 _public_ PAM_EXTERN int pam_sm_open_session(
325 pam_handle_t *handle,
327 int argc, const char **argv) {
330 bool kill_processes = false, debug = false;
331 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;
332 char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
335 DBusMessageIter iter;
338 DBusConnection *bus = NULL;
339 DBusMessage *m = NULL, *reply = NULL;
340 dbus_bool_t remote, existing;
346 dbus_error_init(&error);
348 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
350 /* Make this a NOP on non-systemd systems */
351 if (sd_booted() <= 0)
354 if (parse_argv(handle,
356 &controllers, &reset_controllers,
357 &kill_processes, &kill_only_users, &kill_exclude_users,
358 &class_pam, &debug) < 0) {
363 r = get_user_data(handle, &username, &pw);
364 if (r != PAM_SUCCESS)
367 /* Make sure we don't enter a loop by talking to
368 * systemd-logind when it is actually waiting for the
369 * background to finish start-up. If the service is
370 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
373 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
374 if (streq_ptr(service, "systemd-shared")) {
377 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
382 r = parse_env_file(p, NEWLINE,
387 if (r < 0 && r != -ENOENT) {
394 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
397 if (r != PAM_SUCCESS) {
398 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
408 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
410 dbus_connection_set_change_sigpipe(FALSE);
412 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
414 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
419 m = dbus_message_new_method_call(
420 "org.freedesktop.login1",
421 "/org/freedesktop/login1",
422 "org.freedesktop.login1.Manager",
425 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
433 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
434 pam_get_item(handle, PAM_TTY, (const void**) &tty);
435 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
436 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
438 seat = pam_getenv(handle, "XDG_SEAT");
440 seat = getenv("XDG_SEAT");
442 cvtnr = pam_getenv(handle, "XDG_VTNR");
444 cvtnr = getenv("XDG_VTNR");
446 service = strempty(service);
448 display = strempty(display);
449 remote_user = strempty(remote_user);
450 remote_host = strempty(remote_host);
451 seat = strempty(seat);
453 if (strchr(tty, ':')) {
454 /* A tty with a colon is usually an X11 display, place
455 * there to show up in utmp. We rearrange things and
456 * don't pretend that an X display was a tty */
458 if (isempty(display))
461 } else if (streq(tty, "cron")) {
462 /* cron has been setting PAM_TTY to "cron" for a very
463 * long time and it probably shouldn't stop doing that
464 * for compatibility reasons. */
466 type = "unspecified";
467 } else if (streq(tty, "ssh")) {
468 /* ssh has been setting PAM_TTY to "ssh" for a very
469 * long time and probably shouldn't stop doing that
470 * for compatibility reasons. */
475 /* If this fails vtnr will be 0, that's intended */
477 safe_atou32(cvtnr, &vtnr);
479 if (!isempty(display) && vtnr <= 0) {
481 get_seat_from_display(display, &seat, &vtnr);
482 else if (streq(seat, "seat0"))
483 get_seat_from_display(display, NULL, &vtnr);
487 type = !isempty(display) ? "x11" :
488 !isempty(tty) ? "tty" : "unspecified";
490 class = pam_getenv(handle, "XDG_SESSION_CLASS");
492 class = getenv("XDG_SESSION_CLASS");
498 remote = !isempty(remote_host) &&
499 !streq(remote_host, "localhost") &&
500 !streq(remote_host, "localhost.localdomain");
502 if (!dbus_message_append_args(m,
503 DBUS_TYPE_UINT32, &uid,
504 DBUS_TYPE_UINT32, &pid,
505 DBUS_TYPE_STRING, &service,
506 DBUS_TYPE_STRING, &type,
507 DBUS_TYPE_STRING, &class,
508 DBUS_TYPE_STRING, &seat,
509 DBUS_TYPE_UINT32, &vtnr,
510 DBUS_TYPE_STRING, &tty,
511 DBUS_TYPE_STRING, &display,
512 DBUS_TYPE_BOOLEAN, &remote,
513 DBUS_TYPE_STRING, &remote_user,
514 DBUS_TYPE_STRING, &remote_host,
515 DBUS_TYPE_INVALID)) {
516 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
521 dbus_message_iter_init_append(m, &iter);
523 r = bus_append_strv_iter(&iter, controllers);
525 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
530 r = bus_append_strv_iter(&iter, reset_controllers);
532 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
538 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
539 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
545 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
546 "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",
547 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
549 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
551 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
556 if (!dbus_message_get_args(reply, &error,
557 DBUS_TYPE_STRING, &id,
558 DBUS_TYPE_OBJECT_PATH, &object_path,
559 DBUS_TYPE_STRING, &runtime_path,
560 DBUS_TYPE_UNIX_FD, &session_fd,
561 DBUS_TYPE_STRING, &seat,
562 DBUS_TYPE_UINT32, &vtnr,
563 DBUS_TYPE_BOOLEAN, &existing,
564 DBUS_TYPE_INVALID)) {
565 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
571 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
572 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
573 id, object_path, runtime_path, session_fd, seat, vtnr);
575 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
576 if (r != PAM_SUCCESS) {
577 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
581 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
582 if (r != PAM_SUCCESS) {
583 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
587 if (!isempty(seat)) {
588 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
589 if (r != PAM_SUCCESS) {
590 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
597 snprintf(buf, sizeof(buf), "%u", vtnr);
600 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
601 if (r != PAM_SUCCESS) {
602 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
607 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
608 if (r != PAM_SUCCESS) {
609 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
613 if (session_fd >= 0) {
614 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
615 if (r != PAM_SUCCESS) {
616 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
626 strv_free(controllers);
627 strv_free(reset_controllers);
628 strv_free(kill_only_users);
629 strv_free(kill_exclude_users);
631 dbus_error_free(&error);
634 dbus_connection_close(bus);
635 dbus_connection_unref(bus);
639 dbus_message_unref(m);
642 dbus_message_unref(reply);
645 close_nointr_nofail(session_fd);
650 _public_ PAM_EXTERN int pam_sm_close_session(
651 pam_handle_t *handle,
653 int argc, const char **argv) {
655 const void *p = NULL, *existing = NULL;
657 DBusConnection *bus = NULL;
658 DBusMessage *m = NULL, *reply = NULL;
664 dbus_error_init(&error);
666 /* Only release session if it wasn't pre-existing when we
667 * tried to create it */
668 pam_get_data(handle, "systemd.existing", &existing);
670 id = pam_getenv(handle, "XDG_SESSION_ID");
671 if (id && !existing) {
673 /* Before we go and close the FIFO we need to tell
674 * logind that this is a clean session shutdown, so
675 * that it doesn't just go and slaughter us
676 * immediately after closing the fd */
678 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
680 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
685 m = dbus_message_new_method_call(
686 "org.freedesktop.login1",
687 "/org/freedesktop/login1",
688 "org.freedesktop.login1.Manager",
691 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
696 if (!dbus_message_append_args(m,
697 DBUS_TYPE_STRING, &id,
698 DBUS_TYPE_INVALID)) {
699 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
704 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
706 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
715 pam_get_data(handle, "systemd.session-fd", &p);
717 close_nointr(PTR_TO_INT(p) - 1);
719 dbus_error_free(&error);
722 dbus_connection_close(bus);
723 dbus_connection_unref(bus);
727 dbus_message_unref(m);
730 dbus_message_unref(reply);