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 _public_ PAM_EXTERN int pam_sm_open_session(
168 pam_handle_t *handle,
170 int argc, const char **argv) {
172 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
173 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
175 *username, *id, *object_path, *runtime_path,
177 *tty = NULL, *display = NULL,
178 *remote_user = NULL, *remote_host = NULL,
180 *type = NULL, *class = NULL,
181 *class_pam = NULL, *cvtnr = NULL;
182 _cleanup_bus_unref_ sd_bus *bus = NULL;
183 int session_fd = -1, existing, r;
184 bool debug = false, remote;
191 /* Make this a NOP on non-logind systems */
192 if (!logind_running())
195 if (parse_argv(handle,
199 return PAM_SESSION_ERR;
202 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
204 r = get_user_data(handle, &username, &pw);
205 if (r != PAM_SUCCESS) {
206 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
210 /* Make sure we don't enter a loop by talking to
211 * systemd-logind when it is actually waiting for the
212 * background to finish start-up. If the service is
213 * "systemd-user" we simply set XDG_RUNTIME_DIR and
216 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
217 if (streq_ptr(service, "systemd-user")) {
218 _cleanup_free_ char *p = NULL, *rt = NULL;
220 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
223 r = parse_env_file(p, NEWLINE,
226 if (r < 0 && r != -ENOENT)
227 return PAM_SESSION_ERR;
230 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
231 if (r != PAM_SUCCESS) {
232 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
240 /* Otherwise, we ask logind to create a session for us */
242 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
243 pam_get_item(handle, PAM_TTY, (const void**) &tty);
244 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
245 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
247 seat = pam_getenv(handle, "XDG_SEAT");
249 seat = getenv("XDG_SEAT");
251 cvtnr = pam_getenv(handle, "XDG_VTNR");
253 cvtnr = getenv("XDG_VTNR");
256 display = strempty(display);
258 if (strchr(tty, ':')) {
259 /* A tty with a colon is usually an X11 display,
260 * placed there to show up in utmp. We rearrange
261 * things and don't pretend that an X display was a
264 if (isempty(display))
267 } else if (streq(tty, "cron")) {
268 /* cron has been setting PAM_TTY to "cron" for a very
269 * long time and it probably shouldn't stop doing that
270 * for compatibility reasons. */
272 type = "unspecified";
273 } else if (streq(tty, "ssh")) {
274 /* ssh has been setting PAM_TTY to "ssh" for a very
275 * long time and probably shouldn't stop doing that
276 * for compatibility reasons. */
281 /* If this fails vtnr will be 0, that's intended */
283 safe_atou32(cvtnr, &vtnr);
285 if (!isempty(display) && !vtnr) {
287 get_seat_from_display(display, &seat, &vtnr);
288 else if (streq(seat, "seat0"))
289 get_seat_from_display(display, NULL, &vtnr);
293 type = !isempty(display) ? "x11" :
294 !isempty(tty) ? "tty" : "unspecified";
296 class = pam_getenv(handle, "XDG_SESSION_CLASS");
298 class = getenv("XDG_SESSION_CLASS");
302 class = streq(type, "unspecified") ? "background" : "user";
304 remote = !isempty(remote_host) &&
305 !streq_ptr(remote_host, "localhost") &&
306 !streq_ptr(remote_host, "localhost.localdomain");
308 /* Talk to logind over the message bus */
310 r = sd_bus_open_system(&bus);
312 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
313 return PAM_SESSION_ERR;
317 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
318 "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",
319 pw->pw_uid, getpid(),
322 strempty(seat), vtnr, tty, strempty(display),
323 yes_no(remote), strempty(remote_user), strempty(remote_host));
325 r = sd_bus_call_method(bus,
326 "org.freedesktop.login1",
327 "/org/freedesktop/login1",
328 "org.freedesktop.login1.Manager",
333 (uint32_t) pw->pw_uid,
343 strempty(remote_user),
344 strempty(remote_host),
347 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
348 return PAM_SYSTEM_ERR;
351 r = sd_bus_message_read(reply,
362 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
363 return PAM_SESSION_ERR;
367 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
368 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
369 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
371 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
372 if (r != PAM_SUCCESS) {
373 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
377 if (original_uid == pw->pw_uid) {
378 /* Don't set $XDG_RUNTIME_DIR if the user we now
379 * authenticated for does not match the original user
380 * of the session. We do this in order not to result
381 * in privileged apps clobbering the runtime directory
384 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
385 if (r != PAM_SUCCESS) {
386 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
391 if (!isempty(seat)) {
392 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
393 if (r != PAM_SUCCESS) {
394 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
400 char buf[DECIMAL_STR_MAX(vtnr)];
401 sprintf(buf, "%u", vtnr);
403 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
404 if (r != PAM_SUCCESS) {
405 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
410 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
411 if (r != PAM_SUCCESS) {
412 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
416 if (session_fd >= 0) {
417 session_fd = dup(session_fd);
418 if (session_fd < 0) {
419 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
420 return PAM_SESSION_ERR;
423 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
424 if (r != PAM_SUCCESS) {
425 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
426 close_nointr_nofail(session_fd);
434 _public_ PAM_EXTERN int pam_sm_close_session(
435 pam_handle_t *handle,
437 int argc, const char **argv) {
439 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
440 _cleanup_bus_unref_ sd_bus *bus = NULL;
441 const void *p = NULL, *existing = NULL;
447 /* Only release session if it wasn't pre-existing when we
448 * tried to create it */
449 pam_get_data(handle, "systemd.existing", &existing);
451 id = pam_getenv(handle, "XDG_SESSION_ID");
452 if (id && !existing) {
454 /* Before we go and close the FIFO we need to tell
455 * logind that this is a clean session shutdown, so
456 * that it doesn't just go and slaughter us
457 * immediately after closing the fd */
459 r = sd_bus_open_system(&bus);
461 pam_syslog(handle, LOG_ERR,
462 "Failed to connect to system bus: %s", strerror(-r));
467 r = sd_bus_call_method(bus,
468 "org.freedesktop.login1",
469 "/org/freedesktop/login1",
470 "org.freedesktop.login1.Manager",
477 pam_syslog(handle, LOG_ERR,
478 "Failed to release session: %s", bus_error_message(&error, r));
488 pam_get_data(handle, "systemd.session-fd", &p);
490 close_nointr(PTR_TO_INT(p) - 1);