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/>.
28 #include <security/pam_modules.h>
29 #include <security/_pam_macros.h>
30 #include <security/pam_modutil.h>
31 #include <security/pam_ext.h>
32 #include <security/pam_misc.h>
40 #include "socket-util.h"
42 #include "bus-error.h"
44 static int parse_argv(
46 int argc
, const char **argv
,
54 assert(argc
== 0 || argv
);
56 for (i
= 0; i
< (unsigned) argc
; i
++) {
57 if (startswith(argv
[i
], "class=")) {
61 } else if (startswith(argv
[i
], "type=")) {
65 } else if (streq(argv
[i
], "debug")) {
69 } else if (startswith(argv
[i
], "debug=")) {
72 k
= parse_boolean(argv
[i
] + 6);
74 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring.");
79 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
85 static int get_user_data(
87 const char **ret_username
,
88 struct passwd
**ret_pw
) {
90 const char *username
= NULL
;
91 struct passwd
*pw
= NULL
;
98 r
= pam_get_user(handle
, &username
, NULL
);
99 if (r
!= PAM_SUCCESS
) {
100 pam_syslog(handle
, LOG_ERR
, "Failed to get user name.");
104 if (isempty(username
)) {
105 pam_syslog(handle
, LOG_ERR
, "User name not valid.");
109 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
;
121 static int get_seat_from_display(const char *display
, const char **seat
, uint32_t *vtnr
) {
122 union sockaddr_union sa
= {
123 .un
.sun_family
= AF_UNIX
,
125 _cleanup_free_
char *p
= NULL
, *tty
= NULL
;
126 _cleanup_close_
int fd
= -1;
133 /* We deduce the X11 socket from the display name, then use
134 * SO_PEERCRED to determine the X11 server process, ask for
135 * the controlling tty of that and if it's a VC then we know
136 * the seat and the virtual terminal. Sounds ugly, is only
139 r
= socket_from_display(display
, &p
);
142 strncpy(sa
.un
.sun_path
, p
, sizeof(sa
.un
.sun_path
)-1);
144 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
148 if (connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
151 r
= getpeercred(fd
, &ucred
);
155 r
= get_ctty(ucred
.pid
, NULL
, &tty
);
159 v
= vtnr_from_tty(tty
);
167 *vtnr
= (uint32_t) v
;
172 static int export_legacy_dbus_address(
173 pam_handle_t
*handle
,
175 const char *runtime
) {
178 _cleanup_free_
char *s
= NULL
;
181 /* skip export if kdbus is not active */
182 if (access("/sys/fs/kdbus", F_OK
) < 0)
185 if (asprintf(&s
, KERNEL_USER_BUS_ADDRESS_FMT
";" UNIX_USER_BUS_ADDRESS_FMT
, uid
, runtime
) < 0) {
186 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
190 r
= pam_misc_setenv(handle
, "DBUS_SESSION_BUS_ADDRESS", s
, 0);
191 if (r
!= PAM_SUCCESS
) {
192 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
199 _public_ PAM_EXTERN
int pam_sm_open_session(
200 pam_handle_t
*handle
,
202 int argc
, const char **argv
) {
204 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
205 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
207 *username
, *id
, *object_path
, *runtime_path
,
209 *tty
= NULL
, *display
= NULL
,
210 *remote_user
= NULL
, *remote_host
= NULL
,
212 *type
= NULL
, *class = NULL
,
213 *class_pam
= NULL
, *type_pam
= NULL
, *cvtnr
= NULL
, *desktop
= NULL
;
214 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
215 int session_fd
= -1, existing
, r
;
216 bool debug
= false, remote
;
223 /* Make this a NOP on non-logind systems */
224 if (!logind_running())
227 if (parse_argv(handle
,
232 return PAM_SESSION_ERR
;
235 pam_syslog(handle
, LOG_DEBUG
, "pam-systemd initializing");
237 r
= get_user_data(handle
, &username
, &pw
);
238 if (r
!= PAM_SUCCESS
) {
239 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
243 /* Make sure we don't enter a loop by talking to
244 * systemd-logind when it is actually waiting for the
245 * background to finish start-up. If the service is
246 * "systemd-user" we simply set XDG_RUNTIME_DIR and
249 pam_get_item(handle
, PAM_SERVICE
, (const void**) &service
);
250 if (streq_ptr(service
, "systemd-user")) {
251 _cleanup_free_
char *p
= NULL
, *rt
= NULL
;
253 if (asprintf(&p
, "/run/systemd/users/"UID_FMT
, pw
->pw_uid
) < 0)
256 r
= parse_env_file(p
, NEWLINE
,
259 if (r
< 0 && r
!= -ENOENT
)
260 return PAM_SESSION_ERR
;
263 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", rt
, 0);
264 if (r
!= PAM_SUCCESS
) {
265 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
269 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, rt
);
270 if (r
!= PAM_SUCCESS
)
277 /* Otherwise, we ask logind to create a session for us */
279 pam_get_item(handle
, PAM_XDISPLAY
, (const void**) &display
);
280 pam_get_item(handle
, PAM_TTY
, (const void**) &tty
);
281 pam_get_item(handle
, PAM_RUSER
, (const void**) &remote_user
);
282 pam_get_item(handle
, PAM_RHOST
, (const void**) &remote_host
);
284 seat
= pam_getenv(handle
, "XDG_SEAT");
286 seat
= getenv("XDG_SEAT");
288 cvtnr
= pam_getenv(handle
, "XDG_VTNR");
290 cvtnr
= getenv("XDG_VTNR");
292 type
= pam_getenv(handle
, "XDG_SESSION_TYPE");
294 type
= getenv("XDG_SESSION_TYPE");
298 class = pam_getenv(handle
, "XDG_SESSION_CLASS");
300 class = getenv("XDG_SESSION_CLASS");
304 desktop
= pam_getenv(handle
, "XDG_SESSION_DESKTOP");
305 if (isempty(desktop
))
306 desktop
= getenv("XDG_SESSION_DESKTOP");
310 if (strchr(tty
, ':')) {
311 /* A tty with a colon is usually an X11 display,
312 * placed there to show up in utmp. We rearrange
313 * things and don't pretend that an X display was a
316 if (isempty(display
))
319 } else if (streq(tty
, "cron")) {
320 /* cron has been setting PAM_TTY to "cron" for a very
321 * long time and it probably shouldn't stop doing that
322 * for compatibility reasons. */
323 type
= "unspecified";
324 class = "background";
326 } else if (streq(tty
, "ssh")) {
327 /* ssh has been setting PAM_TTY to "ssh" for a very
328 * long time and probably shouldn't stop doing that
329 * for compatibility reasons. */
335 /* If this fails vtnr will be 0, that's intended */
337 safe_atou32(cvtnr
, &vtnr
);
339 if (!isempty(display
) && !vtnr
) {
341 get_seat_from_display(display
, &seat
, &vtnr
);
342 else if (streq(seat
, "seat0"))
343 get_seat_from_display(display
, NULL
, &vtnr
);
346 if (seat
&& !streq(seat
, "seat0") && vtnr
!= 0) {
347 pam_syslog(handle
, LOG_DEBUG
, "Ignoring vtnr %"PRIu32
" for %s which is not seat0", vtnr
, seat
);
352 type
= !isempty(display
) ? "x11" :
353 !isempty(tty
) ? "tty" : "unspecified";
356 class = streq(type
, "unspecified") ? "background" : "user";
358 remote
= !isempty(remote_host
) && !is_localhost(remote_host
);
360 /* Talk to logind over the message bus */
362 r
= sd_bus_open_system(&bus
);
364 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
365 return PAM_SESSION_ERR
;
369 pam_syslog(handle
, LOG_DEBUG
, "Asking logind to create session: "
370 "uid="UID_FMT
" pid="PID_FMT
" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32
" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
371 pw
->pw_uid
, getpid(),
373 type
, class, strempty(desktop
),
374 strempty(seat
), vtnr
, strempty(tty
), strempty(display
),
375 yes_no(remote
), strempty(remote_user
), strempty(remote_host
));
377 r
= sd_bus_call_method(bus
,
378 "org.freedesktop.login1",
379 "/org/freedesktop/login1",
380 "org.freedesktop.login1.Manager",
384 "uusssssussbssa(sv)",
385 (uint32_t) pw
->pw_uid
,
400 pam_syslog(handle
, LOG_ERR
, "Failed to create session: %s", bus_error_message(&error
, r
));
401 return PAM_SYSTEM_ERR
;
404 r
= sd_bus_message_read(reply
,
415 pam_syslog(handle
, LOG_ERR
, "Failed to parse message: %s", strerror(-r
));
416 return PAM_SESSION_ERR
;
420 pam_syslog(handle
, LOG_DEBUG
, "Reply from logind: "
421 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
422 id
, object_path
, runtime_path
, session_fd
, seat
, vtnr
, original_uid
);
424 r
= pam_misc_setenv(handle
, "XDG_SESSION_ID", id
, 0);
425 if (r
!= PAM_SUCCESS
) {
426 pam_syslog(handle
, LOG_ERR
, "Failed to set session id.");
430 if (original_uid
== pw
->pw_uid
) {
431 /* Don't set $XDG_RUNTIME_DIR if the user we now
432 * authenticated for does not match the original user
433 * of the session. We do this in order not to result
434 * in privileged apps clobbering the runtime directory
437 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", runtime_path
, 0);
438 if (r
!= PAM_SUCCESS
) {
439 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
443 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, runtime_path
);
444 if (r
!= PAM_SUCCESS
)
448 if (!isempty(seat
)) {
449 r
= pam_misc_setenv(handle
, "XDG_SEAT", seat
, 0);
450 if (r
!= PAM_SUCCESS
) {
451 pam_syslog(handle
, LOG_ERR
, "Failed to set seat.");
457 char buf
[DECIMAL_STR_MAX(vtnr
)];
458 sprintf(buf
, "%u", vtnr
);
460 r
= pam_misc_setenv(handle
, "XDG_VTNR", buf
, 0);
461 if (r
!= PAM_SUCCESS
) {
462 pam_syslog(handle
, LOG_ERR
, "Failed to set virtual terminal number.");
467 r
= pam_set_data(handle
, "systemd.existing", INT_TO_PTR(!!existing
), NULL
);
468 if (r
!= PAM_SUCCESS
) {
469 pam_syslog(handle
, LOG_ERR
, "Failed to install existing flag.");
473 if (session_fd
>= 0) {
474 session_fd
= fcntl(session_fd
, F_DUPFD_CLOEXEC
, 3);
475 if (session_fd
< 0) {
476 pam_syslog(handle
, LOG_ERR
, "Failed to dup session fd: %m");
477 return PAM_SESSION_ERR
;
480 r
= pam_set_data(handle
, "systemd.session-fd", INT_TO_PTR(session_fd
+1), NULL
);
481 if (r
!= PAM_SUCCESS
) {
482 pam_syslog(handle
, LOG_ERR
, "Failed to install session fd.");
483 safe_close(session_fd
);
491 _public_ PAM_EXTERN
int pam_sm_close_session(
492 pam_handle_t
*handle
,
494 int argc
, const char **argv
) {
496 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
497 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
498 const void *existing
= NULL
;
504 /* Only release session if it wasn't pre-existing when we
505 * tried to create it */
506 pam_get_data(handle
, "systemd.existing", &existing
);
508 id
= pam_getenv(handle
, "XDG_SESSION_ID");
509 if (id
&& !existing
) {
511 /* Before we go and close the FIFO we need to tell
512 * logind that this is a clean session shutdown, so
513 * that it doesn't just go and slaughter us
514 * immediately after closing the fd */
516 r
= sd_bus_open_system(&bus
);
518 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
519 return PAM_SESSION_ERR
;
522 r
= sd_bus_call_method(bus
,
523 "org.freedesktop.login1",
524 "/org/freedesktop/login1",
525 "org.freedesktop.login1.Manager",
532 pam_syslog(handle
, LOG_ERR
, "Failed to release session: %s", bus_error_message(&error
, r
));
533 return PAM_SESSION_ERR
;
537 /* Note that we are knowingly leaking the FIFO fd here. This
538 * way, logind can watch us die. If we closed it here it would
539 * not have any clue when that is completed. Given that one
540 * cannot really have multiple PAM sessions open from the same
541 * process this means we will leak one FD at max. */