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>
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 "socket-util.h"
46 #include "terminal-util.h"
49 static int parse_argv(
51 int argc
, const char **argv
,
59 assert(argc
== 0 || argv
);
61 for (i
= 0; i
< (unsigned) argc
; i
++) {
62 if (startswith(argv
[i
], "class=")) {
66 } else if (startswith(argv
[i
], "type=")) {
70 } else if (streq(argv
[i
], "debug")) {
74 } else if (startswith(argv
[i
], "debug=")) {
77 k
= parse_boolean(argv
[i
] + 6);
79 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring.");
84 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
90 static int get_user_data(
92 const char **ret_username
,
93 struct passwd
**ret_pw
) {
95 const char *username
= NULL
;
96 struct passwd
*pw
= NULL
;
100 assert(ret_username
);
103 r
= pam_get_user(handle
, &username
, NULL
);
104 if (r
!= PAM_SUCCESS
) {
105 pam_syslog(handle
, LOG_ERR
, "Failed to get user name.");
109 if (isempty(username
)) {
110 pam_syslog(handle
, LOG_ERR
, "User name not valid.");
114 pw
= pam_modutil_getpwnam(handle
, username
);
116 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
117 return PAM_USER_UNKNOWN
;
121 *ret_username
= username
;
126 static int get_seat_from_display(const char *display
, const char **seat
, uint32_t *vtnr
) {
127 union sockaddr_union sa
= {
128 .un
.sun_family
= AF_UNIX
,
130 _cleanup_free_
char *p
= NULL
, *tty
= NULL
;
131 _cleanup_close_
int fd
= -1;
138 /* We deduce the X11 socket from the display name, then use
139 * SO_PEERCRED to determine the X11 server process, ask for
140 * the controlling tty of that and if it's a VC then we know
141 * the seat and the virtual terminal. Sounds ugly, is only
144 r
= socket_from_display(display
, &p
);
147 strncpy(sa
.un
.sun_path
, p
, sizeof(sa
.un
.sun_path
)-1);
149 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
153 if (connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
156 r
= getpeercred(fd
, &ucred
);
160 r
= get_ctty(ucred
.pid
, NULL
, &tty
);
164 v
= vtnr_from_tty(tty
);
172 *vtnr
= (uint32_t) v
;
177 static int export_legacy_dbus_address(
178 pam_handle_t
*handle
,
180 const char *runtime
) {
182 _cleanup_free_
char *s
= NULL
;
185 if (is_kdbus_available()) {
186 if (asprintf(&s
, KERNEL_USER_BUS_ADDRESS_FMT
";" UNIX_USER_BUS_ADDRESS_FMT
, uid
, runtime
) < 0)
189 /* FIXME: We *really* should move the access() check into the
190 * daemons that spawn dbus-daemon, instead of forcing
191 * DBUS_SESSION_BUS_ADDRESS= here. */
193 s
= strjoin(runtime
, "/bus", NULL
);
197 if (access(s
, F_OK
) < 0)
201 if (asprintf(&s
, UNIX_USER_BUS_ADDRESS_FMT
, runtime
) < 0)
205 r
= pam_misc_setenv(handle
, "DBUS_SESSION_BUS_ADDRESS", s
, 0);
206 if (r
!= PAM_SUCCESS
)
212 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
216 _public_ PAM_EXTERN
int pam_sm_open_session(
217 pam_handle_t
*handle
,
219 int argc
, const char **argv
) {
221 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
222 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
224 *username
, *id
, *object_path
, *runtime_path
,
226 *tty
= NULL
, *display
= NULL
,
227 *remote_user
= NULL
, *remote_host
= NULL
,
229 *type
= NULL
, *class = NULL
,
230 *class_pam
= NULL
, *type_pam
= NULL
, *cvtnr
= NULL
, *desktop
= NULL
;
231 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
232 int session_fd
= -1, existing
, r
;
233 bool debug
= false, remote
;
240 /* Make this a NOP on non-logind systems */
241 if (!logind_running())
244 if (parse_argv(handle
,
249 return PAM_SESSION_ERR
;
252 pam_syslog(handle
, LOG_DEBUG
, "pam-systemd initializing");
254 r
= get_user_data(handle
, &username
, &pw
);
255 if (r
!= PAM_SUCCESS
) {
256 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
260 /* Make sure we don't enter a loop by talking to
261 * systemd-logind when it is actually waiting for the
262 * background to finish start-up. If the service is
263 * "systemd-user" we simply set XDG_RUNTIME_DIR and
266 pam_get_item(handle
, PAM_SERVICE
, (const void**) &service
);
267 if (streq_ptr(service
, "systemd-user")) {
268 _cleanup_free_
char *p
= NULL
, *rt
= NULL
;
270 if (asprintf(&p
, "/run/systemd/users/"UID_FMT
, pw
->pw_uid
) < 0)
273 r
= parse_env_file(p
, NEWLINE
,
276 if (r
< 0 && r
!= -ENOENT
)
277 return PAM_SESSION_ERR
;
280 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", rt
, 0);
281 if (r
!= PAM_SUCCESS
) {
282 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
286 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, rt
);
287 if (r
!= PAM_SUCCESS
)
294 /* Otherwise, we ask logind to create a session for us */
296 pam_get_item(handle
, PAM_XDISPLAY
, (const void**) &display
);
297 pam_get_item(handle
, PAM_TTY
, (const void**) &tty
);
298 pam_get_item(handle
, PAM_RUSER
, (const void**) &remote_user
);
299 pam_get_item(handle
, PAM_RHOST
, (const void**) &remote_host
);
301 seat
= pam_getenv(handle
, "XDG_SEAT");
303 seat
= getenv("XDG_SEAT");
305 cvtnr
= pam_getenv(handle
, "XDG_VTNR");
307 cvtnr
= getenv("XDG_VTNR");
309 type
= pam_getenv(handle
, "XDG_SESSION_TYPE");
311 type
= getenv("XDG_SESSION_TYPE");
315 class = pam_getenv(handle
, "XDG_SESSION_CLASS");
317 class = getenv("XDG_SESSION_CLASS");
321 desktop
= pam_getenv(handle
, "XDG_SESSION_DESKTOP");
322 if (isempty(desktop
))
323 desktop
= getenv("XDG_SESSION_DESKTOP");
327 if (strchr(tty
, ':')) {
328 /* A tty with a colon is usually an X11 display,
329 * placed there to show up in utmp. We rearrange
330 * things and don't pretend that an X display was a
333 if (isempty(display
))
336 } else if (streq(tty
, "cron")) {
337 /* cron has been setting PAM_TTY to "cron" for a very
338 * long time and it probably shouldn't stop doing that
339 * for compatibility reasons. */
340 type
= "unspecified";
341 class = "background";
343 } else if (streq(tty
, "ssh")) {
344 /* ssh has been setting PAM_TTY to "ssh" for a very
345 * long time and probably shouldn't stop doing that
346 * for compatibility reasons. */
352 /* If this fails vtnr will be 0, that's intended */
354 (void) safe_atou32(cvtnr
, &vtnr
);
356 if (!isempty(display
) && !vtnr
) {
358 get_seat_from_display(display
, &seat
, &vtnr
);
359 else if (streq(seat
, "seat0"))
360 get_seat_from_display(display
, NULL
, &vtnr
);
363 if (seat
&& !streq(seat
, "seat0") && vtnr
!= 0) {
364 pam_syslog(handle
, LOG_DEBUG
, "Ignoring vtnr %"PRIu32
" for %s which is not seat0", vtnr
, seat
);
369 type
= !isempty(display
) ? "x11" :
370 !isempty(tty
) ? "tty" : "unspecified";
373 class = streq(type
, "unspecified") ? "background" : "user";
375 remote
= !isempty(remote_host
) && !is_localhost(remote_host
);
377 /* Talk to logind over the message bus */
379 r
= sd_bus_open_system(&bus
);
381 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
382 return PAM_SESSION_ERR
;
386 pam_syslog(handle
, LOG_DEBUG
, "Asking logind to create session: "
387 "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",
388 pw
->pw_uid
, getpid(),
390 type
, class, strempty(desktop
),
391 strempty(seat
), vtnr
, strempty(tty
), strempty(display
),
392 yes_no(remote
), strempty(remote_user
), strempty(remote_host
));
394 r
= sd_bus_call_method(bus
,
395 "org.freedesktop.login1",
396 "/org/freedesktop/login1",
397 "org.freedesktop.login1.Manager",
401 "uusssssussbssa(sv)",
402 (uint32_t) pw
->pw_uid
,
417 if (sd_bus_error_has_name(&error
, BUS_ERROR_SESSION_BUSY
)) {
418 pam_syslog(handle
, LOG_DEBUG
, "Cannot create session: %s", bus_error_message(&error
, r
));
421 pam_syslog(handle
, LOG_ERR
, "Failed to create session: %s", bus_error_message(&error
, r
));
422 return PAM_SYSTEM_ERR
;
426 r
= sd_bus_message_read(reply
,
437 pam_syslog(handle
, LOG_ERR
, "Failed to parse message: %s", strerror(-r
));
438 return PAM_SESSION_ERR
;
442 pam_syslog(handle
, LOG_DEBUG
, "Reply from logind: "
443 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
444 id
, object_path
, runtime_path
, session_fd
, seat
, vtnr
, original_uid
);
446 r
= pam_misc_setenv(handle
, "XDG_SESSION_ID", id
, 0);
447 if (r
!= PAM_SUCCESS
) {
448 pam_syslog(handle
, LOG_ERR
, "Failed to set session id.");
452 if (original_uid
== pw
->pw_uid
) {
453 /* Don't set $XDG_RUNTIME_DIR if the user we now
454 * authenticated for does not match the original user
455 * of the session. We do this in order not to result
456 * in privileged apps clobbering the runtime directory
459 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", runtime_path
, 0);
460 if (r
!= PAM_SUCCESS
) {
461 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
465 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, runtime_path
);
466 if (r
!= PAM_SUCCESS
)
470 if (!isempty(seat
)) {
471 r
= pam_misc_setenv(handle
, "XDG_SEAT", seat
, 0);
472 if (r
!= PAM_SUCCESS
) {
473 pam_syslog(handle
, LOG_ERR
, "Failed to set seat.");
479 char buf
[DECIMAL_STR_MAX(vtnr
)];
480 sprintf(buf
, "%u", vtnr
);
482 r
= pam_misc_setenv(handle
, "XDG_VTNR", buf
, 0);
483 if (r
!= PAM_SUCCESS
) {
484 pam_syslog(handle
, LOG_ERR
, "Failed to set virtual terminal number.");
489 r
= pam_set_data(handle
, "systemd.existing", INT_TO_PTR(!!existing
), NULL
);
490 if (r
!= PAM_SUCCESS
) {
491 pam_syslog(handle
, LOG_ERR
, "Failed to install existing flag.");
495 if (session_fd
>= 0) {
496 session_fd
= fcntl(session_fd
, F_DUPFD_CLOEXEC
, 3);
497 if (session_fd
< 0) {
498 pam_syslog(handle
, LOG_ERR
, "Failed to dup session fd: %m");
499 return PAM_SESSION_ERR
;
502 r
= pam_set_data(handle
, "systemd.session-fd", INT_TO_PTR(session_fd
+1), NULL
);
503 if (r
!= PAM_SUCCESS
) {
504 pam_syslog(handle
, LOG_ERR
, "Failed to install session fd.");
505 safe_close(session_fd
);
513 _public_ PAM_EXTERN
int pam_sm_close_session(
514 pam_handle_t
*handle
,
516 int argc
, const char **argv
) {
518 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
519 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
520 const void *existing
= NULL
;
526 /* Only release session if it wasn't pre-existing when we
527 * tried to create it */
528 pam_get_data(handle
, "systemd.existing", &existing
);
530 id
= pam_getenv(handle
, "XDG_SESSION_ID");
531 if (id
&& !existing
) {
533 /* Before we go and close the FIFO we need to tell
534 * logind that this is a clean session shutdown, so
535 * that it doesn't just go and slaughter us
536 * immediately after closing the fd */
538 r
= sd_bus_open_system(&bus
);
540 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
541 return PAM_SESSION_ERR
;
544 r
= sd_bus_call_method(bus
,
545 "org.freedesktop.login1",
546 "/org/freedesktop/login1",
547 "org.freedesktop.login1.Manager",
554 pam_syslog(handle
, LOG_ERR
, "Failed to release session: %s", bus_error_message(&error
, r
));
555 return PAM_SESSION_ERR
;
559 /* Note that we are knowingly leaking the FIFO fd here. This
560 * way, logind can watch us die. If we closed it here it would
561 * not have any clue when that is completed. Given that one
562 * cannot really have multiple PAM sessions open from the same
563 * process this means we will leak one FD at max. */