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) {
183 const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL;
191 _cleanup_bus_unref_ sd_bus *bus = NULL;
192 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
193 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
198 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
200 /* Make this a NOP on non-logind systems */
201 if (!logind_running())
204 if (parse_argv(handle,
212 r = get_user_data(handle, &username, &pw);
213 if (r != PAM_SUCCESS)
216 /* Make sure we don't enter a loop by talking to
217 * systemd-logind when it is actually waiting for the
218 * background to finish start-up. If the service is
219 * "systemd-user" we simply set XDG_RUNTIME_DIR and
222 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
223 if (streq_ptr(service, "systemd-user")) {
224 _cleanup_free_ char *p = NULL, *rt = NULL;
226 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
229 r = parse_env_file(p, NEWLINE,
232 if (r < 0 && r != -ENOENT)
233 return PAM_SESSION_ERR;
236 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
237 if (r != PAM_SUCCESS) {
238 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
246 /* Otherwise, we ask logind to create a session for us */
251 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
252 pam_get_item(handle, PAM_TTY, (const void**) &tty);
253 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
254 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
256 seat = pam_getenv(handle, "XDG_SEAT");
258 seat = getenv("XDG_SEAT");
260 cvtnr = pam_getenv(handle, "XDG_VTNR");
262 cvtnr = getenv("XDG_VTNR");
264 service = strempty(service);
266 display = strempty(display);
267 remote_user = strempty(remote_user);
268 remote_host = strempty(remote_host);
269 seat = strempty(seat);
271 if (strchr(tty, ':')) {
272 /* A tty with a colon is usually an X11 display,
273 * placed there to show up in utmp. We rearrange
274 * things and don't pretend that an X display was a
277 if (isempty(display))
280 } else if (streq(tty, "cron")) {
281 /* cron has been setting PAM_TTY to "cron" for a very
282 * long time and it probably shouldn't stop doing that
283 * for compatibility reasons. */
285 type = "unspecified";
286 } else if (streq(tty, "ssh")) {
287 /* ssh has been setting PAM_TTY to "ssh" for a very
288 * long time and probably shouldn't stop doing that
289 * for compatibility reasons. */
294 /* If this fails vtnr will be 0, that's intended */
296 safe_atou32(cvtnr, &vtnr);
298 if (!isempty(display) && vtnr <= 0) {
300 get_seat_from_display(display, &seat, &vtnr);
301 else if (streq(seat, "seat0"))
302 get_seat_from_display(display, NULL, &vtnr);
306 type = !isempty(display) ? "x11" :
307 !isempty(tty) ? "tty" : "unspecified";
309 class = pam_getenv(handle, "XDG_SESSION_CLASS");
311 class = getenv("XDG_SESSION_CLASS");
315 class = streq(type, "unspecified") ? "background" : "user";
317 remote = !isempty(remote_host) &&
318 !streq(remote_host, "localhost") &&
319 !streq(remote_host, "localhost.localdomain");
321 /* Talk to logind over the message bug */
323 r = sd_bus_open_system(&bus);
325 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
326 return PAM_SESSION_ERR;
330 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
331 "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",
332 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
334 r = sd_bus_call_method(bus,
335 "org.freedesktop.login1",
336 "/org/freedesktop/login1",
337 "org.freedesktop.login1.Manager",
356 pam_syslog(handle, LOG_ERR, "Failed to communicate with systemd-logind: %s", strerror(-r));
357 if (error.name || error.message)
358 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
359 error.name ?: "unknown error",
360 error.message ?: "no message");
361 return PAM_SYSTEM_ERR;
364 r = sd_bus_message_read(reply,
374 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
380 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
381 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
382 id, object_path, runtime_path, session_fd, seat, vtnr);
384 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
385 if (r != PAM_SUCCESS) {
386 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
390 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
391 if (r != PAM_SUCCESS) {
392 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
396 if (!isempty(seat)) {
397 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
398 if (r != PAM_SUCCESS) {
399 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
406 snprintf(buf, sizeof(buf), "%u", vtnr);
409 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
410 if (r != PAM_SUCCESS) {
411 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
416 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
417 if (r != PAM_SUCCESS) {
418 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
422 if (session_fd >= 0) {
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.");
434 close_nointr_nofail(session_fd);
439 _public_ PAM_EXTERN int pam_sm_close_session(
440 pam_handle_t *handle,
442 int argc, const char **argv) {
444 const void *p = NULL, *existing = NULL;
448 _cleanup_bus_unref_ sd_bus *bus = NULL;
449 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
450 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
454 /* Only release session if it wasn't pre-existing when we
455 * tried to create it */
456 pam_get_data(handle, "systemd.existing", &existing);
458 id = pam_getenv(handle, "XDG_SESSION_ID");
459 if (id && !existing) {
461 /* Before we go and close the FIFO we need to tell
462 * logind that this is a clean session shutdown, so
463 * that it doesn't just go and slaughter us
464 * immediately after closing the fd */
466 r = sd_bus_open_system(&bus);
468 pam_syslog(handle, LOG_ERR,
469 "Failed to connect to system bus: %s", strerror(-r));
474 r = sd_bus_call_method(bus,
475 "org.freedesktop.login1",
476 "/org/freedesktop/login1",
477 "org.freedesktop.login1.Manager",
484 pam_syslog(handle, LOG_ERR,
485 "Failed to release session: %s", strerror(-r));
486 if (error.name || error.message)
487 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
488 error.name ?: "unknown error",
489 error.message ?: "no message");
499 pam_get_data(handle, "systemd.session-fd", &p);
501 close_nointr(PTR_TO_INT(p) - 1);