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>
39 #include "dbus-common.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 DBusMessageIter iter, sub;
188 DBusConnection *bus = NULL;
189 DBusMessage *m = NULL, *reply = NULL;
190 dbus_bool_t remote, existing;
196 dbus_error_init(&error);
199 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
201 /* Make this a NOP on non-logind systems */
202 if (!logind_running())
205 if (parse_argv(handle,
213 r = get_user_data(handle, &username, &pw);
214 if (r != PAM_SUCCESS)
217 /* Make sure we don't enter a loop by talking to
218 * systemd-logind when it is actually waiting for the
219 * background to finish start-up. If the service is
220 * "systemd-user" we simply set XDG_RUNTIME_DIR and
223 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
224 if (streq_ptr(service, "systemd-user")) {
225 _cleanup_free_ char *p = NULL, *rt = NULL;
227 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) {
241 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
242 if (r != PAM_SUCCESS) {
243 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
252 dbus_connection_set_change_sigpipe(FALSE);
254 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
256 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
261 m = dbus_message_new_method_call(
262 "org.freedesktop.login1",
263 "/org/freedesktop/login1",
264 "org.freedesktop.login1.Manager",
267 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
275 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
276 pam_get_item(handle, PAM_TTY, (const void**) &tty);
277 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
278 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
280 seat = pam_getenv(handle, "XDG_SEAT");
282 seat = getenv("XDG_SEAT");
284 cvtnr = pam_getenv(handle, "XDG_VTNR");
286 cvtnr = getenv("XDG_VTNR");
288 service = strempty(service);
290 display = strempty(display);
291 remote_user = strempty(remote_user);
292 remote_host = strempty(remote_host);
293 seat = strempty(seat);
295 if (strchr(tty, ':')) {
296 /* A tty with a colon is usually an X11 display,
297 * placed there to show up in utmp. We rearrange
298 * things and don't pretend that an X display was a
301 if (isempty(display))
304 } else if (streq(tty, "cron")) {
305 /* cron has been setting PAM_TTY to "cron" for a very
306 * long time and it probably shouldn't stop doing that
307 * for compatibility reasons. */
309 type = "unspecified";
310 } else if (streq(tty, "ssh")) {
311 /* ssh has been setting PAM_TTY to "ssh" for a very
312 * long time and probably shouldn't stop doing that
313 * for compatibility reasons. */
318 /* If this fails vtnr will be 0, that's intended */
320 safe_atou32(cvtnr, &vtnr);
322 if (!isempty(display) && vtnr <= 0) {
324 get_seat_from_display(display, &seat, &vtnr);
325 else if (streq(seat, "seat0"))
326 get_seat_from_display(display, NULL, &vtnr);
330 type = !isempty(display) ? "x11" :
331 !isempty(tty) ? "tty" : "unspecified";
333 class = pam_getenv(handle, "XDG_SESSION_CLASS");
335 class = getenv("XDG_SESSION_CLASS");
339 class = streq(type, "unspecified") ? "background" : "user";
341 remote = !isempty(remote_host) &&
342 !streq(remote_host, "localhost") &&
343 !streq(remote_host, "localhost.localdomain");
345 if (!dbus_message_append_args(m,
346 DBUS_TYPE_UINT32, &uid,
347 DBUS_TYPE_UINT32, &pid,
348 DBUS_TYPE_STRING, &service,
349 DBUS_TYPE_STRING, &type,
350 DBUS_TYPE_STRING, &class,
351 DBUS_TYPE_STRING, &seat,
352 DBUS_TYPE_UINT32, &vtnr,
353 DBUS_TYPE_STRING, &tty,
354 DBUS_TYPE_STRING, &display,
355 DBUS_TYPE_BOOLEAN, &remote,
356 DBUS_TYPE_STRING, &remote_user,
357 DBUS_TYPE_STRING, &remote_host,
358 DBUS_TYPE_INVALID)) {
359 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
364 dbus_message_iter_init_append(m, &iter);
366 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub) ||
367 !dbus_message_iter_close_container(&iter, &sub)) {
368 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
374 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
375 "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",
376 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
378 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
380 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
385 if (!dbus_message_get_args(reply, &error,
386 DBUS_TYPE_STRING, &id,
387 DBUS_TYPE_OBJECT_PATH, &object_path,
388 DBUS_TYPE_STRING, &runtime_path,
389 DBUS_TYPE_UNIX_FD, &session_fd,
390 DBUS_TYPE_STRING, &seat,
391 DBUS_TYPE_UINT32, &vtnr,
392 DBUS_TYPE_BOOLEAN, &existing,
393 DBUS_TYPE_INVALID)) {
394 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
400 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
401 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
402 id, object_path, runtime_path, session_fd, seat, vtnr);
404 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
405 if (r != PAM_SUCCESS) {
406 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
410 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
411 if (r != PAM_SUCCESS) {
412 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
416 if (!isempty(seat)) {
417 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
418 if (r != PAM_SUCCESS) {
419 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
426 snprintf(buf, sizeof(buf), "%u", vtnr);
429 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
430 if (r != PAM_SUCCESS) {
431 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
436 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
437 if (r != PAM_SUCCESS) {
438 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
442 if (session_fd >= 0) {
443 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
444 if (r != PAM_SUCCESS) {
445 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
455 dbus_error_free(&error);
458 dbus_connection_close(bus);
459 dbus_connection_unref(bus);
463 dbus_message_unref(m);
466 dbus_message_unref(reply);
469 close_nointr_nofail(session_fd);
474 _public_ PAM_EXTERN int pam_sm_close_session(
475 pam_handle_t *handle,
477 int argc, const char **argv) {
479 const void *p = NULL, *existing = NULL;
481 DBusConnection *bus = NULL;
482 DBusMessage *m = NULL, *reply = NULL;
488 dbus_error_init(&error);
490 /* Only release session if it wasn't pre-existing when we
491 * tried to create it */
492 pam_get_data(handle, "systemd.existing", &existing);
494 id = pam_getenv(handle, "XDG_SESSION_ID");
495 if (id && !existing) {
497 /* Before we go and close the FIFO we need to tell
498 * logind that this is a clean session shutdown, so
499 * that it doesn't just go and slaughter us
500 * immediately after closing the fd */
502 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
504 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
509 m = dbus_message_new_method_call(
510 "org.freedesktop.login1",
511 "/org/freedesktop/login1",
512 "org.freedesktop.login1.Manager",
515 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
520 if (!dbus_message_append_args(m,
521 DBUS_TYPE_STRING, &id,
522 DBUS_TYPE_INVALID)) {
523 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
528 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
530 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
539 pam_get_data(handle, "systemd.session-fd", &p);
541 close_nointr(PTR_TO_INT(p) - 1);
543 dbus_error_free(&error);
546 dbus_connection_close(bus);
547 dbus_connection_unref(bus);
551 dbus_message_unref(m);
554 dbus_message_unref(reply);