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>
34 #include "bus-common-errors.h"
41 #include "socket-util.h"
43 #include "bus-error.h"
44 #include "formats-util.h"
45 #include "terminal-util.h"
46 #include "hostname-util.h"
48 static int parse_argv(
50 int argc
, const char **argv
,
58 assert(argc
== 0 || argv
);
60 for (i
= 0; i
< (unsigned) argc
; i
++) {
61 if (startswith(argv
[i
], "class=")) {
65 } else if (startswith(argv
[i
], "type=")) {
69 } else if (streq(argv
[i
], "debug")) {
73 } else if (startswith(argv
[i
], "debug=")) {
76 k
= parse_boolean(argv
[i
] + 6);
78 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring.");
83 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
89 static int get_user_data(
91 const char **ret_username
,
92 struct passwd
**ret_pw
) {
94 const char *username
= NULL
;
95 struct passwd
*pw
= NULL
;
102 r
= pam_get_user(handle
, &username
, NULL
);
103 if (r
!= PAM_SUCCESS
) {
104 pam_syslog(handle
, LOG_ERR
, "Failed to get user name.");
108 if (isempty(username
)) {
109 pam_syslog(handle
, LOG_ERR
, "User name not valid.");
113 pw
= pam_modutil_getpwnam(handle
, username
);
115 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
116 return PAM_USER_UNKNOWN
;
120 *ret_username
= username
;
125 static int get_seat_from_display(const char *display
, const char **seat
, uint32_t *vtnr
) {
126 union sockaddr_union sa
= {
127 .un
.sun_family
= AF_UNIX
,
129 _cleanup_free_
char *p
= NULL
, *tty
= NULL
;
130 _cleanup_close_
int fd
= -1;
137 /* We deduce the X11 socket from the display name, then use
138 * SO_PEERCRED to determine the X11 server process, ask for
139 * the controlling tty of that and if it's a VC then we know
140 * the seat and the virtual terminal. Sounds ugly, is only
143 r
= socket_from_display(display
, &p
);
146 strncpy(sa
.un
.sun_path
, p
, sizeof(sa
.un
.sun_path
)-1);
148 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
152 if (connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
155 r
= getpeercred(fd
, &ucred
);
159 r
= get_ctty(ucred
.pid
, NULL
, &tty
);
163 v
= vtnr_from_tty(tty
);
171 *vtnr
= (uint32_t) v
;
176 static int export_legacy_dbus_address(
177 pam_handle_t
*handle
,
179 const char *runtime
) {
181 _cleanup_free_
char *s
= NULL
;
184 if (is_kdbus_available()) {
185 if (asprintf(&s
, KERNEL_USER_BUS_ADDRESS_FMT
";" UNIX_USER_BUS_ADDRESS_FMT
, uid
, runtime
) < 0)
188 /* FIXME: We *really* should move the access() check into the
189 * daemons that spawn dbus-daemon, instead of forcing
190 * DBUS_SESSION_BUS_ADDRESS= here. */
192 s
= strjoin(runtime
, "/bus", NULL
);
196 if (access(s
, F_OK
) < 0)
200 if (asprintf(&s
, UNIX_USER_BUS_ADDRESS_FMT
, runtime
) < 0)
204 r
= pam_misc_setenv(handle
, "DBUS_SESSION_BUS_ADDRESS", s
, 0);
205 if (r
!= PAM_SUCCESS
)
211 pam_syslog(handle
, LOG_ERR
, "Failed to set bus variable.");
215 _public_ PAM_EXTERN
int pam_sm_open_session(
216 pam_handle_t
*handle
,
218 int argc
, const char **argv
) {
220 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
221 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
223 *username
, *id
, *object_path
, *runtime_path
,
225 *tty
= NULL
, *display
= NULL
,
226 *remote_user
= NULL
, *remote_host
= NULL
,
228 *type
= NULL
, *class = NULL
,
229 *class_pam
= NULL
, *type_pam
= NULL
, *cvtnr
= NULL
, *desktop
= NULL
;
230 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
231 int session_fd
= -1, existing
, r
;
232 bool debug
= false, remote
;
239 /* Make this a NOP on non-logind systems */
240 if (!logind_running())
243 if (parse_argv(handle
,
248 return PAM_SESSION_ERR
;
251 pam_syslog(handle
, LOG_DEBUG
, "pam-systemd initializing");
253 r
= get_user_data(handle
, &username
, &pw
);
254 if (r
!= PAM_SUCCESS
) {
255 pam_syslog(handle
, LOG_ERR
, "Failed to get user data.");
259 /* Make sure we don't enter a loop by talking to
260 * systemd-logind when it is actually waiting for the
261 * background to finish start-up. If the service is
262 * "systemd-user" we simply set XDG_RUNTIME_DIR and
265 pam_get_item(handle
, PAM_SERVICE
, (const void**) &service
);
266 if (streq_ptr(service
, "systemd-user")) {
267 _cleanup_free_
char *p
= NULL
, *rt
= NULL
;
269 if (asprintf(&p
, "/run/systemd/users/"UID_FMT
, pw
->pw_uid
) < 0)
272 r
= parse_env_file(p
, NEWLINE
,
275 if (r
< 0 && r
!= -ENOENT
)
276 return PAM_SESSION_ERR
;
279 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", rt
, 0);
280 if (r
!= PAM_SUCCESS
) {
281 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
285 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, rt
);
286 if (r
!= PAM_SUCCESS
)
293 /* Otherwise, we ask logind to create a session for us */
295 pam_get_item(handle
, PAM_XDISPLAY
, (const void**) &display
);
296 pam_get_item(handle
, PAM_TTY
, (const void**) &tty
);
297 pam_get_item(handle
, PAM_RUSER
, (const void**) &remote_user
);
298 pam_get_item(handle
, PAM_RHOST
, (const void**) &remote_host
);
300 seat
= pam_getenv(handle
, "XDG_SEAT");
302 seat
= getenv("XDG_SEAT");
304 cvtnr
= pam_getenv(handle
, "XDG_VTNR");
306 cvtnr
= getenv("XDG_VTNR");
308 type
= pam_getenv(handle
, "XDG_SESSION_TYPE");
310 type
= getenv("XDG_SESSION_TYPE");
314 class = pam_getenv(handle
, "XDG_SESSION_CLASS");
316 class = getenv("XDG_SESSION_CLASS");
320 desktop
= pam_getenv(handle
, "XDG_SESSION_DESKTOP");
321 if (isempty(desktop
))
322 desktop
= getenv("XDG_SESSION_DESKTOP");
326 if (strchr(tty
, ':')) {
327 /* A tty with a colon is usually an X11 display,
328 * placed there to show up in utmp. We rearrange
329 * things and don't pretend that an X display was a
332 if (isempty(display
))
335 } else if (streq(tty
, "cron")) {
336 /* cron has been setting PAM_TTY to "cron" for a very
337 * long time and it probably shouldn't stop doing that
338 * for compatibility reasons. */
339 type
= "unspecified";
340 class = "background";
342 } else if (streq(tty
, "ssh")) {
343 /* ssh has been setting PAM_TTY to "ssh" for a very
344 * long time and probably shouldn't stop doing that
345 * for compatibility reasons. */
351 /* If this fails vtnr will be 0, that's intended */
353 (void) safe_atou32(cvtnr
, &vtnr
);
355 if (!isempty(display
) && !vtnr
) {
357 get_seat_from_display(display
, &seat
, &vtnr
);
358 else if (streq(seat
, "seat0"))
359 get_seat_from_display(display
, NULL
, &vtnr
);
362 if (seat
&& !streq(seat
, "seat0") && vtnr
!= 0) {
363 pam_syslog(handle
, LOG_DEBUG
, "Ignoring vtnr %"PRIu32
" for %s which is not seat0", vtnr
, seat
);
368 type
= !isempty(display
) ? "x11" :
369 !isempty(tty
) ? "tty" : "unspecified";
372 class = streq(type
, "unspecified") ? "background" : "user";
374 remote
= !isempty(remote_host
) && !is_localhost(remote_host
);
376 /* Talk to logind over the message bus */
378 r
= sd_bus_open_system(&bus
);
380 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
381 return PAM_SESSION_ERR
;
385 pam_syslog(handle
, LOG_DEBUG
, "Asking logind to create session: "
386 "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",
387 pw
->pw_uid
, getpid(),
389 type
, class, strempty(desktop
),
390 strempty(seat
), vtnr
, strempty(tty
), strempty(display
),
391 yes_no(remote
), strempty(remote_user
), strempty(remote_host
));
393 r
= sd_bus_call_method(bus
,
394 "org.freedesktop.login1",
395 "/org/freedesktop/login1",
396 "org.freedesktop.login1.Manager",
400 "uusssssussbssa(sv)",
401 (uint32_t) pw
->pw_uid
,
416 if (sd_bus_error_has_name(&error
, BUS_ERROR_SESSION_BUSY
)) {
417 pam_syslog(handle
, LOG_DEBUG
, "Cannot create session: %s", bus_error_message(&error
, r
));
420 pam_syslog(handle
, LOG_ERR
, "Failed to create session: %s", bus_error_message(&error
, r
));
421 return PAM_SYSTEM_ERR
;
425 r
= sd_bus_message_read(reply
,
436 pam_syslog(handle
, LOG_ERR
, "Failed to parse message: %s", strerror(-r
));
437 return PAM_SESSION_ERR
;
441 pam_syslog(handle
, LOG_DEBUG
, "Reply from logind: "
442 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
443 id
, object_path
, runtime_path
, session_fd
, seat
, vtnr
, original_uid
);
445 r
= pam_misc_setenv(handle
, "XDG_SESSION_ID", id
, 0);
446 if (r
!= PAM_SUCCESS
) {
447 pam_syslog(handle
, LOG_ERR
, "Failed to set session id.");
451 if (original_uid
== pw
->pw_uid
) {
452 /* Don't set $XDG_RUNTIME_DIR if the user we now
453 * authenticated for does not match the original user
454 * of the session. We do this in order not to result
455 * in privileged apps clobbering the runtime directory
458 r
= pam_misc_setenv(handle
, "XDG_RUNTIME_DIR", runtime_path
, 0);
459 if (r
!= PAM_SUCCESS
) {
460 pam_syslog(handle
, LOG_ERR
, "Failed to set runtime dir.");
464 r
= export_legacy_dbus_address(handle
, pw
->pw_uid
, runtime_path
);
465 if (r
!= PAM_SUCCESS
)
469 if (!isempty(seat
)) {
470 r
= pam_misc_setenv(handle
, "XDG_SEAT", seat
, 0);
471 if (r
!= PAM_SUCCESS
) {
472 pam_syslog(handle
, LOG_ERR
, "Failed to set seat.");
478 char buf
[DECIMAL_STR_MAX(vtnr
)];
479 sprintf(buf
, "%u", vtnr
);
481 r
= pam_misc_setenv(handle
, "XDG_VTNR", buf
, 0);
482 if (r
!= PAM_SUCCESS
) {
483 pam_syslog(handle
, LOG_ERR
, "Failed to set virtual terminal number.");
488 r
= pam_set_data(handle
, "systemd.existing", INT_TO_PTR(!!existing
), NULL
);
489 if (r
!= PAM_SUCCESS
) {
490 pam_syslog(handle
, LOG_ERR
, "Failed to install existing flag.");
494 if (session_fd
>= 0) {
495 session_fd
= fcntl(session_fd
, F_DUPFD_CLOEXEC
, 3);
496 if (session_fd
< 0) {
497 pam_syslog(handle
, LOG_ERR
, "Failed to dup session fd: %m");
498 return PAM_SESSION_ERR
;
501 r
= pam_set_data(handle
, "systemd.session-fd", INT_TO_PTR(session_fd
+1), NULL
);
502 if (r
!= PAM_SUCCESS
) {
503 pam_syslog(handle
, LOG_ERR
, "Failed to install session fd.");
504 safe_close(session_fd
);
512 _public_ PAM_EXTERN
int pam_sm_close_session(
513 pam_handle_t
*handle
,
515 int argc
, const char **argv
) {
517 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
518 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
519 const void *existing
= NULL
;
525 /* Only release session if it wasn't pre-existing when we
526 * tried to create it */
527 pam_get_data(handle
, "systemd.existing", &existing
);
529 id
= pam_getenv(handle
, "XDG_SESSION_ID");
530 if (id
&& !existing
) {
532 /* Before we go and close the FIFO we need to tell
533 * logind that this is a clean session shutdown, so
534 * that it doesn't just go and slaughter us
535 * immediately after closing the fd */
537 r
= sd_bus_open_system(&bus
);
539 pam_syslog(handle
, LOG_ERR
, "Failed to connect to system bus: %s", strerror(-r
));
540 return PAM_SESSION_ERR
;
543 r
= sd_bus_call_method(bus
,
544 "org.freedesktop.login1",
545 "/org/freedesktop/login1",
546 "org.freedesktop.login1.Manager",
553 pam_syslog(handle
, LOG_ERR
, "Failed to release session: %s", bus_error_message(&error
, r
));
554 return PAM_SESSION_ERR
;
558 /* Note that we are knowingly leaking the FIFO fd here. This
559 * way, logind can watch us die. If we closed it here it would
560 * not have any clue when that is completed. Given that one
561 * cannot really have multiple PAM sessions open from the same
562 * process this means we will leak one FD at max. */