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"
44 static int parse_argv(pam_handle_t *handle,
45 int argc, const char **argv,
52 assert(argc == 0 || argv);
54 for (i = 0; i < (unsigned) argc; i++)
55 if (startswith(argv[i], "class=")) {
59 } else if (streq(argv[i], "debug")) {
63 } else if (startswith(argv[i], "debug=")) {
66 k = parse_boolean(argv[i] + 6);
68 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
73 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
78 static int get_user_data(
80 const char **ret_username,
81 struct passwd **ret_pw) {
83 const char *username = NULL;
84 struct passwd *pw = NULL;
92 r = audit_loginuid_from_pid(0, &uid);
94 pw = pam_modutil_getpwuid(handle, uid);
96 r = pam_get_user(handle, &username, NULL);
97 if (r != PAM_SUCCESS) {
98 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
102 if (isempty(username)) {
103 pam_syslog(handle, LOG_ERR, "User name not valid.");
107 pw = pam_modutil_getpwnam(handle, username);
111 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
112 return PAM_USER_UNKNOWN;
116 *ret_username = username ? username : pw->pw_name;
121 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
122 _cleanup_free_ char *p = NULL;
124 _cleanup_close_ int fd = -1;
125 union sockaddr_union sa = {
126 .un.sun_family = AF_UNIX,
130 _cleanup_free_ char *tty = NULL;
136 /* We deduce the X11 socket from the display name, then use
137 * SO_PEERCRED to determine the X11 server process, ask for
138 * the controlling tty of that and if it's a VC then we know
139 * the seat and the virtual terminal. Sounds ugly, is only
142 r = socket_from_display(display, &p);
145 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
147 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
151 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
155 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
159 r = get_ctty(ucred.pid, NULL, &tty);
163 v = vtnr_from_tty(tty);
171 *vtnr = (uint32_t) v;
176 _public_ PAM_EXTERN int pam_sm_open_session(
177 pam_handle_t *handle,
179 int argc, const char **argv) {
181 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
182 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
184 *username, *id, *object_path, *runtime_path,
186 *tty = NULL, *display = NULL,
187 *remote_user = NULL, *remote_host = NULL,
189 *type = NULL, *class = NULL,
190 *class_pam = NULL, *cvtnr = NULL;
191 _cleanup_bus_unref_ sd_bus *bus = NULL;
192 int session_fd = -1, existing, r;
193 uint32_t uid, pid, vtnr = 0;
194 bool debug = false, remote;
200 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
202 /* Make this a NOP on non-logind systems */
203 if (!logind_running())
206 if (parse_argv(handle,
210 return PAM_SESSION_ERR;
212 r = get_user_data(handle, &username, &pw);
213 if (r != PAM_SUCCESS) {
214 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
218 /* Make sure we don't enter a loop by talking to
219 * systemd-logind when it is actually waiting for the
220 * background to finish start-up. If the service is
221 * "systemd-user" we simply set XDG_RUNTIME_DIR and
224 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
225 if (streq_ptr(service, "systemd-user")) {
226 _cleanup_free_ char *p = NULL, *rt = NULL;
228 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
231 r = parse_env_file(p, NEWLINE,
234 if (r < 0 && r != -ENOENT)
235 return PAM_SESSION_ERR;
238 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
239 if (r != PAM_SUCCESS) {
240 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
248 /* Otherwise, we ask logind to create a session for us */
253 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
254 pam_get_item(handle, PAM_TTY, (const void**) &tty);
255 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
256 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
258 seat = pam_getenv(handle, "XDG_SEAT");
260 seat = getenv("XDG_SEAT");
262 cvtnr = pam_getenv(handle, "XDG_VTNR");
264 cvtnr = getenv("XDG_VTNR");
266 service = strempty(service);
268 display = strempty(display);
269 remote_user = strempty(remote_user);
270 remote_host = strempty(remote_host);
271 seat = strempty(seat);
273 if (strchr(tty, ':')) {
274 /* A tty with a colon is usually an X11 display,
275 * placed there to show up in utmp. We rearrange
276 * things and don't pretend that an X display was a
279 if (isempty(display))
282 } else if (streq(tty, "cron")) {
283 /* cron has been setting PAM_TTY to "cron" for a very
284 * long time and it probably shouldn't stop doing that
285 * for compatibility reasons. */
287 type = "unspecified";
288 } else if (streq(tty, "ssh")) {
289 /* ssh has been setting PAM_TTY to "ssh" for a very
290 * long time and probably shouldn't stop doing that
291 * for compatibility reasons. */
296 /* If this fails vtnr will be 0, that's intended */
298 safe_atou32(cvtnr, &vtnr);
300 if (!isempty(display) && vtnr <= 0) {
302 get_seat_from_display(display, &seat, &vtnr);
303 else if (streq(seat, "seat0"))
304 get_seat_from_display(display, NULL, &vtnr);
308 type = !isempty(display) ? "x11" :
309 !isempty(tty) ? "tty" : "unspecified";
311 class = pam_getenv(handle, "XDG_SESSION_CLASS");
313 class = getenv("XDG_SESSION_CLASS");
317 class = streq(type, "unspecified") ? "background" : "user";
319 remote = !isempty(remote_host) &&
320 !streq(remote_host, "localhost") &&
321 !streq(remote_host, "localhost.localdomain");
323 /* Talk to logind over the message bus */
325 r = sd_bus_open_system(&bus);
327 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
328 return PAM_SESSION_ERR;
332 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
333 "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",
334 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
336 r = sd_bus_call_method(bus,
337 "org.freedesktop.login1",
338 "/org/freedesktop/login1",
339 "org.freedesktop.login1.Manager",
358 pam_syslog(handle, LOG_ERR, "Failed to communicate with systemd-logind: %s", strerror(-r));
359 if (error.name || error.message)
360 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
361 error.name ?: "unknown error",
362 error.message ?: "no message");
363 return PAM_SYSTEM_ERR;
366 r = sd_bus_message_read(reply,
376 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
377 return PAM_SESSION_ERR;
381 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
382 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
383 id, object_path, runtime_path, session_fd, seat, vtnr);
385 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
386 if (r != PAM_SUCCESS) {
387 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
391 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
392 if (r != PAM_SUCCESS) {
393 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
397 if (!isempty(seat)) {
398 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
399 if (r != PAM_SUCCESS) {
400 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
407 snprintf(buf, sizeof(buf), "%u", vtnr);
410 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
411 if (r != PAM_SUCCESS) {
412 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
417 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
418 if (r != PAM_SUCCESS) {
419 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
423 if (session_fd >= 0) {
424 session_fd = dup(session_fd);
425 if (session_fd < 0) {
426 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
427 return PAM_SESSION_ERR;
430 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
431 if (r != PAM_SUCCESS) {
432 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
433 close_nointr_nofail(session_fd);
441 _public_ PAM_EXTERN int pam_sm_close_session(
442 pam_handle_t *handle,
444 int argc, const char **argv) {
446 const void *p = NULL, *existing = NULL;
450 _cleanup_bus_unref_ sd_bus *bus = NULL;
451 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
452 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
456 /* Only release session if it wasn't pre-existing when we
457 * tried to create it */
458 pam_get_data(handle, "systemd.existing", &existing);
460 id = pam_getenv(handle, "XDG_SESSION_ID");
461 if (id && !existing) {
463 /* Before we go and close the FIFO we need to tell
464 * logind that this is a clean session shutdown, so
465 * that it doesn't just go and slaughter us
466 * immediately after closing the fd */
468 r = sd_bus_open_system(&bus);
470 pam_syslog(handle, LOG_ERR,
471 "Failed to connect to system bus: %s", strerror(-r));
476 r = sd_bus_call_method(bus,
477 "org.freedesktop.login1",
478 "/org/freedesktop/login1",
479 "org.freedesktop.login1.Manager",
486 pam_syslog(handle, LOG_ERR,
487 "Failed to release session: %s", strerror(-r));
488 if (error.name || error.message)
489 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
490 error.name ?: "unknown error",
491 error.message ?: "no message");
501 pam_get_data(handle, "systemd.session-fd", &p);
503 close_nointr(PTR_TO_INT(p) - 1);