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 = NULL, *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
457 * long time and it probably shouldn't stop doing that
458 * for compatibility reasons. */
460 type = "unspecified";
461 } else if (streq(tty, "ssh")) {
462 /* ssh has been setting PAM_TTY to "ssh" for a very
463 * long time and probably shouldn't stop doing that
464 * for compatibility reasons. */
469 /* If this fails vtnr will be 0, that's intended */
471 safe_atou32(cvtnr, &vtnr);
473 if (!isempty(display) && vtnr <= 0) {
475 get_seat_from_display(display, &seat, &vtnr);
476 else if (streq(seat, "seat0"))
477 get_seat_from_display(display, NULL, &vtnr);
481 type = !isempty(display) ? "x11" :
482 !isempty(tty) ? "tty" : "unspecified";
484 class = pam_getenv(handle, "XDG_SESSION_CLASS");
486 class = getenv("XDG_SESSION_CLASS");
490 remote = !isempty(remote_host) &&
491 !streq(remote_host, "localhost") &&
492 !streq(remote_host, "localhost.localdomain");
494 if (!dbus_message_append_args(m,
495 DBUS_TYPE_UINT32, &uid,
496 DBUS_TYPE_UINT32, &pid,
497 DBUS_TYPE_STRING, &service,
498 DBUS_TYPE_STRING, &type,
499 DBUS_TYPE_STRING, &class,
500 DBUS_TYPE_STRING, &seat,
501 DBUS_TYPE_UINT32, &vtnr,
502 DBUS_TYPE_STRING, &tty,
503 DBUS_TYPE_STRING, &display,
504 DBUS_TYPE_BOOLEAN, &remote,
505 DBUS_TYPE_STRING, &remote_user,
506 DBUS_TYPE_STRING, &remote_host,
507 DBUS_TYPE_INVALID)) {
508 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
513 dbus_message_iter_init_append(m, &iter);
515 r = bus_append_strv_iter(&iter, controllers);
517 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
522 r = bus_append_strv_iter(&iter, reset_controllers);
524 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
530 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
531 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
537 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
538 "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",
539 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
541 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
543 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
548 if (!dbus_message_get_args(reply, &error,
549 DBUS_TYPE_STRING, &id,
550 DBUS_TYPE_OBJECT_PATH, &object_path,
551 DBUS_TYPE_STRING, &runtime_path,
552 DBUS_TYPE_UNIX_FD, &session_fd,
553 DBUS_TYPE_STRING, &seat,
554 DBUS_TYPE_UINT32, &vtnr,
555 DBUS_TYPE_BOOLEAN, &existing,
556 DBUS_TYPE_INVALID)) {
557 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
563 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
564 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
565 id, object_path, runtime_path, session_fd, seat, vtnr);
567 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
568 if (r != PAM_SUCCESS) {
569 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
573 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
574 if (r != PAM_SUCCESS) {
575 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
579 if (!isempty(seat)) {
580 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
581 if (r != PAM_SUCCESS) {
582 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
589 snprintf(buf, sizeof(buf), "%u", vtnr);
592 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
593 if (r != PAM_SUCCESS) {
594 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
599 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
600 if (r != PAM_SUCCESS) {
601 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
605 if (session_fd >= 0) {
606 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
607 if (r != PAM_SUCCESS) {
608 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
618 strv_free(controllers);
619 strv_free(reset_controllers);
620 strv_free(kill_only_users);
621 strv_free(kill_exclude_users);
623 dbus_error_free(&error);
626 dbus_connection_close(bus);
627 dbus_connection_unref(bus);
631 dbus_message_unref(m);
634 dbus_message_unref(reply);
637 close_nointr_nofail(session_fd);
642 _public_ PAM_EXTERN int pam_sm_close_session(
643 pam_handle_t *handle,
645 int argc, const char **argv) {
647 const void *p = NULL, *existing = NULL;
649 DBusConnection *bus = NULL;
650 DBusMessage *m = NULL, *reply = NULL;
656 dbus_error_init(&error);
658 /* Only release session if it wasn't pre-existing when we
659 * tried to create it */
660 pam_get_data(handle, "systemd.existing", &existing);
662 id = pam_getenv(handle, "XDG_SESSION_ID");
663 if (id && !existing) {
665 /* Before we go and close the FIFO we need to tell
666 * logind that this is a clean session shutdown, so
667 * that it doesn't just go and slaughter us
668 * immediately after closing the fd */
670 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
672 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
677 m = dbus_message_new_method_call(
678 "org.freedesktop.login1",
679 "/org/freedesktop/login1",
680 "org.freedesktop.login1.Manager",
683 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
688 if (!dbus_message_append_args(m,
689 DBUS_TYPE_STRING, &id,
690 DBUS_TYPE_INVALID)) {
691 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
696 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
698 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
707 pam_get_data(handle, "systemd.session-fd", &p);
709 close_nointr(PTR_TO_INT(p) - 1);
711 dbus_error_free(&error);
714 dbus_connection_close(bus);
715 dbus_connection_unref(bus);
719 dbus_message_unref(m);
722 dbus_message_unref(reply);