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>
41 #include "socket-util.h"
43 #include "bus-error.h"
45 static int parse_argv(
47 int argc, const char **argv,
54 assert(argc == 0 || argv);
56 for (i = 0; i < (unsigned) argc; i++)
57 if (startswith(argv[i], "class=")) {
61 } else if (streq(argv[i], "debug")) {
65 } else if (startswith(argv[i], "debug=")) {
68 k = parse_boolean(argv[i] + 6);
70 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
75 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
80 static int get_user_data(
82 const char **ret_username,
83 struct passwd **ret_pw) {
85 const char *username = NULL;
86 struct passwd *pw = NULL;
93 r = pam_get_user(handle, &username, NULL);
94 if (r != PAM_SUCCESS) {
95 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
99 if (isempty(username)) {
100 pam_syslog(handle, LOG_ERR, "User name not valid.");
104 pw = pam_modutil_getpwnam(handle, username);
106 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
107 return PAM_USER_UNKNOWN;
111 *ret_username = username ? username : pw->pw_name;
116 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
117 union sockaddr_union sa = {
118 .un.sun_family = AF_UNIX,
120 _cleanup_free_ char *p = NULL, *tty = NULL;
121 _cleanup_close_ int fd = -1;
128 /* We deduce the X11 socket from the display name, then use
129 * SO_PEERCRED to determine the X11 server process, ask for
130 * the controlling tty of that and if it's a VC then we know
131 * the seat and the virtual terminal. Sounds ugly, is only
134 r = socket_from_display(display, &p);
137 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
139 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
143 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
146 r = getpeercred(fd, &ucred);
150 r = get_ctty(ucred.pid, NULL, &tty);
154 v = vtnr_from_tty(tty);
162 *vtnr = (uint32_t) v;
167 static int export_legacy_dbus_address(
168 pam_handle_t *handle,
170 const char *runtime) {
172 _cleanup_free_ char *s = NULL;
176 if (asprintf(&s, KERNEL_USER_BUS_FMT ";" UNIX_USER_BUS_FMT,
177 (unsigned long) uid, runtime) < 0) {
178 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
182 if (asprintf(&s, UNIX_USER_BUS_FMT, runtime) < 0) {
183 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
188 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
189 if (r != PAM_SUCCESS) {
190 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
197 _public_ PAM_EXTERN int pam_sm_open_session(
198 pam_handle_t *handle,
200 int argc, const char **argv) {
202 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
203 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
205 *username, *id, *object_path, *runtime_path,
207 *tty = NULL, *display = NULL,
208 *remote_user = NULL, *remote_host = NULL,
210 *type = NULL, *class = NULL,
211 *class_pam = NULL, *cvtnr = NULL;
212 _cleanup_bus_unref_ sd_bus *bus = NULL;
213 int session_fd = -1, existing, r;
214 bool debug = false, remote;
221 /* Make this a NOP on non-logind systems */
222 if (!logind_running())
225 if (parse_argv(handle,
229 return PAM_SESSION_ERR;
232 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
234 r = get_user_data(handle, &username, &pw);
235 if (r != PAM_SUCCESS) {
236 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
240 /* Make sure we don't enter a loop by talking to
241 * systemd-logind when it is actually waiting for the
242 * background to finish start-up. If the service is
243 * "systemd-user" we simply set XDG_RUNTIME_DIR and
246 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
247 if (streq_ptr(service, "systemd-user")) {
248 _cleanup_free_ char *p = NULL, *rt = NULL;
250 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
253 r = parse_env_file(p, NEWLINE,
256 if (r < 0 && r != -ENOENT)
257 return PAM_SESSION_ERR;
260 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
261 if (r != PAM_SUCCESS) {
262 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
266 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
267 if (r != PAM_SUCCESS)
274 /* Otherwise, we ask logind to create a session for us */
276 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
277 pam_get_item(handle, PAM_TTY, (const void**) &tty);
278 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
279 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
281 seat = pam_getenv(handle, "XDG_SEAT");
283 seat = getenv("XDG_SEAT");
285 cvtnr = pam_getenv(handle, "XDG_VTNR");
287 cvtnr = getenv("XDG_VTNR");
290 display = strempty(display);
292 if (strchr(tty, ':')) {
293 /* A tty with a colon is usually an X11 display,
294 * placed there to show up in utmp. We rearrange
295 * things and don't pretend that an X display was a
298 if (isempty(display))
301 } else if (streq(tty, "cron")) {
302 /* cron has been setting PAM_TTY to "cron" for a very
303 * long time and it probably shouldn't stop doing that
304 * for compatibility reasons. */
306 type = "unspecified";
307 } else if (streq(tty, "ssh")) {
308 /* ssh has been setting PAM_TTY to "ssh" for a very
309 * long time and probably shouldn't stop doing that
310 * for compatibility reasons. */
315 /* If this fails vtnr will be 0, that's intended */
317 safe_atou32(cvtnr, &vtnr);
319 if (!isempty(display) && !vtnr) {
321 get_seat_from_display(display, &seat, &vtnr);
322 else if (streq(seat, "seat0"))
323 get_seat_from_display(display, NULL, &vtnr);
327 type = !isempty(display) ? "x11" :
328 !isempty(tty) ? "tty" : "unspecified";
330 class = pam_getenv(handle, "XDG_SESSION_CLASS");
332 class = getenv("XDG_SESSION_CLASS");
336 class = streq(type, "unspecified") ? "background" : "user";
338 remote = !isempty(remote_host) &&
339 !streq_ptr(remote_host, "localhost") &&
340 !streq_ptr(remote_host, "localhost.localdomain");
342 /* Talk to logind over the message bus */
344 r = sd_bus_open_system(&bus);
346 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
347 return PAM_SESSION_ERR;
351 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
352 "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",
353 pw->pw_uid, getpid(),
356 strempty(seat), vtnr, tty, strempty(display),
357 yes_no(remote), strempty(remote_user), strempty(remote_host));
359 r = sd_bus_call_method(bus,
360 "org.freedesktop.login1",
361 "/org/freedesktop/login1",
362 "org.freedesktop.login1.Manager",
367 (uint32_t) pw->pw_uid,
377 strempty(remote_user),
378 strempty(remote_host),
381 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
382 return PAM_SYSTEM_ERR;
385 r = sd_bus_message_read(reply,
396 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
397 return PAM_SESSION_ERR;
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 original_uid=%u",
403 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
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 if (original_uid == pw->pw_uid) {
412 /* Don't set $XDG_RUNTIME_DIR if the user we now
413 * authenticated for does not match the original user
414 * of the session. We do this in order not to result
415 * in privileged apps clobbering the runtime directory
418 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
419 if (r != PAM_SUCCESS) {
420 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
424 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
425 if (r != PAM_SUCCESS)
429 if (!isempty(seat)) {
430 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
431 if (r != PAM_SUCCESS) {
432 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
438 char buf[DECIMAL_STR_MAX(vtnr)];
439 sprintf(buf, "%u", vtnr);
441 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
442 if (r != PAM_SUCCESS) {
443 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
448 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
449 if (r != PAM_SUCCESS) {
450 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
454 if (session_fd >= 0) {
455 session_fd = dup(session_fd);
456 if (session_fd < 0) {
457 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
458 return PAM_SESSION_ERR;
461 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
462 if (r != PAM_SUCCESS) {
463 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
464 close_nointr_nofail(session_fd);
472 _public_ PAM_EXTERN int pam_sm_close_session(
473 pam_handle_t *handle,
475 int argc, const char **argv) {
477 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
478 _cleanup_bus_unref_ sd_bus *bus = NULL;
479 const void *p = NULL, *existing = NULL;
485 /* Only release session if it wasn't pre-existing when we
486 * tried to create it */
487 pam_get_data(handle, "systemd.existing", &existing);
489 id = pam_getenv(handle, "XDG_SESSION_ID");
490 if (id && !existing) {
492 /* Before we go and close the FIFO we need to tell
493 * logind that this is a clean session shutdown, so
494 * that it doesn't just go and slaughter us
495 * immediately after closing the fd */
497 r = sd_bus_open_system(&bus);
499 pam_syslog(handle, LOG_ERR,
500 "Failed to connect to system bus: %s", strerror(-r));
505 r = sd_bus_call_method(bus,
506 "org.freedesktop.login1",
507 "/org/freedesktop/login1",
508 "org.freedesktop.login1.Manager",
515 pam_syslog(handle, LOG_ERR,
516 "Failed to release session: %s", bus_error_message(&error, r));
526 pam_get_data(handle, "systemd.session-fd", &p);
528 close_nointr(PTR_TO_INT(p) - 1);