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 _cleanup_free_ char *p = NULL;
261 _cleanup_close_ int fd = -1;
262 union sockaddr_union sa = {
263 .un.sun_family = AF_UNIX,
267 _cleanup_free_ char *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,
444 * placed there to show up in utmp. We rearrange
445 * things and don't pretend that an X display was a
448 if (isempty(display))
451 } else if (streq(tty, "cron")) {
452 /* cron has been setting PAM_TTY to "cron" for a very
453 * long time and it probably shouldn't stop doing that
454 * for compatibility reasons. */
456 type = "unspecified";
457 } else if (streq(tty, "ssh")) {
458 /* ssh has been setting PAM_TTY to "ssh" for a very
459 * long time and probably shouldn't stop doing that
460 * for compatibility reasons. */
465 /* If this fails vtnr will be 0, that's intended */
467 safe_atou32(cvtnr, &vtnr);
469 if (!isempty(display) && vtnr <= 0) {
471 get_seat_from_display(display, &seat, &vtnr);
472 else if (streq(seat, "seat0"))
473 get_seat_from_display(display, NULL, &vtnr);
477 type = !isempty(display) ? "x11" :
478 !isempty(tty) ? "tty" : "unspecified";
480 class = pam_getenv(handle, "XDG_SESSION_CLASS");
482 class = getenv("XDG_SESSION_CLASS");
486 class = streq(type, "unspecified") ? "background" : "user";
488 remote = !isempty(remote_host) &&
489 !streq(remote_host, "localhost") &&
490 !streq(remote_host, "localhost.localdomain");
492 if (!dbus_message_append_args(m,
493 DBUS_TYPE_UINT32, &uid,
494 DBUS_TYPE_UINT32, &pid,
495 DBUS_TYPE_STRING, &service,
496 DBUS_TYPE_STRING, &type,
497 DBUS_TYPE_STRING, &class,
498 DBUS_TYPE_STRING, &seat,
499 DBUS_TYPE_UINT32, &vtnr,
500 DBUS_TYPE_STRING, &tty,
501 DBUS_TYPE_STRING, &display,
502 DBUS_TYPE_BOOLEAN, &remote,
503 DBUS_TYPE_STRING, &remote_user,
504 DBUS_TYPE_STRING, &remote_host,
505 DBUS_TYPE_INVALID)) {
506 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
511 dbus_message_iter_init_append(m, &iter);
513 r = bus_append_strv_iter(&iter, controllers);
515 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
520 r = bus_append_strv_iter(&iter, reset_controllers);
522 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
528 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
529 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
535 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
536 "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",
537 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
539 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
541 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
546 if (!dbus_message_get_args(reply, &error,
547 DBUS_TYPE_STRING, &id,
548 DBUS_TYPE_OBJECT_PATH, &object_path,
549 DBUS_TYPE_STRING, &runtime_path,
550 DBUS_TYPE_UNIX_FD, &session_fd,
551 DBUS_TYPE_STRING, &seat,
552 DBUS_TYPE_UINT32, &vtnr,
553 DBUS_TYPE_BOOLEAN, &existing,
554 DBUS_TYPE_INVALID)) {
555 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
561 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
562 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
563 id, object_path, runtime_path, session_fd, seat, vtnr);
565 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
566 if (r != PAM_SUCCESS) {
567 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
571 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
572 if (r != PAM_SUCCESS) {
573 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
577 if (!isempty(seat)) {
578 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
579 if (r != PAM_SUCCESS) {
580 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
587 snprintf(buf, sizeof(buf), "%u", vtnr);
590 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
591 if (r != PAM_SUCCESS) {
592 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
597 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
598 if (r != PAM_SUCCESS) {
599 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
603 if (session_fd >= 0) {
604 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
605 if (r != PAM_SUCCESS) {
606 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
616 strv_free(controllers);
617 strv_free(reset_controllers);
618 strv_free(kill_only_users);
619 strv_free(kill_exclude_users);
621 dbus_error_free(&error);
624 dbus_connection_close(bus);
625 dbus_connection_unref(bus);
629 dbus_message_unref(m);
632 dbus_message_unref(reply);
635 close_nointr_nofail(session_fd);
640 _public_ PAM_EXTERN int pam_sm_close_session(
641 pam_handle_t *handle,
643 int argc, const char **argv) {
645 const void *p = NULL, *existing = NULL;
647 DBusConnection *bus = NULL;
648 DBusMessage *m = NULL, *reply = NULL;
654 dbus_error_init(&error);
656 /* Only release session if it wasn't pre-existing when we
657 * tried to create it */
658 pam_get_data(handle, "systemd.existing", &existing);
660 id = pam_getenv(handle, "XDG_SESSION_ID");
661 if (id && !existing) {
663 /* Before we go and close the FIFO we need to tell
664 * logind that this is a clean session shutdown, so
665 * that it doesn't just go and slaughter us
666 * immediately after closing the fd */
668 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
670 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
675 m = dbus_message_new_method_call(
676 "org.freedesktop.login1",
677 "/org/freedesktop/login1",
678 "org.freedesktop.login1.Manager",
681 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
686 if (!dbus_message_append_args(m,
687 DBUS_TYPE_STRING, &id,
688 DBUS_TYPE_INVALID)) {
689 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
694 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
696 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
705 pam_get_data(handle, "systemd.session-fd", &p);
707 close_nointr(PTR_TO_INT(p) - 1);
709 dbus_error_free(&error);
712 dbus_connection_close(bus);
713 dbus_connection_unref(bus);
717 dbus_message_unref(m);
720 dbus_message_unref(reply);