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;
186 bool remote, existing;
190 _cleanup_bus_unref_ sd_bus *bus = NULL;
191 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
192 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
197 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
199 /* Make this a NOP on non-logind systems */
200 if (!logind_running())
203 if (parse_argv(handle,
211 r = get_user_data(handle, &username, &pw);
212 if (r != PAM_SUCCESS)
215 /* Make sure we don't enter a loop by talking to
216 * systemd-logind when it is actually waiting for the
217 * background to finish start-up. If the service is
218 * "systemd-user" we simply set XDG_RUNTIME_DIR and
221 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
222 if (streq_ptr(service, "systemd-user")) {
223 _cleanup_free_ char *p = NULL, *rt = NULL;
225 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
228 r = parse_env_file(p, NEWLINE,
231 if (r < 0 && r != -ENOENT)
232 return PAM_SESSION_ERR;
235 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
236 if (r != PAM_SUCCESS) {
237 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
245 /* Otherwise, we ask logind to create a session for us */
250 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
251 pam_get_item(handle, PAM_TTY, (const void**) &tty);
252 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
253 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
255 seat = pam_getenv(handle, "XDG_SEAT");
257 seat = getenv("XDG_SEAT");
259 cvtnr = pam_getenv(handle, "XDG_VTNR");
261 cvtnr = getenv("XDG_VTNR");
263 service = strempty(service);
265 display = strempty(display);
266 remote_user = strempty(remote_user);
267 remote_host = strempty(remote_host);
268 seat = strempty(seat);
270 if (strchr(tty, ':')) {
271 /* A tty with a colon is usually an X11 display,
272 * placed there to show up in utmp. We rearrange
273 * things and don't pretend that an X display was a
276 if (isempty(display))
279 } else if (streq(tty, "cron")) {
280 /* cron has been setting PAM_TTY to "cron" for a very
281 * long time and it probably shouldn't stop doing that
282 * for compatibility reasons. */
284 type = "unspecified";
285 } else if (streq(tty, "ssh")) {
286 /* ssh has been setting PAM_TTY to "ssh" for a very
287 * long time and probably shouldn't stop doing that
288 * for compatibility reasons. */
293 /* If this fails vtnr will be 0, that's intended */
295 safe_atou32(cvtnr, &vtnr);
297 if (!isempty(display) && vtnr <= 0) {
299 get_seat_from_display(display, &seat, &vtnr);
300 else if (streq(seat, "seat0"))
301 get_seat_from_display(display, NULL, &vtnr);
305 type = !isempty(display) ? "x11" :
306 !isempty(tty) ? "tty" : "unspecified";
308 class = pam_getenv(handle, "XDG_SESSION_CLASS");
310 class = getenv("XDG_SESSION_CLASS");
314 class = streq(type, "unspecified") ? "background" : "user";
316 remote = !isempty(remote_host) &&
317 !streq(remote_host, "localhost") &&
318 !streq(remote_host, "localhost.localdomain");
320 /* Talk to logind over the message bug */
322 r = sd_bus_open_system(&bus);
324 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
325 return PAM_SESSION_ERR;
329 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
330 "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",
331 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
333 r = sd_bus_call_method(bus,
334 "org.freedesktop.login1",
335 "/org/freedesktop/login1",
336 "org.freedesktop.login1.Manager",
355 pam_syslog(handle, LOG_ERR, "Failed to communicate with systemd-logind: %s", strerror(-r));
356 if (error.name || error.message)
357 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
358 error.name ?: "unknown error",
359 error.message ?: "no message");
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));
379 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
380 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
381 id, object_path, runtime_path, session_fd, seat, vtnr);
383 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
384 if (r != PAM_SUCCESS) {
385 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
389 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
390 if (r != PAM_SUCCESS) {
391 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
395 if (!isempty(seat)) {
396 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
397 if (r != PAM_SUCCESS) {
398 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
405 snprintf(buf, sizeof(buf), "%u", vtnr);
408 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
409 if (r != PAM_SUCCESS) {
410 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
415 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
416 if (r != PAM_SUCCESS) {
417 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
421 if (session_fd >= 0) {
422 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
423 if (r != PAM_SUCCESS) {
424 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
433 close_nointr_nofail(session_fd);
438 _public_ PAM_EXTERN int pam_sm_close_session(
439 pam_handle_t *handle,
441 int argc, const char **argv) {
443 const void *p = NULL, *existing = NULL;
447 _cleanup_bus_unref_ sd_bus *bus = NULL;
448 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
449 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
453 /* Only release session if it wasn't pre-existing when we
454 * tried to create it */
455 pam_get_data(handle, "systemd.existing", &existing);
457 id = pam_getenv(handle, "XDG_SESSION_ID");
458 if (id && !existing) {
460 /* Before we go and close the FIFO we need to tell
461 * logind that this is a clean session shutdown, so
462 * that it doesn't just go and slaughter us
463 * immediately after closing the fd */
465 r = sd_bus_open_system(&bus);
467 pam_syslog(handle, LOG_ERR,
468 "Failed to connect to system bus: %s", strerror(-r));
473 r = sd_bus_call_method(bus,
474 "org.freedesktop.login1",
475 "/org/freedesktop/login1",
476 "org.freedesktop.login1.Manager",
483 pam_syslog(handle, LOG_ERR,
484 "Failed to release session: %s", strerror(-r));
485 if (error.name || error.message)
486 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
487 error.name ?: "unknown error",
488 error.message ?: "no message");
498 pam_get_data(handle, "systemd.session-fd", &p);
500 close_nointr(PTR_TO_INT(p) - 1);