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"
46 static int parse_argv(pam_handle_t *handle,
47 int argc, const char **argv,
49 char ***reset_controllers,
51 char ***kill_only_users,
52 char ***kill_exclude_users,
59 assert(argc == 0 || argv);
61 for (i = 0; i < (unsigned) argc; i++) {
64 if (startswith(argv[i], "kill-session-processes=")) {
65 if ((k = parse_boolean(argv[i] + 23)) < 0) {
66 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument.");
73 } else if (startswith(argv[i], "kill-session=")) {
74 /* As compatibility for old versions */
76 if ((k = parse_boolean(argv[i] + 13)) < 0) {
77 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument.");
84 } else if (startswith(argv[i], "controllers=")) {
89 if (!(l = strv_split(argv[i] + 12, ","))) {
90 pam_syslog(handle, LOG_ERR, "Out of memory.");
94 strv_free(*controllers);
98 } else if (startswith(argv[i], "reset-controllers=")) {
100 if (reset_controllers) {
103 if (!(l = strv_split(argv[i] + 18, ","))) {
104 pam_syslog(handle, LOG_ERR, "Out of memory.");
108 strv_free(*reset_controllers);
109 *reset_controllers = l;
112 } else if (startswith(argv[i], "kill-only-users=")) {
114 if (kill_only_users) {
117 if (!(l = strv_split(argv[i] + 16, ","))) {
118 pam_syslog(handle, LOG_ERR, "Out of memory.");
122 strv_free(*kill_only_users);
123 *kill_only_users = l;
126 } else if (startswith(argv[i], "kill-exclude-users=")) {
128 if (kill_exclude_users) {
131 if (!(l = strv_split(argv[i] + 19, ","))) {
132 pam_syslog(handle, LOG_ERR, "Out of memory.");
136 strv_free(*kill_exclude_users);
137 *kill_exclude_users = l;
140 } else if (startswith(argv[i], "class=")) {
143 *class = argv[i] + 6;
145 } else if (startswith(argv[i], "debug=")) {
146 if ((k = parse_boolean(argv[i] + 6)) < 0) {
147 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
154 } else if (startswith(argv[i], "create-session=") ||
155 startswith(argv[i], "kill-user=")) {
157 pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
160 pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
168 static int get_user_data(
169 pam_handle_t *handle,
170 const char **ret_username,
171 struct passwd **ret_pw) {
173 const char *username = NULL;
174 struct passwd *pw = NULL;
179 assert(ret_username);
182 r = audit_loginuid_from_pid(0, &uid);
184 pw = pam_modutil_getpwuid(handle, uid);
186 r = pam_get_user(handle, &username, NULL);
187 if (r != PAM_SUCCESS) {
188 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
192 if (isempty(username)) {
193 pam_syslog(handle, LOG_ERR, "User name not valid.");
197 pw = pam_modutil_getpwnam(handle, username);
201 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
202 return PAM_USER_UNKNOWN;
206 *ret_username = username ? username : pw->pw_name;
211 static bool check_user_lists(
212 pam_handle_t *handle,
214 char **kill_only_users,
215 char **kill_exclude_users) {
217 const char *name = NULL;
223 name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
227 pw = pam_modutil_getpwuid(handle, uid);
232 STRV_FOREACH(l, kill_exclude_users) {
235 if (parse_uid(*l, &u) >= 0)
239 if (name && streq(name, *l))
243 if (strv_isempty(kill_only_users))
246 STRV_FOREACH(l, kill_only_users) {
249 if (parse_uid(*l, &u) >= 0)
253 if (name && streq(name, *l))
260 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
264 union sockaddr_union sa;
273 /* We deduce the X11 socket from the display name, then use
274 * SO_PEERCRED to determine the X11 server process, ask for
275 * the controlling tty of that and if it's a VC then we know
276 * the seat and the virtual terminal. Sounds ugly, is only
279 r = socket_from_display(display, &p);
283 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
290 sa.un.sun_family = AF_UNIX;
291 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
294 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
295 close_nointr_nofail(fd);
300 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
301 close_nointr_nofail(fd);
306 r = get_ctty(ucred.pid, NULL, &tty);
310 v = vtnr_from_tty(tty);
320 *vtnr = (uint32_t) v;
325 _public_ PAM_EXTERN int pam_sm_open_session(
326 pam_handle_t *handle,
328 int argc, const char **argv) {
331 bool kill_processes = false, debug = false;
332 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;
333 char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
336 DBusMessageIter iter;
339 DBusConnection *bus = NULL;
340 DBusMessage *m = NULL, *reply = NULL;
341 dbus_bool_t remote, existing;
347 dbus_error_init(&error);
349 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
351 /* Make this a NOP on non-systemd systems */
352 if (sd_booted() <= 0)
355 if (parse_argv(handle,
357 &controllers, &reset_controllers,
358 &kill_processes, &kill_only_users, &kill_exclude_users,
359 &class_pam, &debug) < 0) {
364 r = get_user_data(handle, &username, &pw);
365 if (r != PAM_SUCCESS)
368 /* Make sure we don't enter a loop by talking to
369 * systemd-logind when it is actually waiting for the
370 * background to finish start-up. If the service is
371 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
374 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
375 if (streq_ptr(service, "systemd-shared")) {
378 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
383 r = parse_env_file(p, NEWLINE,
388 if (r < 0 && r != -ENOENT) {
395 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
398 if (r != PAM_SUCCESS) {
399 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
409 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
411 dbus_connection_set_change_sigpipe(FALSE);
413 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
415 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
420 m = dbus_message_new_method_call(
421 "org.freedesktop.login1",
422 "/org/freedesktop/login1",
423 "org.freedesktop.login1.Manager",
426 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
434 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
435 pam_get_item(handle, PAM_TTY, (const void**) &tty);
436 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
437 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
439 seat = pam_getenv(handle, "XDG_SEAT");
441 seat = getenv("XDG_SEAT");
443 cvtnr = pam_getenv(handle, "XDG_VTNR");
445 cvtnr = getenv("XDG_VTNR");
447 service = strempty(service);
449 display = strempty(display);
450 remote_user = strempty(remote_user);
451 remote_host = strempty(remote_host);
452 seat = strempty(seat);
454 if (strchr(tty, ':')) {
455 /* A tty with a colon is usually an X11 display, place
456 * there to show up in utmp. We rearrange things and
457 * don't pretend that an X display was a tty */
459 if (isempty(display))
462 } else if (streq(tty, "cron")) {
463 /* cron has been setting PAM_TTY to "cron" for a very
464 * long time and it probably shouldn't stop doing that
465 * for compatibility reasons. */
467 type = "unspecified";
468 } else if (streq(tty, "ssh")) {
469 /* ssh has been setting PAM_TTY to "ssh" for a very
470 * long time and probably shouldn't stop doing that
471 * for compatibility reasons. */
476 /* If this fails vtnr will be 0, that's intended */
478 safe_atou32(cvtnr, &vtnr);
480 if (!isempty(display) && vtnr <= 0) {
482 get_seat_from_display(display, &seat, &vtnr);
483 else if (streq(seat, "seat0"))
484 get_seat_from_display(display, NULL, &vtnr);
488 type = !isempty(display) ? "x11" :
489 !isempty(tty) ? "tty" : "unspecified";
491 class = pam_getenv(handle, "XDG_SESSION_CLASS");
493 class = getenv("XDG_SESSION_CLASS");
499 remote = !isempty(remote_host) &&
500 !streq(remote_host, "localhost") &&
501 !streq(remote_host, "localhost.localdomain");
503 if (!dbus_message_append_args(m,
504 DBUS_TYPE_UINT32, &uid,
505 DBUS_TYPE_UINT32, &pid,
506 DBUS_TYPE_STRING, &service,
507 DBUS_TYPE_STRING, &type,
508 DBUS_TYPE_STRING, &class,
509 DBUS_TYPE_STRING, &seat,
510 DBUS_TYPE_UINT32, &vtnr,
511 DBUS_TYPE_STRING, &tty,
512 DBUS_TYPE_STRING, &display,
513 DBUS_TYPE_BOOLEAN, &remote,
514 DBUS_TYPE_STRING, &remote_user,
515 DBUS_TYPE_STRING, &remote_host,
516 DBUS_TYPE_INVALID)) {
517 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
522 dbus_message_iter_init_append(m, &iter);
524 r = bus_append_strv_iter(&iter, controllers);
526 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
531 r = bus_append_strv_iter(&iter, reset_controllers);
533 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
539 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
540 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
546 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
547 "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",
548 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
550 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
552 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
557 if (!dbus_message_get_args(reply, &error,
558 DBUS_TYPE_STRING, &id,
559 DBUS_TYPE_OBJECT_PATH, &object_path,
560 DBUS_TYPE_STRING, &runtime_path,
561 DBUS_TYPE_UNIX_FD, &session_fd,
562 DBUS_TYPE_STRING, &seat,
563 DBUS_TYPE_UINT32, &vtnr,
564 DBUS_TYPE_BOOLEAN, &existing,
565 DBUS_TYPE_INVALID)) {
566 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
572 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
573 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
574 id, object_path, runtime_path, session_fd, seat, vtnr);
576 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
577 if (r != PAM_SUCCESS) {
578 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
582 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
583 if (r != PAM_SUCCESS) {
584 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
588 if (!isempty(seat)) {
589 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
590 if (r != PAM_SUCCESS) {
591 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
598 snprintf(buf, sizeof(buf), "%u", vtnr);
601 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
602 if (r != PAM_SUCCESS) {
603 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
608 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
609 if (r != PAM_SUCCESS) {
610 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
614 if (session_fd >= 0) {
615 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
616 if (r != PAM_SUCCESS) {
617 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
627 strv_free(controllers);
628 strv_free(reset_controllers);
629 strv_free(kill_only_users);
630 strv_free(kill_exclude_users);
632 dbus_error_free(&error);
635 dbus_connection_close(bus);
636 dbus_connection_unref(bus);
640 dbus_message_unref(m);
643 dbus_message_unref(reply);
646 close_nointr_nofail(session_fd);
651 _public_ PAM_EXTERN int pam_sm_close_session(
652 pam_handle_t *handle,
654 int argc, const char **argv) {
656 const void *p = NULL, *existing = NULL;
658 DBusConnection *bus = NULL;
659 DBusMessage *m = NULL, *reply = NULL;
665 dbus_error_init(&error);
667 /* Only release session if it wasn't pre-existing when we
668 * tried to create it */
669 pam_get_data(handle, "systemd.existing", &existing);
671 id = pam_getenv(handle, "XDG_SESSION_ID");
672 if (id && !existing) {
674 /* Before we go and close the FIFO we need to tell
675 * logind that this is a clean session shutdown, so
676 * that it doesn't just go and slaughter us
677 * immediately after closing the fd */
679 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
681 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
686 m = dbus_message_new_method_call(
687 "org.freedesktop.login1",
688 "/org/freedesktop/login1",
689 "org.freedesktop.login1.Manager",
692 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
697 if (!dbus_message_append_args(m,
698 DBUS_TYPE_STRING, &id,
699 DBUS_TYPE_INVALID)) {
700 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
705 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
707 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
716 pam_get_data(handle, "systemd.session-fd", &p);
718 close_nointr(PTR_TO_INT(p) - 1);
720 dbus_error_free(&error);
723 dbus_connection_close(bus);
724 dbus_connection_unref(bus);
728 dbus_message_unref(m);
731 dbus_message_unref(reply);