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);
203 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
205 /* Make this a NOP on non-logind systems */
206 if (!logind_running())
209 if (parse_argv(handle,
217 r = get_user_data(handle, &username, &pw);
218 if (r != PAM_SUCCESS)
221 /* Make sure we don't enter a loop by talking to
222 * systemd-logind when it is actually waiting for the
223 * background to finish start-up. If the service is
224 * "systemd-user" we simply set XDG_RUNTIME_DIR and
227 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
228 if (streq_ptr(service, "systemd-user")) {
231 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
236 r = parse_env_file(p, NEWLINE,
241 if (r < 0 && r != -ENOENT) {
248 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
251 if (r != PAM_SUCCESS) {
252 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
261 dbus_connection_set_change_sigpipe(FALSE);
263 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
265 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
270 m = dbus_message_new_method_call(
271 "org.freedesktop.login1",
272 "/org/freedesktop/login1",
273 "org.freedesktop.login1.Manager",
276 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
284 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
285 pam_get_item(handle, PAM_TTY, (const void**) &tty);
286 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
287 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
289 seat = pam_getenv(handle, "XDG_SEAT");
291 seat = getenv("XDG_SEAT");
293 cvtnr = pam_getenv(handle, "XDG_VTNR");
295 cvtnr = getenv("XDG_VTNR");
297 service = strempty(service);
299 display = strempty(display);
300 remote_user = strempty(remote_user);
301 remote_host = strempty(remote_host);
302 seat = strempty(seat);
304 if (strchr(tty, ':')) {
305 /* A tty with a colon is usually an X11 display,
306 * placed there to show up in utmp. We rearrange
307 * things and don't pretend that an X display was a
310 if (isempty(display))
313 } else if (streq(tty, "cron")) {
314 /* cron has been setting PAM_TTY to "cron" for a very
315 * long time and it probably shouldn't stop doing that
316 * for compatibility reasons. */
318 type = "unspecified";
319 } else if (streq(tty, "ssh")) {
320 /* ssh has been setting PAM_TTY to "ssh" for a very
321 * long time and probably shouldn't stop doing that
322 * for compatibility reasons. */
327 /* If this fails vtnr will be 0, that's intended */
329 safe_atou32(cvtnr, &vtnr);
331 if (!isempty(display) && vtnr <= 0) {
333 get_seat_from_display(display, &seat, &vtnr);
334 else if (streq(seat, "seat0"))
335 get_seat_from_display(display, NULL, &vtnr);
339 type = !isempty(display) ? "x11" :
340 !isempty(tty) ? "tty" : "unspecified";
342 class = pam_getenv(handle, "XDG_SESSION_CLASS");
344 class = getenv("XDG_SESSION_CLASS");
348 class = streq(type, "unspecified") ? "background" : "user";
350 remote = !isempty(remote_host) &&
351 !streq(remote_host, "localhost") &&
352 !streq(remote_host, "localhost.localdomain");
354 if (!dbus_message_append_args(m,
355 DBUS_TYPE_UINT32, &uid,
356 DBUS_TYPE_UINT32, &pid,
357 DBUS_TYPE_STRING, &service,
358 DBUS_TYPE_STRING, &type,
359 DBUS_TYPE_STRING, &class,
360 DBUS_TYPE_STRING, &seat,
361 DBUS_TYPE_UINT32, &vtnr,
362 DBUS_TYPE_STRING, &tty,
363 DBUS_TYPE_STRING, &display,
364 DBUS_TYPE_BOOLEAN, &remote,
365 DBUS_TYPE_STRING, &remote_user,
366 DBUS_TYPE_STRING, &remote_host,
367 DBUS_TYPE_INVALID)) {
368 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
373 dbus_message_iter_init_append(m, &iter);
376 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
377 "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",
378 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
380 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
382 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
387 if (!dbus_message_get_args(reply, &error,
388 DBUS_TYPE_STRING, &id,
389 DBUS_TYPE_OBJECT_PATH, &object_path,
390 DBUS_TYPE_STRING, &runtime_path,
391 DBUS_TYPE_UNIX_FD, &session_fd,
392 DBUS_TYPE_STRING, &seat,
393 DBUS_TYPE_UINT32, &vtnr,
394 DBUS_TYPE_BOOLEAN, &existing,
395 DBUS_TYPE_INVALID)) {
396 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
402 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
403 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
404 id, object_path, runtime_path, session_fd, seat, vtnr);
406 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
407 if (r != PAM_SUCCESS) {
408 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
412 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
413 if (r != PAM_SUCCESS) {
414 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
418 if (!isempty(seat)) {
419 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
420 if (r != PAM_SUCCESS) {
421 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
428 snprintf(buf, sizeof(buf), "%u", vtnr);
431 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
432 if (r != PAM_SUCCESS) {
433 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
438 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
439 if (r != PAM_SUCCESS) {
440 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
444 if (session_fd >= 0) {
445 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
446 if (r != PAM_SUCCESS) {
447 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
457 dbus_error_free(&error);
460 dbus_connection_close(bus);
461 dbus_connection_unref(bus);
465 dbus_message_unref(m);
468 dbus_message_unref(reply);
471 close_nointr_nofail(session_fd);
476 _public_ PAM_EXTERN int pam_sm_close_session(
477 pam_handle_t *handle,
479 int argc, const char **argv) {
481 const void *p = NULL, *existing = NULL;
483 DBusConnection *bus = NULL;
484 DBusMessage *m = NULL, *reply = NULL;
490 dbus_error_init(&error);
492 /* Only release session if it wasn't pre-existing when we
493 * tried to create it */
494 pam_get_data(handle, "systemd.existing", &existing);
496 id = pam_getenv(handle, "XDG_SESSION_ID");
497 if (id && !existing) {
499 /* Before we go and close the FIFO we need to tell
500 * logind that this is a clean session shutdown, so
501 * that it doesn't just go and slaughter us
502 * immediately after closing the fd */
504 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
506 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
511 m = dbus_message_new_method_call(
512 "org.freedesktop.login1",
513 "/org/freedesktop/login1",
514 "org.freedesktop.login1.Manager",
517 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
522 if (!dbus_message_append_args(m,
523 DBUS_TYPE_STRING, &id,
524 DBUS_TYPE_INVALID)) {
525 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
530 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
532 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
541 pam_get_data(handle, "systemd.session-fd", &p);
543 close_nointr(PTR_TO_INT(p) - 1);
545 dbus_error_free(&error);
548 dbus_connection_close(bus);
549 dbus_connection_unref(bus);
553 dbus_message_unref(m);
556 dbus_message_unref(reply);