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;
129 /* We deduce the X11 socket from the display name, then use
130 * SO_PEERCRED to determine the X11 server process, ask for
131 * the controlling tty of that and if it's a VC then we know
132 * the seat and the virtual terminal. Sounds ugly, is only
135 r = socket_from_display(display, &p);
138 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
140 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
144 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
148 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
152 r = get_ctty(ucred.pid, NULL, &tty);
156 v = vtnr_from_tty(tty);
164 *vtnr = (uint32_t) v;
169 _public_ PAM_EXTERN int pam_sm_open_session(
170 pam_handle_t *handle,
172 int argc, const char **argv) {
174 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
175 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
177 *username, *id, *object_path, *runtime_path,
179 *tty = NULL, *display = NULL,
180 *remote_user = NULL, *remote_host = NULL,
182 *type = NULL, *class = NULL,
183 *class_pam = NULL, *cvtnr = NULL;
184 _cleanup_bus_unref_ sd_bus *bus = NULL;
185 int session_fd = -1, existing, r;
186 bool debug = false, remote;
193 /* Make this a NOP on non-logind systems */
194 if (!logind_running())
197 if (parse_argv(handle,
201 return PAM_SESSION_ERR;
204 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
206 r = get_user_data(handle, &username, &pw);
207 if (r != PAM_SUCCESS) {
208 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
212 /* Make sure we don't enter a loop by talking to
213 * systemd-logind when it is actually waiting for the
214 * background to finish start-up. If the service is
215 * "systemd-user" we simply set XDG_RUNTIME_DIR and
218 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
219 if (streq_ptr(service, "systemd-user")) {
220 _cleanup_free_ char *p = NULL, *rt = NULL;
222 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
225 r = parse_env_file(p, NEWLINE,
228 if (r < 0 && r != -ENOENT)
229 return PAM_SESSION_ERR;
232 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
233 if (r != PAM_SUCCESS) {
234 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
242 /* Otherwise, we ask logind to create a session for us */
244 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
245 pam_get_item(handle, PAM_TTY, (const void**) &tty);
246 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
247 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
249 seat = pam_getenv(handle, "XDG_SEAT");
251 seat = getenv("XDG_SEAT");
253 cvtnr = pam_getenv(handle, "XDG_VTNR");
255 cvtnr = getenv("XDG_VTNR");
258 display = strempty(display);
260 if (strchr(tty, ':')) {
261 /* A tty with a colon is usually an X11 display,
262 * placed there to show up in utmp. We rearrange
263 * things and don't pretend that an X display was a
266 if (isempty(display))
269 } else if (streq(tty, "cron")) {
270 /* cron has been setting PAM_TTY to "cron" for a very
271 * long time and it probably shouldn't stop doing that
272 * for compatibility reasons. */
274 type = "unspecified";
275 } else if (streq(tty, "ssh")) {
276 /* ssh has been setting PAM_TTY to "ssh" for a very
277 * long time and probably shouldn't stop doing that
278 * for compatibility reasons. */
283 /* If this fails vtnr will be 0, that's intended */
285 safe_atou32(cvtnr, &vtnr);
287 if (!isempty(display) && !vtnr) {
289 get_seat_from_display(display, &seat, &vtnr);
290 else if (streq(seat, "seat0"))
291 get_seat_from_display(display, NULL, &vtnr);
295 type = !isempty(display) ? "x11" :
296 !isempty(tty) ? "tty" : "unspecified";
298 class = pam_getenv(handle, "XDG_SESSION_CLASS");
300 class = getenv("XDG_SESSION_CLASS");
304 class = streq(type, "unspecified") ? "background" : "user";
306 remote = !isempty(remote_host) &&
307 !streq_ptr(remote_host, "localhost") &&
308 !streq_ptr(remote_host, "localhost.localdomain");
310 /* Talk to logind over the message bus */
312 r = sd_bus_open_system(&bus);
314 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
315 return PAM_SESSION_ERR;
319 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
320 "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",
321 pw->pw_uid, getpid(),
324 strempty(seat), vtnr, tty, strempty(display),
325 yes_no(remote), strempty(remote_user), strempty(remote_host));
327 r = sd_bus_call_method(bus,
328 "org.freedesktop.login1",
329 "/org/freedesktop/login1",
330 "org.freedesktop.login1.Manager",
335 (uint32_t) pw->pw_uid,
345 strempty(remote_user),
346 strempty(remote_host),
349 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
350 return PAM_SYSTEM_ERR;
353 r = sd_bus_message_read(reply,
364 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
365 return PAM_SESSION_ERR;
369 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
370 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
371 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
373 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
374 if (r != PAM_SUCCESS) {
375 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
379 if (original_uid == pw->pw_uid) {
380 /* Don't set $XDG_RUNTIME_DIR if the user we now
381 * authenticated for does not match the original user
382 * of the session. We do this in order not to result
383 * in privileged apps clobbering the runtime directory
386 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
387 if (r != PAM_SUCCESS) {
388 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
393 if (!isempty(seat)) {
394 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
395 if (r != PAM_SUCCESS) {
396 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
402 char buf[DECIMAL_STR_MAX(vtnr)];
403 sprintf(buf, "%u", vtnr);
405 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
406 if (r != PAM_SUCCESS) {
407 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
412 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
413 if (r != PAM_SUCCESS) {
414 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
418 if (session_fd >= 0) {
419 session_fd = dup(session_fd);
420 if (session_fd < 0) {
421 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
422 return PAM_SESSION_ERR;
425 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
426 if (r != PAM_SUCCESS) {
427 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
428 close_nointr_nofail(session_fd);
436 _public_ PAM_EXTERN int pam_sm_close_session(
437 pam_handle_t *handle,
439 int argc, const char **argv) {
441 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
442 _cleanup_bus_unref_ sd_bus *bus = NULL;
443 const void *p = NULL, *existing = NULL;
449 /* Only release session if it wasn't pre-existing when we
450 * tried to create it */
451 pam_get_data(handle, "systemd.existing", &existing);
453 id = pam_getenv(handle, "XDG_SESSION_ID");
454 if (id && !existing) {
456 /* Before we go and close the FIFO we need to tell
457 * logind that this is a clean session shutdown, so
458 * that it doesn't just go and slaughter us
459 * immediately after closing the fd */
461 r = sd_bus_open_system(&bus);
463 pam_syslog(handle, LOG_ERR,
464 "Failed to connect to system bus: %s", strerror(-r));
469 r = sd_bus_call_method(bus,
470 "org.freedesktop.login1",
471 "/org/freedesktop/login1",
472 "org.freedesktop.login1.Manager",
479 pam_syslog(handle, LOG_ERR,
480 "Failed to release session: %s", bus_error_message(&error, r));
490 pam_get_data(handle, "systemd.session-fd", &p);
492 close_nointr(PTR_TO_INT(p) - 1);