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) {
259 char _cleanup_free_ *p = NULL;
261 int _cleanup_close_ fd = -1;
262 union sockaddr_union sa = {
263 .un.sun_family = AF_UNIX,
267 char _cleanup_free_ *tty = NULL;
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);
282 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
284 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
288 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
292 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
296 r = get_ctty(ucred.pid, NULL, &tty);
300 v = vtnr_from_tty(tty);
308 *vtnr = (uint32_t) v;
313 _public_ PAM_EXTERN int pam_sm_open_session(
314 pam_handle_t *handle,
316 int argc, const char **argv) {
319 bool kill_processes = false, debug = false;
320 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;
321 char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
324 DBusMessageIter iter;
327 DBusConnection *bus = NULL;
328 DBusMessage *m = NULL, *reply = NULL;
329 dbus_bool_t remote, existing;
335 dbus_error_init(&error);
337 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
339 /* Make this a NOP on non-logind systems */
340 if (!logind_running())
343 if (parse_argv(handle,
345 &controllers, &reset_controllers,
346 &kill_processes, &kill_only_users, &kill_exclude_users,
347 &class_pam, &debug) < 0) {
352 r = get_user_data(handle, &username, &pw);
353 if (r != PAM_SUCCESS)
356 /* Make sure we don't enter a loop by talking to
357 * systemd-logind when it is actually waiting for the
358 * background to finish start-up. If the service is
359 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
362 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
363 if (streq_ptr(service, "systemd-shared")) {
366 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
371 r = parse_env_file(p, NEWLINE,
376 if (r < 0 && r != -ENOENT) {
383 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
386 if (r != PAM_SUCCESS) {
387 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
397 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
399 dbus_connection_set_change_sigpipe(FALSE);
401 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
403 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
408 m = dbus_message_new_method_call(
409 "org.freedesktop.login1",
410 "/org/freedesktop/login1",
411 "org.freedesktop.login1.Manager",
414 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
422 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
423 pam_get_item(handle, PAM_TTY, (const void**) &tty);
424 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
425 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
427 seat = pam_getenv(handle, "XDG_SEAT");
429 seat = getenv("XDG_SEAT");
431 cvtnr = pam_getenv(handle, "XDG_VTNR");
433 cvtnr = getenv("XDG_VTNR");
435 service = strempty(service);
437 display = strempty(display);
438 remote_user = strempty(remote_user);
439 remote_host = strempty(remote_host);
440 seat = strempty(seat);
442 if (strchr(tty, ':')) {
443 /* A tty with a colon is usually an X11 display, place
444 * there to show up in utmp. We rearrange things and
445 * don't pretend that an X display was a tty */
447 if (isempty(display))
450 } else if (streq(tty, "cron")) {
451 /* cron has been setting PAM_TTY to "cron" for a very
452 * long time and it probably shouldn't stop doing that
453 * for compatibility reasons. */
455 type = "unspecified";
456 } else if (streq(tty, "ssh")) {
457 /* ssh has been setting PAM_TTY to "ssh" for a very
458 * long time and probably shouldn't stop doing that
459 * for compatibility reasons. */
464 /* If this fails vtnr will be 0, that's intended */
466 safe_atou32(cvtnr, &vtnr);
468 if (!isempty(display) && vtnr <= 0) {
470 get_seat_from_display(display, &seat, &vtnr);
471 else if (streq(seat, "seat0"))
472 get_seat_from_display(display, NULL, &vtnr);
476 type = !isempty(display) ? "x11" :
477 !isempty(tty) ? "tty" : "unspecified";
479 class = pam_getenv(handle, "XDG_SESSION_CLASS");
481 class = getenv("XDG_SESSION_CLASS");
487 remote = !isempty(remote_host) &&
488 !streq(remote_host, "localhost") &&
489 !streq(remote_host, "localhost.localdomain");
491 if (!dbus_message_append_args(m,
492 DBUS_TYPE_UINT32, &uid,
493 DBUS_TYPE_UINT32, &pid,
494 DBUS_TYPE_STRING, &service,
495 DBUS_TYPE_STRING, &type,
496 DBUS_TYPE_STRING, &class,
497 DBUS_TYPE_STRING, &seat,
498 DBUS_TYPE_UINT32, &vtnr,
499 DBUS_TYPE_STRING, &tty,
500 DBUS_TYPE_STRING, &display,
501 DBUS_TYPE_BOOLEAN, &remote,
502 DBUS_TYPE_STRING, &remote_user,
503 DBUS_TYPE_STRING, &remote_host,
504 DBUS_TYPE_INVALID)) {
505 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
510 dbus_message_iter_init_append(m, &iter);
512 r = bus_append_strv_iter(&iter, controllers);
514 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
519 r = bus_append_strv_iter(&iter, reset_controllers);
521 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
527 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
528 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
534 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
535 "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",
536 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
538 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
540 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
545 if (!dbus_message_get_args(reply, &error,
546 DBUS_TYPE_STRING, &id,
547 DBUS_TYPE_OBJECT_PATH, &object_path,
548 DBUS_TYPE_STRING, &runtime_path,
549 DBUS_TYPE_UNIX_FD, &session_fd,
550 DBUS_TYPE_STRING, &seat,
551 DBUS_TYPE_UINT32, &vtnr,
552 DBUS_TYPE_BOOLEAN, &existing,
553 DBUS_TYPE_INVALID)) {
554 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
560 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
561 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
562 id, object_path, runtime_path, session_fd, seat, vtnr);
564 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
565 if (r != PAM_SUCCESS) {
566 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
570 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
571 if (r != PAM_SUCCESS) {
572 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
576 if (!isempty(seat)) {
577 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
578 if (r != PAM_SUCCESS) {
579 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
586 snprintf(buf, sizeof(buf), "%u", vtnr);
589 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
590 if (r != PAM_SUCCESS) {
591 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
596 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
597 if (r != PAM_SUCCESS) {
598 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
602 if (session_fd >= 0) {
603 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
604 if (r != PAM_SUCCESS) {
605 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
615 strv_free(controllers);
616 strv_free(reset_controllers);
617 strv_free(kill_only_users);
618 strv_free(kill_exclude_users);
620 dbus_error_free(&error);
623 dbus_connection_close(bus);
624 dbus_connection_unref(bus);
628 dbus_message_unref(m);
631 dbus_message_unref(reply);
634 close_nointr_nofail(session_fd);
639 _public_ PAM_EXTERN int pam_sm_close_session(
640 pam_handle_t *handle,
642 int argc, const char **argv) {
644 const void *p = NULL, *existing = NULL;
646 DBusConnection *bus = NULL;
647 DBusMessage *m = NULL, *reply = NULL;
653 dbus_error_init(&error);
655 /* Only release session if it wasn't pre-existing when we
656 * tried to create it */
657 pam_get_data(handle, "systemd.existing", &existing);
659 id = pam_getenv(handle, "XDG_SESSION_ID");
660 if (id && !existing) {
662 /* Before we go and close the FIFO we need to tell
663 * logind that this is a clean session shutdown, so
664 * that it doesn't just go and slaughter us
665 * immediately after closing the fd */
667 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
669 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
674 m = dbus_message_new_method_call(
675 "org.freedesktop.login1",
676 "/org/freedesktop/login1",
677 "org.freedesktop.login1.Manager",
680 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
685 if (!dbus_message_append_args(m,
686 DBUS_TYPE_STRING, &id,
687 DBUS_TYPE_INVALID)) {
688 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
693 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
695 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
704 pam_get_data(handle, "systemd.session-fd", &p);
706 close_nointr(PTR_TO_INT(p) - 1);
708 dbus_error_free(&error);
711 dbus_connection_close(bus);
712 dbus_connection_unref(bus);
716 dbus_message_unref(m);
719 dbus_message_unref(reply);