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/>.
26 #include <security/_pam_macros.h>
27 #include <security/pam_ext.h>
28 #include <security/pam_misc.h>
29 #include <security/pam_modules.h>
30 #include <security/pam_modutil.h>
33 #include "audit-util.h"
34 #include "bus-common-errors.h"
35 #include "bus-error.h"
40 #include "formats-util.h"
41 #include "hostname-util.h"
42 #include "login-util.h"
44 #include "parse-util.h"
45 #include "socket-util.h"
47 #include "terminal-util.h"
50 static int parse_argv(
52 int argc
, const char **argv
,
60 assert(argc
== 0 || argv
);
62 for (i
= 0; i
< (unsigned) argc
; i
++) {
63 if (startswith(argv
[i
], "class=")) {
67 } else if (startswith(argv
[i
], "type=")) {
71 } else if (streq(argv
[i
], "debug")) {
75 } else if (startswith(argv
[i
], "debug=")) {
78 k
= parse_boolean(argv
[i
] + 6);
80 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring.");
85 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
91 static int get_user_data(
93 const char **ret_username
,
94 struct passwd
**ret_pw
) {
96 const char *username
= NULL
;
97 struct passwd
*pw
= NULL
;
101 assert(ret_username
);
104 r
= pam_get_user(handle
, &username
, NULL
);
105 if (r
!= PAM_SUCCESS
) {
106 pam_syslog(handle
, LOG_ERR
, "Failed to get user name.");
110 if (isempty(username
)) {
111 pam_syslog(handle
, LOG_ERR
, "User name not valid.");
115 pw
= pam_modutil_getpwnam(handle
, username
);
117 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
118 return PAM_USER_UNKNOWN
;
122 *ret_username
= username
;
127 static int get_seat_from_display(const char *display
, const char **seat
, uint32_t *vtnr
) {
128 union sockaddr_union sa
= {
129 .un
.sun_family
= AF_UNIX
,
131 _cleanup_free_
char *p
= NULL
, *tty
= NULL
;
132 _cleanup_close_
int fd
= -1;
139 /* We deduce the X11 socket from the display name, then use
140 * SO_PEERCRED to determine the X11 server process, ask for
141 * the controlling tty of that and if it's a VC then we know
142 * the seat and the virtual terminal. Sounds ugly, is only
145 r
= socket_from_display(display
, &p
);
148 strncpy(sa
.un
.sun_path
, p
, sizeof(sa
.un
.sun_path
)-1);
150 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
154 if (connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
157 r
= getpeercred(fd
, &ucred
);
161 r
= get_ctty(ucred
.pid
, NULL
, &tty
);
165 v
= vtnr_from_tty(tty
);
173 *vtnr
= (uint32_t) v
;
178 static int export_legacy_dbus_address(
179 pam_handle_t
*handle
,
181 const char *runtime
) {
183 _cleanup_free_
char *s
= NULL
;
186 if (is_kdbus_available()) {
187 if (asprintf(&s
, KERNEL_USER_BUS_ADDRESS_FMT
";" UNIX_USER_BUS_ADDRESS_FMT
, uid
, runtime
) < 0)
190 /* FIXME: We *really* should move the access() check into the
191 * daemons that spawn dbus-daemon, instead of forcing
192 * DBUS_SESSION_BUS_ADDRESS= here. */
194 s
= strjoin(runtime
, "/bus", NULL
);
198 if (access(s
, F_OK
) < 0)
202 if (asprintf(&s
, UNIX_USER_BUS_ADDRESS_FMT
, runtime
) < 0)
206 r
= pam_misc_setenv(handle
, "DBUS_SESSION_BUS_ADDRESS", s
, 0);
207 if (r
!= PAM_SUCCESS
)
213 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
217 _public_ PAM_EXTERN
int pam_sm_open_session(
218 pam_handle_t
*handle
,
220 int argc
, const char **argv
) {
222 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
223 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
225 *username
, *id
, *object_path
, *runtime_path
,
227 *tty
= NULL
, *display
= NULL
,
228 *remote_user
= NULL
, *remote_host
= NULL
,
230 *type
= NULL
, *class = NULL
,
231 *class_pam
= NULL
, *type_pam
= NULL
, *cvtnr
= NULL
, *desktop
= NULL
;
232 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
233 int session_fd
= -1, existing
, r
;
234 bool debug
= false, remote
;
241 /* Make this a NOP on non-logind systems */
242 if (!logind_running())
245 if (parse_argv(handle
,
250 return PAM_SESSION_ERR
;
253 pam_syslog(handle
, LOG_DEBUG
, "pam-systemd initializing");
255 r
= get_user_data(handle
, &username
, &pw
);
256 if (r
!= PAM_SUCCESS
) {
257 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
261 /* Make sure we don't enter a loop by talking to
262 * systemd-logind when it is actually waiting for the
263 * background to finish start-up. If the service is
264 * "systemd-user" we simply set XDG_RUNTIME_DIR and
267 pam_get_item(handle
, PAM_SERVICE
, (const void**) &service
);
268 if (streq_ptr(service
, "systemd-user")) {
269 _cleanup_free_
char *p
= NULL
, *rt
= NULL
;
271 if (asprintf(&p
, "/run/systemd/users/"UID_FMT
, pw
->pw_uid
) < 0)
274 r
= parse_env_file(p
, NEWLINE
,
277 if (r
< 0 && r
!= -ENOENT
)
278 return PAM_SESSION_ERR
;
281 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", rt
, 0);
282 if (r
!= PAM_SUCCESS
) {
283 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
287 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, rt
);
288 if (r
!= PAM_SUCCESS
)
295 /* Otherwise, we ask logind to create a session for us */
297 pam_get_item(handle
, PAM_XDISPLAY
, (const void**) &display
);
298 pam_get_item(handle
, PAM_TTY
, (const void**) &tty
);
299 pam_get_item(handle
, PAM_RUSER
, (const void**) &remote_user
);
300 pam_get_item(handle
, PAM_RHOST
, (const void**) &remote_host
);
302 seat
= pam_getenv(handle
, "XDG_SEAT");
304 seat
= getenv("XDG_SEAT");
306 cvtnr
= pam_getenv(handle
, "XDG_VTNR");
308 cvtnr
= getenv("XDG_VTNR");
310 type
= pam_getenv(handle
, "XDG_SESSION_TYPE");
312 type
= getenv("XDG_SESSION_TYPE");
316 class = pam_getenv(handle
, "XDG_SESSION_CLASS");
318 class = getenv("XDG_SESSION_CLASS");
322 desktop
= pam_getenv(handle
, "XDG_SESSION_DESKTOP");
323 if (isempty(desktop
))
324 desktop
= getenv("XDG_SESSION_DESKTOP");
328 if (strchr(tty
, ':')) {
329 /* A tty with a colon is usually an X11 display,
330 * placed there to show up in utmp. We rearrange
331 * things and don't pretend that an X display was a
334 if (isempty(display
))
337 } else if (streq(tty
, "cron")) {
338 /* cron has been setting PAM_TTY to "cron" for a very
339 * long time and it probably shouldn't stop doing that
340 * for compatibility reasons. */
341 type
= "unspecified";
342 class = "background";
344 } else if (streq(tty
, "ssh")) {
345 /* ssh has been setting PAM_TTY to "ssh" for a very
346 * long time and probably shouldn't stop doing that
347 * for compatibility reasons. */
353 /* If this fails vtnr will be 0, that's intended */
355 (void) safe_atou32(cvtnr
, &vtnr
);
357 if (!isempty(display
) && !vtnr
) {
359 get_seat_from_display(display
, &seat
, &vtnr
);
360 else if (streq(seat
, "seat0"))
361 get_seat_from_display(display
, NULL
, &vtnr
);
364 if (seat
&& !streq(seat
, "seat0") && vtnr
!= 0) {
365 pam_syslog(handle
, LOG_DEBUG
, "Ignoring vtnr %"PRIu32
" for %s which is not seat0", vtnr
, seat
);
370 type
= !isempty(display
) ? "x11" :
371 !isempty(tty
) ? "tty" : "unspecified";
374 class = streq(type
, "unspecified") ? "background" : "user";
376 remote
= !isempty(remote_host
) && !is_localhost(remote_host
);
378 /* Talk to logind over the message bus */
380 r
= sd_bus_open_system(&bus
);
382 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
383 return PAM_SESSION_ERR
;
387 pam_syslog(handle
, LOG_DEBUG
, "Asking logind to create session: "
388 "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",
389 pw
->pw_uid
, getpid(),
391 type
, class, strempty(desktop
),
392 strempty(seat
), vtnr
, strempty(tty
), strempty(display
),
393 yes_no(remote
), strempty(remote_user
), strempty(remote_host
));
395 r
= sd_bus_call_method(bus
,
396 "org.freedesktop.login1",
397 "/org/freedesktop/login1",
398 "org.freedesktop.login1.Manager",
402 "uusssssussbssa(sv)",
403 (uint32_t) pw
->pw_uid
,
418 if (sd_bus_error_has_name(&error
, BUS_ERROR_SESSION_BUSY
)) {
419 pam_syslog(handle
, LOG_DEBUG
, "Cannot create session: %s", bus_error_message(&error
, r
));
422 pam_syslog(handle
, LOG_ERR
, "Failed to create session: %s", bus_error_message(&error
, r
));
423 return PAM_SYSTEM_ERR
;
427 r
= sd_bus_message_read(reply
,
438 pam_syslog(handle
, LOG_ERR
, "Failed to parse message: %s", strerror(-r
));
439 return PAM_SESSION_ERR
;
443 pam_syslog(handle
, LOG_DEBUG
, "Reply from logind: "
444 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
445 id
, object_path
, runtime_path
, session_fd
, seat
, vtnr
, original_uid
);
447 r
= pam_misc_setenv(handle
, "XDG_SESSION_ID", id
, 0);
448 if (r
!= PAM_SUCCESS
) {
449 pam_syslog(handle
, LOG_ERR
, "Failed to set session id.");
453 if (original_uid
== pw
->pw_uid
) {
454 /* Don't set $XDG_RUNTIME_DIR if the user we now
455 * authenticated for does not match the original user
456 * of the session. We do this in order not to result
457 * in privileged apps clobbering the runtime directory
460 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", runtime_path
, 0);
461 if (r
!= PAM_SUCCESS
) {
462 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
466 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, runtime_path
);
467 if (r
!= PAM_SUCCESS
)
471 if (!isempty(seat
)) {
472 r
= pam_misc_setenv(handle
, "XDG_SEAT", seat
, 0);
473 if (r
!= PAM_SUCCESS
) {
474 pam_syslog(handle
, LOG_ERR
, "Failed to set seat.");
480 char buf
[DECIMAL_STR_MAX(vtnr
)];
481 sprintf(buf
, "%u", vtnr
);
483 r
= pam_misc_setenv(handle
, "XDG_VTNR", buf
, 0);
484 if (r
!= PAM_SUCCESS
) {
485 pam_syslog(handle
, LOG_ERR
, "Failed to set virtual terminal number.");
490 r
= pam_set_data(handle
, "systemd.existing", INT_TO_PTR(!!existing
), NULL
);
491 if (r
!= PAM_SUCCESS
) {
492 pam_syslog(handle
, LOG_ERR
, "Failed to install existing flag.");
496 if (session_fd
>= 0) {
497 session_fd
= fcntl(session_fd
, F_DUPFD_CLOEXEC
, 3);
498 if (session_fd
< 0) {
499 pam_syslog(handle
, LOG_ERR
, "Failed to dup session fd: %m");
500 return PAM_SESSION_ERR
;
503 r
= pam_set_data(handle
, "systemd.session-fd", INT_TO_PTR(session_fd
+1), NULL
);
504 if (r
!= PAM_SUCCESS
) {
505 pam_syslog(handle
, LOG_ERR
, "Failed to install session fd.");
506 safe_close(session_fd
);
514 _public_ PAM_EXTERN
int pam_sm_close_session(
515 pam_handle_t
*handle
,
517 int argc
, const char **argv
) {
519 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
520 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
521 const void *existing
= NULL
;
527 /* Only release session if it wasn't pre-existing when we
528 * tried to create it */
529 pam_get_data(handle
, "systemd.existing", &existing
);
531 id
= pam_getenv(handle
, "XDG_SESSION_ID");
532 if (id
&& !existing
) {
534 /* Before we go and close the FIFO we need to tell
535 * logind that this is a clean session shutdown, so
536 * that it doesn't just go and slaughter us
537 * immediately after closing the fd */
539 r
= sd_bus_open_system(&bus
);
541 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
542 return PAM_SESSION_ERR
;
545 r
= sd_bus_call_method(bus
,
546 "org.freedesktop.login1",
547 "/org/freedesktop/login1",
548 "org.freedesktop.login1.Manager",
555 pam_syslog(handle
, LOG_ERR
, "Failed to release session: %s", bus_error_message(&error
, r
));
556 return PAM_SESSION_ERR
;
560 /* Note that we are knowingly leaking the FIFO fd here. This
561 * way, logind can watch us die. If we closed it here it would
562 * not have any clue when that is completed. Given that one
563 * cannot really have multiple PAM sessions open from the same
564 * process this means we will leak one FD at max. */