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(pam_handle_t *handle,
46 int argc, const char **argv,
53 assert(argc == 0 || argv);
55 for (i = 0; i < (unsigned) argc; i++)
56 if (startswith(argv[i], "class=")) {
60 } else if (streq(argv[i], "debug")) {
64 } else if (startswith(argv[i], "debug=")) {
67 k = parse_boolean(argv[i] + 6);
69 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
74 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
79 static int get_user_data(
81 const char **ret_username,
82 struct passwd **ret_pw) {
84 const char *username = NULL;
85 struct passwd *pw = NULL;
93 r = audit_loginuid_from_pid(0, &uid);
95 pw = pam_modutil_getpwuid(handle, uid);
97 r = pam_get_user(handle, &username, NULL);
98 if (r != PAM_SUCCESS) {
99 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
103 if (isempty(username)) {
104 pam_syslog(handle, LOG_ERR, "User name not valid.");
108 pw = pam_modutil_getpwnam(handle, username);
112 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
113 return PAM_USER_UNKNOWN;
117 *ret_username = username ? username : pw->pw_name;
122 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
123 _cleanup_free_ char *p = NULL;
125 _cleanup_close_ int fd = -1;
126 union sockaddr_union sa = {
127 .un.sun_family = AF_UNIX,
131 _cleanup_free_ char *tty = NULL;
137 /* We deduce the X11 socket from the display name, then use
138 * SO_PEERCRED to determine the X11 server process, ask for
139 * the controlling tty of that and if it's a VC then we know
140 * the seat and the virtual terminal. Sounds ugly, is only
143 r = socket_from_display(display, &p);
146 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
148 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
152 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
156 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
160 r = get_ctty(ucred.pid, NULL, &tty);
164 v = vtnr_from_tty(tty);
172 *vtnr = (uint32_t) v;
177 _public_ PAM_EXTERN int pam_sm_open_session(
178 pam_handle_t *handle,
180 int argc, const char **argv) {
182 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
183 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
185 *username, *id, *object_path, *runtime_path,
187 *tty = NULL, *display = NULL,
188 *remote_user = NULL, *remote_host = NULL,
190 *type = NULL, *class = NULL,
191 *class_pam = NULL, *cvtnr = NULL;
192 _cleanup_bus_unref_ sd_bus *bus = NULL;
193 int session_fd = -1, existing, r;
194 uint32_t uid, pid, vtnr = 0;
195 bool debug = false, remote;
201 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
203 /* Make this a NOP on non-logind systems */
204 if (!logind_running())
207 if (parse_argv(handle,
211 return PAM_SESSION_ERR;
213 r = get_user_data(handle, &username, &pw);
214 if (r != PAM_SUCCESS) {
215 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
219 /* Make sure we don't enter a loop by talking to
220 * systemd-logind when it is actually waiting for the
221 * background to finish start-up. If the service is
222 * "systemd-user" we simply set XDG_RUNTIME_DIR and
225 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
226 if (streq_ptr(service, "systemd-user")) {
227 _cleanup_free_ char *p = NULL, *rt = NULL;
229 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
232 r = parse_env_file(p, NEWLINE,
235 if (r < 0 && r != -ENOENT)
236 return PAM_SESSION_ERR;
239 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
240 if (r != PAM_SUCCESS) {
241 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
249 /* Otherwise, we ask logind to create a session for us */
254 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
255 pam_get_item(handle, PAM_TTY, (const void**) &tty);
256 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
257 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
259 seat = pam_getenv(handle, "XDG_SEAT");
261 seat = getenv("XDG_SEAT");
263 cvtnr = pam_getenv(handle, "XDG_VTNR");
265 cvtnr = getenv("XDG_VTNR");
267 service = strempty(service);
269 display = strempty(display);
270 remote_user = strempty(remote_user);
271 remote_host = strempty(remote_host);
272 seat = strempty(seat);
274 if (strchr(tty, ':')) {
275 /* A tty with a colon is usually an X11 display,
276 * placed there to show up in utmp. We rearrange
277 * things and don't pretend that an X display was a
280 if (isempty(display))
283 } else if (streq(tty, "cron")) {
284 /* cron has been setting PAM_TTY to "cron" for a very
285 * long time and it probably shouldn't stop doing that
286 * for compatibility reasons. */
288 type = "unspecified";
289 } else if (streq(tty, "ssh")) {
290 /* ssh has been setting PAM_TTY to "ssh" for a very
291 * long time and probably shouldn't stop doing that
292 * for compatibility reasons. */
297 /* If this fails vtnr will be 0, that's intended */
299 safe_atou32(cvtnr, &vtnr);
301 if (!isempty(display) && vtnr <= 0) {
303 get_seat_from_display(display, &seat, &vtnr);
304 else if (streq(seat, "seat0"))
305 get_seat_from_display(display, NULL, &vtnr);
309 type = !isempty(display) ? "x11" :
310 !isempty(tty) ? "tty" : "unspecified";
312 class = pam_getenv(handle, "XDG_SESSION_CLASS");
314 class = getenv("XDG_SESSION_CLASS");
318 class = streq(type, "unspecified") ? "background" : "user";
320 remote = !isempty(remote_host) &&
321 !streq(remote_host, "localhost") &&
322 !streq(remote_host, "localhost.localdomain");
324 /* Talk to logind over the message bus */
326 r = sd_bus_open_system(&bus);
328 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
329 return PAM_SESSION_ERR;
333 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
334 "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",
335 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
337 r = sd_bus_call_method(bus,
338 "org.freedesktop.login1",
339 "/org/freedesktop/login1",
340 "org.freedesktop.login1.Manager",
359 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
360 return PAM_SYSTEM_ERR;
363 r = sd_bus_message_read(reply,
373 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
374 return PAM_SESSION_ERR;
378 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
379 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
380 id, object_path, runtime_path, session_fd, seat, vtnr);
382 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
383 if (r != PAM_SUCCESS) {
384 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
388 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
389 if (r != PAM_SUCCESS) {
390 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
394 if (!isempty(seat)) {
395 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
396 if (r != PAM_SUCCESS) {
397 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
403 char buf[DECIMAL_STR_MAX(vtnr)];
404 snprintf(buf, sizeof(buf), "%u", vtnr);
406 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
407 if (r != PAM_SUCCESS) {
408 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
413 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
414 if (r != PAM_SUCCESS) {
415 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
419 if (session_fd >= 0) {
420 session_fd = dup(session_fd);
421 if (session_fd < 0) {
422 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
423 return PAM_SESSION_ERR;
426 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
427 if (r != PAM_SUCCESS) {
428 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
429 close_nointr_nofail(session_fd);
437 _public_ PAM_EXTERN int pam_sm_close_session(
438 pam_handle_t *handle,
440 int argc, const char **argv) {
442 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
443 _cleanup_bus_unref_ sd_bus *bus = NULL;
444 const void *p = NULL, *existing = NULL;
450 /* Only release session if it wasn't pre-existing when we
451 * tried to create it */
452 pam_get_data(handle, "systemd.existing", &existing);
454 id = pam_getenv(handle, "XDG_SESSION_ID");
455 if (id && !existing) {
457 /* Before we go and close the FIFO we need to tell
458 * logind that this is a clean session shutdown, so
459 * that it doesn't just go and slaughter us
460 * immediately after closing the fd */
462 r = sd_bus_open_system(&bus);
464 pam_syslog(handle, LOG_ERR,
465 "Failed to connect to system bus: %s", strerror(-r));
470 r = sd_bus_call_method(bus,
471 "org.freedesktop.login1",
472 "/org/freedesktop/login1",
473 "org.freedesktop.login1.Manager",
480 pam_syslog(handle, LOG_ERR,
481 "Failed to release session: %s", bus_error_message(&error, r));
491 pam_get_data(handle, "systemd.session-fd", &p);
493 close_nointr(PTR_TO_INT(p) - 1);