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 /* skip export if kdbus is not active */
177 if (access("/dev/kdbus", F_OK) < 0)
180 if (asprintf(&s, KERNEL_USER_BUS_FMT ";" UNIX_USER_BUS_FMT,
181 (unsigned long) uid, runtime) < 0) {
182 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
186 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
187 if (r != PAM_SUCCESS) {
188 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
195 _public_ PAM_EXTERN int pam_sm_open_session(
196 pam_handle_t *handle,
198 int argc, const char **argv) {
200 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
201 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
203 *username, *id, *object_path, *runtime_path,
205 *tty = NULL, *display = NULL,
206 *remote_user = NULL, *remote_host = NULL,
208 *type = NULL, *class = NULL,
209 *class_pam = NULL, *cvtnr = NULL;
210 _cleanup_bus_unref_ sd_bus *bus = NULL;
211 int session_fd = -1, existing, r;
212 bool debug = false, remote;
219 /* Make this a NOP on non-logind systems */
220 if (!logind_running())
223 if (parse_argv(handle,
227 return PAM_SESSION_ERR;
230 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
232 r = get_user_data(handle, &username, &pw);
233 if (r != PAM_SUCCESS) {
234 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
238 /* Make sure we don't enter a loop by talking to
239 * systemd-logind when it is actually waiting for the
240 * background to finish start-up. If the service is
241 * "systemd-user" we simply set XDG_RUNTIME_DIR and
244 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
245 if (streq_ptr(service, "systemd-user")) {
246 _cleanup_free_ char *p = NULL, *rt = NULL;
248 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
251 r = parse_env_file(p, NEWLINE,
254 if (r < 0 && r != -ENOENT)
255 return PAM_SESSION_ERR;
258 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
259 if (r != PAM_SUCCESS) {
260 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
264 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
265 if (r != PAM_SUCCESS)
272 /* Otherwise, we ask logind to create a session for us */
274 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
275 pam_get_item(handle, PAM_TTY, (const void**) &tty);
276 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
277 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
279 seat = pam_getenv(handle, "XDG_SEAT");
281 seat = getenv("XDG_SEAT");
283 cvtnr = pam_getenv(handle, "XDG_VTNR");
285 cvtnr = getenv("XDG_VTNR");
288 display = strempty(display);
290 if (strchr(tty, ':')) {
291 /* A tty with a colon is usually an X11 display,
292 * placed there to show up in utmp. We rearrange
293 * things and don't pretend that an X display was a
296 if (isempty(display))
299 } else if (streq(tty, "cron")) {
300 /* cron has been setting PAM_TTY to "cron" for a very
301 * long time and it probably shouldn't stop doing that
302 * for compatibility reasons. */
304 type = "unspecified";
305 } else if (streq(tty, "ssh")) {
306 /* ssh has been setting PAM_TTY to "ssh" for a very
307 * long time and probably shouldn't stop doing that
308 * for compatibility reasons. */
313 /* If this fails vtnr will be 0, that's intended */
315 safe_atou32(cvtnr, &vtnr);
317 if (!isempty(display) && !vtnr) {
319 get_seat_from_display(display, &seat, &vtnr);
320 else if (streq(seat, "seat0"))
321 get_seat_from_display(display, NULL, &vtnr);
324 if (seat && !streq(seat, "seat0")) {
325 pam_syslog(handle, LOG_DEBUG,
326 "Ignoring vtnr %d for %s which is not seat0", vtnr, seat);
331 type = !isempty(display) ? "x11" :
332 !isempty(tty) ? "tty" : "unspecified";
334 class = pam_getenv(handle, "XDG_SESSION_CLASS");
336 class = getenv("XDG_SESSION_CLASS");
340 class = streq(type, "unspecified") ? "background" : "user";
342 remote = !isempty(remote_host) &&
343 !streq_ptr(remote_host, "localhost") &&
344 !streq_ptr(remote_host, "localhost.localdomain");
346 /* Talk to logind over the message bus */
348 r = sd_bus_open_system(&bus);
350 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
351 return PAM_SESSION_ERR;
355 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
356 "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",
357 pw->pw_uid, getpid(),
360 strempty(seat), vtnr, tty, strempty(display),
361 yes_no(remote), strempty(remote_user), strempty(remote_host));
363 r = sd_bus_call_method(bus,
364 "org.freedesktop.login1",
365 "/org/freedesktop/login1",
366 "org.freedesktop.login1.Manager",
371 (uint32_t) pw->pw_uid,
381 strempty(remote_user),
382 strempty(remote_host),
385 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
386 return PAM_SYSTEM_ERR;
389 r = sd_bus_message_read(reply,
400 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
401 return PAM_SESSION_ERR;
405 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
406 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
407 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
409 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
410 if (r != PAM_SUCCESS) {
411 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
415 if (original_uid == pw->pw_uid) {
416 /* Don't set $XDG_RUNTIME_DIR if the user we now
417 * authenticated for does not match the original user
418 * of the session. We do this in order not to result
419 * in privileged apps clobbering the runtime directory
422 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
423 if (r != PAM_SUCCESS) {
424 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
428 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
429 if (r != PAM_SUCCESS)
433 if (!isempty(seat)) {
434 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
435 if (r != PAM_SUCCESS) {
436 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
442 char buf[DECIMAL_STR_MAX(vtnr)];
443 sprintf(buf, "%u", vtnr);
445 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
446 if (r != PAM_SUCCESS) {
447 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
452 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
453 if (r != PAM_SUCCESS) {
454 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
458 if (session_fd >= 0) {
459 session_fd = dup(session_fd);
460 if (session_fd < 0) {
461 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
462 return PAM_SESSION_ERR;
465 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
466 if (r != PAM_SUCCESS) {
467 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
468 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 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
482 _cleanup_bus_unref_ sd_bus *bus = NULL;
483 const void *p = NULL, *existing = NULL;
489 /* Only release session if it wasn't pre-existing when we
490 * tried to create it */
491 pam_get_data(handle, "systemd.existing", &existing);
493 id = pam_getenv(handle, "XDG_SESSION_ID");
494 if (id && !existing) {
496 /* Before we go and close the FIFO we need to tell
497 * logind that this is a clean session shutdown, so
498 * that it doesn't just go and slaughter us
499 * immediately after closing the fd */
501 r = sd_bus_open_system(&bus);
503 pam_syslog(handle, LOG_ERR,
504 "Failed to connect to system bus: %s", strerror(-r));
509 r = sd_bus_call_method(bus,
510 "org.freedesktop.login1",
511 "/org/freedesktop/login1",
512 "org.freedesktop.login1.Manager",
519 pam_syslog(handle, LOG_ERR,
520 "Failed to release session: %s", bus_error_message(&error, r));
530 pam_get_data(handle, "systemd.session-fd", &p);
532 close_nointr(PTR_TO_INT(p) - 1);