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,
52 assert(argc == 0 || argv);
54 for (i = 0; i < (unsigned) argc; i++) {
57 if (startswith(argv[i], "class=")) {
62 } else if (startswith(argv[i], "debug=")) {
63 k = parse_boolean(argv[i] + 6);
66 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
74 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
82 static int get_user_data(
84 const char **ret_username,
85 struct passwd **ret_pw) {
87 const char *username = NULL;
88 struct passwd *pw = NULL;
96 r = audit_loginuid_from_pid(0, &uid);
98 pw = pam_modutil_getpwuid(handle, uid);
100 r = pam_get_user(handle, &username, NULL);
101 if (r != PAM_SUCCESS) {
102 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
106 if (isempty(username)) {
107 pam_syslog(handle, LOG_ERR, "User name not valid.");
111 pw = pam_modutil_getpwnam(handle, username);
115 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
116 return PAM_USER_UNKNOWN;
120 *ret_username = username ? username : pw->pw_name;
125 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
126 _cleanup_free_ char *p = NULL;
128 _cleanup_close_ int fd = -1;
129 union sockaddr_union sa = {
130 .un.sun_family = AF_UNIX,
134 _cleanup_free_ char *tty = NULL;
140 /* We deduce the X11 socket from the display name, then use
141 * SO_PEERCRED to determine the X11 server process, ask for
142 * the controlling tty of that and if it's a VC then we know
143 * the seat and the virtual terminal. Sounds ugly, is only
146 r = socket_from_display(display, &p);
149 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
151 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
155 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
159 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
163 r = get_ctty(ucred.pid, NULL, &tty);
167 v = vtnr_from_tty(tty);
175 *vtnr = (uint32_t) v;
180 _public_ PAM_EXTERN int pam_sm_open_session(
181 pam_handle_t *handle,
183 int argc, const char **argv) {
187 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;
190 DBusMessageIter iter;
192 DBusConnection *bus = NULL;
193 DBusMessage *m = NULL, *reply = NULL;
194 dbus_bool_t remote, existing;
200 dbus_error_init(&error);
202 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
204 /* Make this a NOP on non-logind systems */
205 if (!logind_running())
208 if (parse_argv(handle,
216 r = get_user_data(handle, &username, &pw);
217 if (r != PAM_SUCCESS)
220 /* Make sure we don't enter a loop by talking to
221 * systemd-logind when it is actually waiting for the
222 * background to finish start-up. If the service is
223 * "systemd-user" we simply set XDG_RUNTIME_DIR and
226 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
227 if (streq_ptr(service, "systemd-user")) {
230 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
235 r = parse_env_file(p, NEWLINE,
240 if (r < 0 && r != -ENOENT) {
247 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
250 if (r != PAM_SUCCESS) {
251 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
260 dbus_connection_set_change_sigpipe(FALSE);
262 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
264 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
269 m = dbus_message_new_method_call(
270 "org.freedesktop.login1",
271 "/org/freedesktop/login1",
272 "org.freedesktop.login1.Manager",
275 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
283 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
284 pam_get_item(handle, PAM_TTY, (const void**) &tty);
285 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
286 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
288 seat = pam_getenv(handle, "XDG_SEAT");
290 seat = getenv("XDG_SEAT");
292 cvtnr = pam_getenv(handle, "XDG_VTNR");
294 cvtnr = getenv("XDG_VTNR");
296 service = strempty(service);
298 display = strempty(display);
299 remote_user = strempty(remote_user);
300 remote_host = strempty(remote_host);
301 seat = strempty(seat);
303 if (strchr(tty, ':')) {
304 /* A tty with a colon is usually an X11 display,
305 * placed there to show up in utmp. We rearrange
306 * things and don't pretend that an X display was a
309 if (isempty(display))
312 } else if (streq(tty, "cron")) {
313 /* cron has been setting PAM_TTY to "cron" for a very
314 * long time and it probably shouldn't stop doing that
315 * for compatibility reasons. */
317 type = "unspecified";
318 } else if (streq(tty, "ssh")) {
319 /* ssh has been setting PAM_TTY to "ssh" for a very
320 * long time and probably shouldn't stop doing that
321 * for compatibility reasons. */
326 /* If this fails vtnr will be 0, that's intended */
328 safe_atou32(cvtnr, &vtnr);
330 if (!isempty(display) && vtnr <= 0) {
332 get_seat_from_display(display, &seat, &vtnr);
333 else if (streq(seat, "seat0"))
334 get_seat_from_display(display, NULL, &vtnr);
338 type = !isempty(display) ? "x11" :
339 !isempty(tty) ? "tty" : "unspecified";
341 class = pam_getenv(handle, "XDG_SESSION_CLASS");
343 class = getenv("XDG_SESSION_CLASS");
347 class = streq(type, "unspecified") ? "background" : "user";
349 remote = !isempty(remote_host) &&
350 !streq(remote_host, "localhost") &&
351 !streq(remote_host, "localhost.localdomain");
353 if (!dbus_message_append_args(m,
354 DBUS_TYPE_UINT32, &uid,
355 DBUS_TYPE_UINT32, &pid,
356 DBUS_TYPE_STRING, &service,
357 DBUS_TYPE_STRING, &type,
358 DBUS_TYPE_STRING, &class,
359 DBUS_TYPE_STRING, &seat,
360 DBUS_TYPE_UINT32, &vtnr,
361 DBUS_TYPE_STRING, &tty,
362 DBUS_TYPE_STRING, &display,
363 DBUS_TYPE_BOOLEAN, &remote,
364 DBUS_TYPE_STRING, &remote_user,
365 DBUS_TYPE_STRING, &remote_host,
366 DBUS_TYPE_INVALID)) {
367 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
372 dbus_message_iter_init_append(m, &iter);
375 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
376 "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",
377 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
379 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
381 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
386 if (!dbus_message_get_args(reply, &error,
387 DBUS_TYPE_STRING, &id,
388 DBUS_TYPE_OBJECT_PATH, &object_path,
389 DBUS_TYPE_STRING, &runtime_path,
390 DBUS_TYPE_UNIX_FD, &session_fd,
391 DBUS_TYPE_STRING, &seat,
392 DBUS_TYPE_UINT32, &vtnr,
393 DBUS_TYPE_BOOLEAN, &existing,
394 DBUS_TYPE_INVALID)) {
395 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
401 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
402 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
403 id, object_path, runtime_path, session_fd, seat, vtnr);
405 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
406 if (r != PAM_SUCCESS) {
407 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
411 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
412 if (r != PAM_SUCCESS) {
413 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
417 if (!isempty(seat)) {
418 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
419 if (r != PAM_SUCCESS) {
420 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
427 snprintf(buf, sizeof(buf), "%u", vtnr);
430 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
431 if (r != PAM_SUCCESS) {
432 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
437 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
438 if (r != PAM_SUCCESS) {
439 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
443 if (session_fd >= 0) {
444 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
445 if (r != PAM_SUCCESS) {
446 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
456 dbus_error_free(&error);
459 dbus_connection_close(bus);
460 dbus_connection_unref(bus);
464 dbus_message_unref(m);
467 dbus_message_unref(reply);
470 close_nointr_nofail(session_fd);
475 _public_ PAM_EXTERN int pam_sm_close_session(
476 pam_handle_t *handle,
478 int argc, const char **argv) {
480 const void *p = NULL, *existing = NULL;
482 DBusConnection *bus = NULL;
483 DBusMessage *m = NULL, *reply = NULL;
489 dbus_error_init(&error);
491 /* Only release session if it wasn't pre-existing when we
492 * tried to create it */
493 pam_get_data(handle, "systemd.existing", &existing);
495 id = pam_getenv(handle, "XDG_SESSION_ID");
496 if (id && !existing) {
498 /* Before we go and close the FIFO we need to tell
499 * logind that this is a clean session shutdown, so
500 * that it doesn't just go and slaughter us
501 * immediately after closing the fd */
503 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
505 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
510 m = dbus_message_new_method_call(
511 "org.freedesktop.login1",
512 "/org/freedesktop/login1",
513 "org.freedesktop.login1.Manager",
516 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
521 if (!dbus_message_append_args(m,
522 DBUS_TYPE_STRING, &id,
523 DBUS_TYPE_INVALID)) {
524 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
529 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
531 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
540 pam_get_data(handle, "systemd.session-fd", &p);
542 close_nointr(PTR_TO_INT(p) - 1);
544 dbus_error_free(&error);
547 dbus_connection_close(bus);
548 dbus_connection_unref(bus);
552 dbus_message_unref(m);
555 dbus_message_unref(reply);