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 "alloc-util.h"
34 #include "audit-util.h"
35 #include "bus-common-errors.h"
36 #include "bus-error.h"
41 #include "formats-util.h"
42 #include "hostname-util.h"
43 #include "login-util.h"
45 #include "parse-util.h"
46 #include "socket-util.h"
48 #include "terminal-util.h"
51 static int parse_argv(
53 int argc
, const char **argv
,
61 assert(argc
== 0 || argv
);
63 for (i
= 0; i
< (unsigned) argc
; i
++) {
64 if (startswith(argv
[i
], "class=")) {
68 } else if (startswith(argv
[i
], "type=")) {
72 } else if (streq(argv
[i
], "debug")) {
76 } else if (startswith(argv
[i
], "debug=")) {
79 k
= parse_boolean(argv
[i
] + 6);
81 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring.");
86 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
92 static int get_user_data(
94 const char **ret_username
,
95 struct passwd
**ret_pw
) {
97 const char *username
= NULL
;
98 struct passwd
*pw
= NULL
;
102 assert(ret_username
);
105 r
= pam_get_user(handle
, &username
, NULL
);
106 if (r
!= PAM_SUCCESS
) {
107 pam_syslog(handle
, LOG_ERR
, "Failed to get user name.");
111 if (isempty(username
)) {
112 pam_syslog(handle
, LOG_ERR
, "User name not valid.");
116 pw
= pam_modutil_getpwnam(handle
, username
);
118 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
119 return PAM_USER_UNKNOWN
;
123 *ret_username
= username
;
128 static int get_seat_from_display(const char *display
, const char **seat
, uint32_t *vtnr
) {
129 union sockaddr_union sa
= {
130 .un
.sun_family
= AF_UNIX
,
132 _cleanup_free_
char *p
= NULL
, *tty
= NULL
;
133 _cleanup_close_
int fd
= -1;
140 /* We deduce the X11 socket from the display name, then use
141 * SO_PEERCRED to determine the X11 server process, ask for
142 * the controlling tty of that and if it's a VC then we know
143 * the seat and the virtual terminal. Sounds ugly, is only
146 r
= socket_from_display(display
, &p
);
149 strncpy(sa
.un
.sun_path
, p
, sizeof(sa
.un
.sun_path
)-1);
151 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
155 if (connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
158 r
= getpeercred(fd
, &ucred
);
162 r
= get_ctty(ucred
.pid
, NULL
, &tty
);
166 v
= vtnr_from_tty(tty
);
174 *vtnr
= (uint32_t) v
;
179 static int export_legacy_dbus_address(
180 pam_handle_t
*handle
,
182 const char *runtime
) {
184 _cleanup_free_
char *s
= NULL
;
187 if (is_kdbus_available()) {
188 if (asprintf(&s
, KERNEL_USER_BUS_ADDRESS_FMT
";" UNIX_USER_BUS_ADDRESS_FMT
, uid
, runtime
) < 0)
191 /* FIXME: We *really* should move the access() check into the
192 * daemons that spawn dbus-daemon, instead of forcing
193 * DBUS_SESSION_BUS_ADDRESS= here. */
195 s
= strjoin(runtime
, "/bus", NULL
);
199 if (access(s
, F_OK
) < 0)
203 if (asprintf(&s
, UNIX_USER_BUS_ADDRESS_FMT
, runtime
) < 0)
207 r
= pam_misc_setenv(handle
, "DBUS_SESSION_BUS_ADDRESS", s
, 0);
208 if (r
!= PAM_SUCCESS
)
214 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
218 _public_ PAM_EXTERN
int pam_sm_open_session(
219 pam_handle_t
*handle
,
221 int argc
, const char **argv
) {
223 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
224 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
226 *username
, *id
, *object_path
, *runtime_path
,
228 *tty
= NULL
, *display
= NULL
,
229 *remote_user
= NULL
, *remote_host
= NULL
,
231 *type
= NULL
, *class = NULL
,
232 *class_pam
= NULL
, *type_pam
= NULL
, *cvtnr
= NULL
, *desktop
= NULL
;
233 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
234 int session_fd
= -1, existing
, r
;
235 bool debug
= false, remote
;
242 /* Make this a NOP on non-logind systems */
243 if (!logind_running())
246 if (parse_argv(handle
,
251 return PAM_SESSION_ERR
;
254 pam_syslog(handle
, LOG_DEBUG
, "pam-systemd initializing");
256 r
= get_user_data(handle
, &username
, &pw
);
257 if (r
!= PAM_SUCCESS
) {
258 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
262 /* Make sure we don't enter a loop by talking to
263 * systemd-logind when it is actually waiting for the
264 * background to finish start-up. If the service is
265 * "systemd-user" we simply set XDG_RUNTIME_DIR and
268 pam_get_item(handle
, PAM_SERVICE
, (const void**) &service
);
269 if (streq_ptr(service
, "systemd-user")) {
270 _cleanup_free_
char *p
= NULL
, *rt
= NULL
;
272 if (asprintf(&p
, "/run/systemd/users/"UID_FMT
, pw
->pw_uid
) < 0)
275 r
= parse_env_file(p
, NEWLINE
,
278 if (r
< 0 && r
!= -ENOENT
)
279 return PAM_SESSION_ERR
;
282 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", rt
, 0);
283 if (r
!= PAM_SUCCESS
) {
284 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
288 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, rt
);
289 if (r
!= PAM_SUCCESS
)
296 /* Otherwise, we ask logind to create a session for us */
298 pam_get_item(handle
, PAM_XDISPLAY
, (const void**) &display
);
299 pam_get_item(handle
, PAM_TTY
, (const void**) &tty
);
300 pam_get_item(handle
, PAM_RUSER
, (const void**) &remote_user
);
301 pam_get_item(handle
, PAM_RHOST
, (const void**) &remote_host
);
303 seat
= pam_getenv(handle
, "XDG_SEAT");
305 seat
= getenv("XDG_SEAT");
307 cvtnr
= pam_getenv(handle
, "XDG_VTNR");
309 cvtnr
= getenv("XDG_VTNR");
311 type
= pam_getenv(handle
, "XDG_SESSION_TYPE");
313 type
= getenv("XDG_SESSION_TYPE");
317 class = pam_getenv(handle
, "XDG_SESSION_CLASS");
319 class = getenv("XDG_SESSION_CLASS");
323 desktop
= pam_getenv(handle
, "XDG_SESSION_DESKTOP");
324 if (isempty(desktop
))
325 desktop
= getenv("XDG_SESSION_DESKTOP");
329 if (strchr(tty
, ':')) {
330 /* A tty with a colon is usually an X11 display,
331 * placed there to show up in utmp. We rearrange
332 * things and don't pretend that an X display was a
335 if (isempty(display
))
338 } else if (streq(tty
, "cron")) {
339 /* cron has been setting PAM_TTY to "cron" for a very
340 * long time and it probably shouldn't stop doing that
341 * for compatibility reasons. */
342 type
= "unspecified";
343 class = "background";
345 } else if (streq(tty
, "ssh")) {
346 /* ssh has been setting PAM_TTY to "ssh" for a very
347 * long time and probably shouldn't stop doing that
348 * for compatibility reasons. */
354 /* If this fails vtnr will be 0, that's intended */
356 (void) safe_atou32(cvtnr
, &vtnr
);
358 if (!isempty(display
) && !vtnr
) {
360 get_seat_from_display(display
, &seat
, &vtnr
);
361 else if (streq(seat
, "seat0"))
362 get_seat_from_display(display
, NULL
, &vtnr
);
365 if (seat
&& !streq(seat
, "seat0") && vtnr
!= 0) {
366 pam_syslog(handle
, LOG_DEBUG
, "Ignoring vtnr %"PRIu32
" for %s which is not seat0", vtnr
, seat
);
371 type
= !isempty(display
) ? "x11" :
372 !isempty(tty
) ? "tty" : "unspecified";
375 class = streq(type
, "unspecified") ? "background" : "user";
377 remote
= !isempty(remote_host
) && !is_localhost(remote_host
);
379 /* Talk to logind over the message bus */
381 r
= sd_bus_open_system(&bus
);
383 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
384 return PAM_SESSION_ERR
;
388 pam_syslog(handle
, LOG_DEBUG
, "Asking logind to create session: "
389 "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",
390 pw
->pw_uid
, getpid(),
392 type
, class, strempty(desktop
),
393 strempty(seat
), vtnr
, strempty(tty
), strempty(display
),
394 yes_no(remote
), strempty(remote_user
), strempty(remote_host
));
396 r
= sd_bus_call_method(bus
,
397 "org.freedesktop.login1",
398 "/org/freedesktop/login1",
399 "org.freedesktop.login1.Manager",
403 "uusssssussbssa(sv)",
404 (uint32_t) pw
->pw_uid
,
419 if (sd_bus_error_has_name(&error
, BUS_ERROR_SESSION_BUSY
)) {
420 pam_syslog(handle
, LOG_DEBUG
, "Cannot create session: %s", bus_error_message(&error
, r
));
423 pam_syslog(handle
, LOG_ERR
, "Failed to create session: %s", bus_error_message(&error
, r
));
424 return PAM_SYSTEM_ERR
;
428 r
= sd_bus_message_read(reply
,
439 pam_syslog(handle
, LOG_ERR
, "Failed to parse message: %s", strerror(-r
));
440 return PAM_SESSION_ERR
;
444 pam_syslog(handle
, LOG_DEBUG
, "Reply from logind: "
445 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
446 id
, object_path
, runtime_path
, session_fd
, seat
, vtnr
, original_uid
);
448 r
= pam_misc_setenv(handle
, "XDG_SESSION_ID", id
, 0);
449 if (r
!= PAM_SUCCESS
) {
450 pam_syslog(handle
, LOG_ERR
, "Failed to set session id.");
454 if (original_uid
== pw
->pw_uid
) {
455 /* Don't set $XDG_RUNTIME_DIR if the user we now
456 * authenticated for does not match the original user
457 * of the session. We do this in order not to result
458 * in privileged apps clobbering the runtime directory
461 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", runtime_path
, 0);
462 if (r
!= PAM_SUCCESS
) {
463 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
467 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, runtime_path
);
468 if (r
!= PAM_SUCCESS
)
472 if (!isempty(seat
)) {
473 r
= pam_misc_setenv(handle
, "XDG_SEAT", seat
, 0);
474 if (r
!= PAM_SUCCESS
) {
475 pam_syslog(handle
, LOG_ERR
, "Failed to set seat.");
481 char buf
[DECIMAL_STR_MAX(vtnr
)];
482 sprintf(buf
, "%u", vtnr
);
484 r
= pam_misc_setenv(handle
, "XDG_VTNR", buf
, 0);
485 if (r
!= PAM_SUCCESS
) {
486 pam_syslog(handle
, LOG_ERR
, "Failed to set virtual terminal number.");
491 r
= pam_set_data(handle
, "systemd.existing", INT_TO_PTR(!!existing
), NULL
);
492 if (r
!= PAM_SUCCESS
) {
493 pam_syslog(handle
, LOG_ERR
, "Failed to install existing flag.");
497 if (session_fd
>= 0) {
498 session_fd
= fcntl(session_fd
, F_DUPFD_CLOEXEC
, 3);
499 if (session_fd
< 0) {
500 pam_syslog(handle
, LOG_ERR
, "Failed to dup session fd: %m");
501 return PAM_SESSION_ERR
;
504 r
= pam_set_data(handle
, "systemd.session-fd", INT_TO_PTR(session_fd
+1), NULL
);
505 if (r
!= PAM_SUCCESS
) {
506 pam_syslog(handle
, LOG_ERR
, "Failed to install session fd.");
507 safe_close(session_fd
);
515 _public_ PAM_EXTERN
int pam_sm_close_session(
516 pam_handle_t
*handle
,
518 int argc
, const char **argv
) {
520 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
521 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
522 const void *existing
= NULL
;
528 /* Only release session if it wasn't pre-existing when we
529 * tried to create it */
530 pam_get_data(handle
, "systemd.existing", &existing
);
532 id
= pam_getenv(handle
, "XDG_SESSION_ID");
533 if (id
&& !existing
) {
535 /* Before we go and close the FIFO we need to tell
536 * logind that this is a clean session shutdown, so
537 * that it doesn't just go and slaughter us
538 * immediately after closing the fd */
540 r
= sd_bus_open_system(&bus
);
542 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
543 return PAM_SESSION_ERR
;
546 r
= sd_bus_call_method(bus
,
547 "org.freedesktop.login1",
548 "/org/freedesktop/login1",
549 "org.freedesktop.login1.Manager",
556 pam_syslog(handle
, LOG_ERR
, "Failed to release session: %s", bus_error_message(&error
, r
));
557 return PAM_SESSION_ERR
;
561 /* Note that we are knowingly leaking the FIFO fd here. This
562 * way, logind can watch us die. If we closed it here it would
563 * not have any clue when that is completed. Given that one
564 * cannot really have multiple PAM sessions open from the same
565 * process this means we will leak one FD at max. */