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 bus */
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);