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) {
173 _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 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
183 if (r != PAM_SUCCESS) {
184 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
191 _public_ PAM_EXTERN int pam_sm_open_session(
192 pam_handle_t *handle,
194 int argc, const char **argv) {
196 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
197 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
199 *username, *id, *object_path, *runtime_path,
201 *tty = NULL, *display = NULL,
202 *remote_user = NULL, *remote_host = NULL,
204 *type = NULL, *class = NULL,
205 *class_pam = NULL, *cvtnr = NULL;
206 _cleanup_bus_unref_ sd_bus *bus = NULL;
207 int session_fd = -1, existing, r;
208 bool debug = false, remote;
215 /* Make this a NOP on non-logind systems */
216 if (!logind_running())
219 if (parse_argv(handle,
223 return PAM_SESSION_ERR;
226 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
228 r = get_user_data(handle, &username, &pw);
229 if (r != PAM_SUCCESS) {
230 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
234 /* Make sure we don't enter a loop by talking to
235 * systemd-logind when it is actually waiting for the
236 * background to finish start-up. If the service is
237 * "systemd-user" we simply set XDG_RUNTIME_DIR and
240 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
241 if (streq_ptr(service, "systemd-user")) {
242 _cleanup_free_ char *p = NULL, *rt = NULL;
244 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
247 r = parse_env_file(p, NEWLINE,
250 if (r < 0 && r != -ENOENT)
251 return PAM_SESSION_ERR;
254 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
255 if (r != PAM_SUCCESS) {
256 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
260 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
261 if (r != PAM_SUCCESS)
268 /* Otherwise, we ask logind to create a session for us */
270 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
271 pam_get_item(handle, PAM_TTY, (const void**) &tty);
272 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
273 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
275 seat = pam_getenv(handle, "XDG_SEAT");
277 seat = getenv("XDG_SEAT");
279 cvtnr = pam_getenv(handle, "XDG_VTNR");
281 cvtnr = getenv("XDG_VTNR");
284 display = strempty(display);
286 if (strchr(tty, ':')) {
287 /* A tty with a colon is usually an X11 display,
288 * placed there to show up in utmp. We rearrange
289 * things and don't pretend that an X display was a
292 if (isempty(display))
295 } else if (streq(tty, "cron")) {
296 /* cron has been setting PAM_TTY to "cron" for a very
297 * long time and it probably shouldn't stop doing that
298 * for compatibility reasons. */
300 type = "unspecified";
301 } else if (streq(tty, "ssh")) {
302 /* ssh has been setting PAM_TTY to "ssh" for a very
303 * long time and probably shouldn't stop doing that
304 * for compatibility reasons. */
309 /* If this fails vtnr will be 0, that's intended */
311 safe_atou32(cvtnr, &vtnr);
313 if (!isempty(display) && !vtnr) {
315 get_seat_from_display(display, &seat, &vtnr);
316 else if (streq(seat, "seat0"))
317 get_seat_from_display(display, NULL, &vtnr);
321 type = !isempty(display) ? "x11" :
322 !isempty(tty) ? "tty" : "unspecified";
324 class = pam_getenv(handle, "XDG_SESSION_CLASS");
326 class = getenv("XDG_SESSION_CLASS");
330 class = streq(type, "unspecified") ? "background" : "user";
332 remote = !isempty(remote_host) &&
333 !streq_ptr(remote_host, "localhost") &&
334 !streq_ptr(remote_host, "localhost.localdomain");
336 /* Talk to logind over the message bus */
338 r = sd_bus_open_system(&bus);
340 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
341 return PAM_SESSION_ERR;
345 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
346 "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",
347 pw->pw_uid, getpid(),
350 strempty(seat), vtnr, tty, strempty(display),
351 yes_no(remote), strempty(remote_user), strempty(remote_host));
353 r = sd_bus_call_method(bus,
354 "org.freedesktop.login1",
355 "/org/freedesktop/login1",
356 "org.freedesktop.login1.Manager",
361 (uint32_t) pw->pw_uid,
371 strempty(remote_user),
372 strempty(remote_host),
375 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
376 return PAM_SYSTEM_ERR;
379 r = sd_bus_message_read(reply,
390 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
391 return PAM_SESSION_ERR;
395 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
396 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
397 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
399 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
400 if (r != PAM_SUCCESS) {
401 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
405 if (original_uid == pw->pw_uid) {
406 /* Don't set $XDG_RUNTIME_DIR if the user we now
407 * authenticated for does not match the original user
408 * of the session. We do this in order not to result
409 * in privileged apps clobbering the runtime directory
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 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
419 if (r != PAM_SUCCESS)
423 if (!isempty(seat)) {
424 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
425 if (r != PAM_SUCCESS) {
426 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
432 char buf[DECIMAL_STR_MAX(vtnr)];
433 sprintf(buf, "%u", vtnr);
435 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
436 if (r != PAM_SUCCESS) {
437 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
442 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
443 if (r != PAM_SUCCESS) {
444 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
448 if (session_fd >= 0) {
449 session_fd = dup(session_fd);
450 if (session_fd < 0) {
451 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
452 return PAM_SESSION_ERR;
455 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
456 if (r != PAM_SUCCESS) {
457 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
458 close_nointr_nofail(session_fd);
466 _public_ PAM_EXTERN int pam_sm_close_session(
467 pam_handle_t *handle,
469 int argc, const char **argv) {
471 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
472 _cleanup_bus_unref_ sd_bus *bus = NULL;
473 const void *p = NULL, *existing = NULL;
479 /* Only release session if it wasn't pre-existing when we
480 * tried to create it */
481 pam_get_data(handle, "systemd.existing", &existing);
483 id = pam_getenv(handle, "XDG_SESSION_ID");
484 if (id && !existing) {
486 /* Before we go and close the FIFO we need to tell
487 * logind that this is a clean session shutdown, so
488 * that it doesn't just go and slaughter us
489 * immediately after closing the fd */
491 r = sd_bus_open_system(&bus);
493 pam_syslog(handle, LOG_ERR,
494 "Failed to connect to system bus: %s", strerror(-r));
499 r = sd_bus_call_method(bus,
500 "org.freedesktop.login1",
501 "/org/freedesktop/login1",
502 "org.freedesktop.login1.Manager",
509 pam_syslog(handle, LOG_ERR,
510 "Failed to release session: %s", bus_error_message(&error, r));
520 pam_get_data(handle, "systemd.session-fd", &p);
522 close_nointr(PTR_TO_INT(p) - 1);