1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <security/pam_ext.h>
5 #include <security/pam_misc.h>
6 #include <security/pam_modules.h>
10 #include "alloc-util.h"
11 #include "bus-common-errors.h"
12 #include "bus-locator.h"
15 #include "home-util.h"
16 #include "locale-util.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "string-util.h"
22 #include "time-util.h"
23 #include "user-record.h"
24 #include "user-record-util.h"
25 #include "user-util.h"
27 typedef enum AcquireHomeFlags
{
28 ACQUIRE_MUST_AUTHENTICATE
= 1 << 0,
29 ACQUIRE_PLEASE_SUSPEND
= 1 << 1,
30 ACQUIRE_REF_ANYWAY
= 1 << 2,
33 static int parse_argv(
35 int argc
, const char **argv
,
36 AcquireHomeFlags
*flags
,
40 assert(argc
== 0 || argv
);
42 for (int i
= 0; i
< argc
; i
++) {
45 if ((v
= startswith(argv
[i
], "suspend="))) {
50 pam_syslog(handle
, LOG_WARNING
, "Failed to parse suspend= argument, ignoring: %s", v
);
52 SET_FLAG(*flags
, ACQUIRE_PLEASE_SUSPEND
, k
);
54 } else if (streq(argv
[i
], "debug")) {
58 } else if ((v
= startswith(argv
[i
], "debug="))) {
62 pam_syslog(handle
, LOG_WARNING
, "Failed to parse debug= argument, ignoring: %s", v
);
67 pam_syslog(handle
, LOG_WARNING
, "Unknown parameter '%s', ignoring", argv
[i
]);
75 AcquireHomeFlags
*flags
) {
80 /* Let's read the suspend setting from an env var in addition to the PAM command line. That makes it
81 * easy to declare the features of a display manager in code rather than configuration, and this is
82 * really a feature of code */
84 v
= pam_getenv(handle
, "SYSTEMD_HOME_SUSPEND");
86 /* Also check the process env block, so that people can control this via an env var from the
87 * outside of our process. */
88 v
= secure_getenv("SYSTEMD_HOME_SUSPEND");
95 pam_syslog(handle
, LOG_WARNING
, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v
);
97 SET_FLAG(*flags
, ACQUIRE_PLEASE_SUSPEND
, r
);
102 static int acquire_user_record(
103 pam_handle_t
*handle
,
104 const char *username
,
106 UserRecord
**ret_record
,
107 PamBusData
**bus_data
) {
114 r
= pam_get_user(handle
, &username
, NULL
);
115 if (r
!= PAM_SUCCESS
)
116 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get user name: @PAMERR@");
117 if (isempty(username
))
118 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SERVICE_ERR
, "User name not set.");
121 /* Possibly split out the area name */
122 _cleanup_free_
char *username_without_area
= NULL
, *area
= NULL
;
123 const char *carea
= strrchr(username
, '%');
124 if (carea
&& (filename_is_valid(carea
+ 1) || isempty(carea
+ 1))) {
125 username_without_area
= strndup(username
, carea
- username
);
126 if (!username_without_area
)
127 return pam_log_oom(handle
);
129 username
= username_without_area
;
130 area
= strdup(carea
+ 1);
132 return pam_log_oom(handle
);
135 /* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
136 * user names we don't consider valid. */
137 if (STR_IN_SET(username
, "root", NOBODY_USER_NAME
) || !valid_user_group_name(username
, 0))
138 return PAM_USER_UNKNOWN
;
140 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
141 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
142 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
143 const char *json
= NULL
;
146 /* We cache the user record in the PAM context. We use a field name that includes the username, since
147 * clients might change the user name associated with a PAM context underneath us. Notably, 'sudo'
148 * creates a single PAM context and first authenticates it with the user set to the originating user,
149 * then updates the user for the destination user and issues the session stack with the same PAM
150 * context. We thus must be prepared that the user record changes between calls and we keep any
151 * caching separate. */
152 _cleanup_free_
char *homed_field
= strjoin("systemd-home-user-record-", username
);
154 return pam_log_oom(handle
);
156 /* Let's use the cache, so that we can share it between the session and the authentication hooks */
157 r
= pam_get_data(handle
, homed_field
, (const void**) &json
);
158 if (!IN_SET(r
, PAM_SUCCESS
, PAM_NO_MODULE_DATA
))
159 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get PAM user record data: @PAMERR@");
160 if (r
== PAM_SUCCESS
&& json
) {
161 /* We determined earlier that this is not a homed user? Then exit early. (We use -1 as
162 * negative cache indicator) */
163 if (json
== POINTER_MAX
)
164 return PAM_USER_UNKNOWN
;
168 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
169 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
171 r
= pam_acquire_bus_connection(handle
, "pam-systemd-home", debug
, &bus
, bus_data
);
172 if (r
!= PAM_SUCCESS
)
175 r
= bus_call_method(bus
, bus_home_mgr
, "GetUserRecordByName", &error
, &reply
, "s", username
);
177 if (bus_error_is_unknown_service(&error
)) {
178 pam_debug_syslog(handle
, debug
,
179 "systemd-homed is not available: %s",
180 bus_error_message(&error
, r
));
184 if (sd_bus_error_has_name(&error
, BUS_ERROR_NO_SUCH_HOME
)) {
185 pam_debug_syslog(handle
, debug
,
186 "Not a user managed by systemd-homed: %s",
187 bus_error_message(&error
, r
));
191 pam_syslog(handle
, LOG_ERR
,
192 "Failed to query user record: %s", bus_error_message(&error
, r
));
193 return PAM_SERVICE_ERR
;
196 r
= sd_bus_message_read(reply
, "sbo", &json
, NULL
, NULL
);
198 return pam_bus_log_parse_error(handle
, r
);
203 r
= sd_json_parse(json
, /* flags= */ 0, &v
, NULL
, NULL
);
205 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to parse JSON user record: %m");
207 ur
= user_record_new();
209 return pam_log_oom(handle
);
211 r
= user_record_load(ur
, v
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
213 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to load user record: %m");
215 /* Safety check if cached record actually matches what we are looking for */
216 if (!user_record_matches_user_name(ur
, username
))
217 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SERVICE_ERR
,
218 "Acquired user record does not match user name.");
220 /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is
221 * going to invalidate the old version after all */
222 username
= ur
->user_name
;
224 /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary,
225 * normalized name of the user record (i.e. not an alias or so). */
226 r
= pam_set_item(handle
, PAM_USER
, ur
->user_name
);
227 if (r
!= PAM_SUCCESS
)
228 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
229 "Failed to update username PAM item to '%s': @PAMERR@", ur
->user_name
);
231 /* Everything seems to be good, let's cache this data now */
233 /* First copy: for the homed-specific data field, i.e. where we know the user record is from
235 _cleanup_free_
char *json_copy
= strdup(json
);
237 return pam_log_oom(handle
);
239 r
= pam_set_data(handle
, homed_field
, json_copy
, pam_cleanup_free
);
240 if (r
!= PAM_SUCCESS
)
241 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
242 "Failed to set PAM user record data '%s': @PAMERR@", homed_field
);
244 /* Take a second copy: for the generic data field, the one which we share with
245 * pam_systemd. While we insist on only reusing homed records, pam_systemd is fine with homed
246 * and non-homed user records. */
247 json_copy
= strdup(json
);
249 return pam_log_oom(handle
);
251 _cleanup_free_
char *generic_field
= strjoin("systemd-user-record-", username
);
253 return pam_log_oom(handle
);
255 r
= pam_set_data(handle
, generic_field
, json_copy
, pam_cleanup_free
);
256 if (r
!= PAM_SUCCESS
)
257 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
258 "Failed to set PAM user record data '%s': @PAMERR@", generic_field
);
263 /* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */
265 r
= pam_misc_setenv(handle
, "XDG_AREA", area
, /* readonly= */ 0);
266 if (r
!= PAM_SUCCESS
)
267 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
268 "Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area
);
272 *ret_record
= TAKE_PTR(ur
);
277 /* Cache this, so that we don't check again */
278 r
= pam_set_data(handle
, homed_field
, POINTER_MAX
, NULL
);
279 if (r
!= PAM_SUCCESS
)
280 pam_syslog_pam_error(handle
, LOG_ERR
, r
,
281 "Failed to set PAM user record data '%s' to invalid, ignoring: @PAMERR@",
284 return PAM_USER_UNKNOWN
;
287 static int release_user_record(pam_handle_t
*handle
, const char *username
) {
288 _cleanup_free_
char *homed_field
= NULL
, *generic_field
= NULL
;
294 homed_field
= strjoin("systemd-home-user-record-", username
);
296 return pam_log_oom(handle
);
298 r
= pam_set_data(handle
, homed_field
, NULL
, NULL
);
299 if (r
!= PAM_SUCCESS
)
300 pam_syslog_pam_error(handle
, LOG_ERR
, r
,
301 "Failed to release PAM user record data '%s': @PAMERR@", homed_field
);
303 generic_field
= strjoin("systemd-user-record-", username
);
305 return pam_log_oom(handle
);
307 k
= pam_set_data(handle
, generic_field
, NULL
, NULL
);
308 if (k
!= PAM_SUCCESS
)
309 pam_syslog_pam_error(handle
, LOG_ERR
, k
,
310 "Failed to release PAM user record data '%s': @PAMERR@", generic_field
);
312 return IN_SET(r
, PAM_SUCCESS
, PAM_NO_MODULE_DATA
) ? k
: r
;
315 static void cleanup_home_fd(pam_handle_t
*handle
, void *data
, int error_status
) {
316 safe_close(PTR_TO_FD(data
));
319 static int handle_generic_user_record_error(
320 pam_handle_t
*handle
,
321 const char *user_name
,
324 const sd_bus_error
*error
,
332 /* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
334 if (sd_bus_error_has_name(error
, BUS_ERROR_HOME_ABSENT
)) {
335 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
,
336 _("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name
);
337 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_PERM_DENIED
,
338 "Failed to acquire home for user %s: %s", user_name
, bus_error_message(error
, ret
));
340 } else if (sd_bus_error_has_name(error
, BUS_ERROR_AUTHENTICATION_LIMIT_HIT
)) {
341 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Too frequent login attempts for user %s, try again later."), user_name
);
342 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_MAXTRIES
,
343 "Failed to acquire home for user %s: %s", user_name
, bus_error_message(error
, ret
));
345 } else if (sd_bus_error_has_name(error
, BUS_ERROR_BAD_PASSWORD
)) {
346 _cleanup_(erase_and_freep
) char *newp
= NULL
;
350 /* This didn't work? Ask for an (additional?) password */
352 if (strv_isempty(secret
->password
))
353 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Password: "));
355 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password incorrect or not sufficient for authentication of user %s."), user_name
);
356 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Sorry, try again: "));
358 if (r
!= PAM_SUCCESS
)
359 return PAM_CONV_ERR
; /* no logging here */
362 pam_debug_syslog(handle
, debug
, "Password request aborted.");
363 return PAM_AUTHTOK_ERR
;
366 r
= user_record_set_password(secret
, STRV_MAKE(newp
), true);
368 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store password: %m");
370 } else if (sd_bus_error_has_name(error
, BUS_ERROR_BAD_RECOVERY_KEY
)) {
371 _cleanup_(erase_and_freep
) char *newp
= NULL
;
375 /* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */
377 if (strv_isempty(secret
->password
))
378 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Recovery key: "));
380 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name
);
381 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Sorry, reenter recovery key: "));
383 if (r
!= PAM_SUCCESS
)
384 return PAM_CONV_ERR
; /* no logging here */
387 pam_debug_syslog(handle
, debug
, "Recovery key request aborted.");
388 return PAM_AUTHTOK_ERR
;
391 r
= user_record_set_password(secret
, STRV_MAKE(newp
), true);
393 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store recovery key: %m");
395 } else if (sd_bus_error_has_name(error
, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN
)) {
396 _cleanup_(erase_and_freep
) char *newp
= NULL
;
400 if (strv_isempty(secret
->password
)) {
401 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Security token of user %s not inserted."), user_name
);
402 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Try again with password: "));
404 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name
);
405 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Try again with password: "));
407 if (r
!= PAM_SUCCESS
)
408 return PAM_CONV_ERR
; /* no logging here */
411 pam_debug_syslog(handle
, debug
, "Password request aborted.");
412 return PAM_AUTHTOK_ERR
;
415 r
= user_record_set_password(secret
, STRV_MAKE(newp
), true);
417 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store password: %m");
419 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_PIN_NEEDED
)) {
420 _cleanup_(erase_and_freep
) char *newp
= NULL
;
424 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Security token PIN: "));
425 if (r
!= PAM_SUCCESS
)
426 return PAM_CONV_ERR
; /* no logging here */
429 pam_debug_syslog(handle
, debug
, "PIN request aborted.");
430 return PAM_AUTHTOK_ERR
;
433 r
= user_record_set_token_pin(secret
, STRV_MAKE(newp
), false);
435 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store PIN: %m");
437 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED
)) {
441 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Please authenticate physically on security token of user %s."), user_name
);
443 r
= user_record_set_pkcs11_protected_authentication_path_permitted(secret
, true);
445 return pam_syslog_errno(handle
, LOG_ERR
, r
,
446 "Failed to set PKCS#11 protected authentication path permitted flag: %m");
448 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED
)) {
452 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Please confirm presence on security token of user %s."), user_name
);
454 r
= user_record_set_fido2_user_presence_permitted(secret
, true);
456 return pam_syslog_errno(handle
, LOG_ERR
, r
,
457 "Failed to set FIDO2 user presence permitted flag: %m");
459 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_USER_VERIFICATION_NEEDED
)) {
463 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Please verify user on security token of user %s."), user_name
);
465 r
= user_record_set_fido2_user_verification_permitted(secret
, true);
467 return pam_syslog_errno(handle
, LOG_ERR
, r
,
468 "Failed to set FIDO2 user verification permitted flag: %m");
470 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_PIN_LOCKED
)) {
472 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
473 return PAM_SERVICE_ERR
;
475 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_BAD_PIN
)) {
476 _cleanup_(erase_and_freep
) char *newp
= NULL
;
480 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Security token PIN incorrect for user %s."), user_name
);
481 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Sorry, retry security token PIN: "));
482 if (r
!= PAM_SUCCESS
)
483 return PAM_CONV_ERR
; /* no logging here */
486 pam_debug_syslog(handle
, debug
, "PIN request aborted.");
487 return PAM_AUTHTOK_ERR
;
490 r
= user_record_set_token_pin(secret
, STRV_MAKE(newp
), false);
492 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store PIN: %m");
494 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT
)) {
495 _cleanup_(erase_and_freep
) char *newp
= NULL
;
499 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name
);
500 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Sorry, retry security token PIN: "));
501 if (r
!= PAM_SUCCESS
)
502 return PAM_CONV_ERR
; /* no logging here */
505 pam_debug_syslog(handle
, debug
, "PIN request aborted.");
506 return PAM_AUTHTOK_ERR
;
509 r
= user_record_set_token_pin(secret
, STRV_MAKE(newp
), false);
511 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store PIN: %m");
513 } else if (sd_bus_error_has_name(error
, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT
)) {
514 _cleanup_(erase_and_freep
) char *newp
= NULL
;
518 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Security token PIN of user %s incorrect (only one try left!)"), user_name
);
519 r
= pam_prompt_graceful(handle
, PAM_PROMPT_ECHO_OFF
, &newp
, _("Sorry, retry security token PIN: "));
520 if (r
!= PAM_SUCCESS
)
521 return PAM_CONV_ERR
; /* no logging here */
524 pam_debug_syslog(handle
, debug
, "PIN request aborted.");
525 return PAM_AUTHTOK_ERR
;
528 r
= user_record_set_token_pin(secret
, STRV_MAKE(newp
), false);
530 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store PIN: %m");
533 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SERVICE_ERR
,
534 "Failed to acquire home for user %s: %s", user_name
, bus_error_message(error
, ret
));
539 static int acquire_home(
540 pam_handle_t
*handle
,
541 AcquireHomeFlags flags
,
543 PamBusData
**bus_data
) {
545 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
, *secret
= NULL
;
546 bool do_auth
= FLAGS_SET(flags
, ACQUIRE_MUST_AUTHENTICATE
), home_not_active
= false, home_locked
= false, unrestricted
= false;
547 _cleanup_close_
int acquired_fd
= -EBADF
;
548 _cleanup_free_
char *fd_field
= NULL
;
549 const void *home_fd_ptr
= NULL
;
550 const char *username
= NULL
;
551 unsigned n_attempts
= 0;
556 /* This acquires a reference to a home directory in the following ways:
558 * 1. If ACQUIRE_MUST_AUTHENTICATE is not set, it tries to call RefHome() first — which will get us a
559 * reference to the home without authentication (which will work for homes that are not encrypted,
560 * or that already are activated). If this works, we are done. Yay!
562 * 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a
563 * reference. If this works, we are done. Yay!
565 * 3. if ACQUIRE_REF_ANYWAY is set, we'll call RefHomeUnrestricted() — which will give us a reference
566 * in any case (even if the activation failed!).
568 * The idea is that ACQUIRE_MUST_AUTHENTICATE is off for the PAM session hooks (since for those
569 * authentication doesn't matter), and on for the PAM authentication hooks (since for those
570 * authentication is essential). And ACQUIRE_REF_ANYWAY should be set if we are pretty sure that we
571 * can later activate the home directory via our fallback shell logic, and hence are OK if we can't
572 * activate things here. Usecase for that are SSH logins where SSH does the authentication and thus
573 * only the session hooks are called. But from the session hooks SSH doesn't allow asking questions,
574 * hence we simply allow the login attempt to continue but then invoke our fallback shell that will
575 * prompt the user for the missing unlock credentials, and then chainload the real shell.
578 r
= pam_get_user(handle
, &username
, NULL
);
579 if (r
!= PAM_SUCCESS
)
580 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get user name: @PAMERR@");
581 if (isempty(username
))
582 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SERVICE_ERR
, "User name not set.");
584 /* If we already have acquired the fd, let's shortcut this */
585 fd_field
= strjoin("systemd-home-fd-", username
);
587 return pam_log_oom(handle
);
589 r
= pam_get_data(handle
, fd_field
, &home_fd_ptr
);
590 if (!IN_SET(r
, PAM_SUCCESS
, PAM_NO_MODULE_DATA
))
591 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
592 "Failed to retrieve PAM home reference fd: @PAMERR@");
593 if (r
== PAM_SUCCESS
&& PTR_TO_FD(home_fd_ptr
) >= 0)
596 r
= acquire_user_record(handle
, username
, debug
, &ur
, bus_data
);
597 if (r
!= PAM_SUCCESS
)
600 /* Implement our own retry loop here instead of relying on the PAM client's one. That's because it
601 * might happen that the record we stored on the host does not match the encryption password of the
602 * LUKS image in case the image was used in a different system where the password was changed. In
603 * that case it will happen that the LUKS password and the host password are different, and we handle
604 * that by collecting and passing multiple passwords in that case. Hence we treat bad passwords as a
605 * request to collect one more password and pass the new and all previously used passwords again. */
607 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
608 r
= pam_acquire_bus_connection(handle
, "pam-systemd-home", debug
, &bus
, bus_data
);
609 if (r
!= PAM_SUCCESS
)
613 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
, *reply
= NULL
;
614 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
615 const char *method
= NULL
;
617 if (do_auth
&& !secret
) {
618 const char *cached_password
= NULL
;
620 secret
= user_record_new();
622 return pam_log_oom(handle
);
624 /* If there's already a cached password, use it. But if not let's authenticate
625 * without anything, maybe some other authentication mechanism systemd-homed
626 * implements (such as PKCS#11) allows us to authenticate without anything else. */
627 r
= pam_get_item(handle
, PAM_AUTHTOK
, (const void**) &cached_password
);
628 if (!IN_SET(r
, PAM_BAD_ITEM
, PAM_SUCCESS
))
629 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
630 "Failed to get cached password: @PAMERR@");
632 if (!isempty(cached_password
)) {
633 r
= user_record_set_password(secret
, STRV_MAKE(cached_password
), true);
635 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store password: %m");
640 method
= "AcquireHome"; /* If we shall authenticate no matter what */
641 else if (unrestricted
)
642 method
= "RefHomeUnrestricted"; /* If we shall get a ref no matter what */
644 method
= "RefHome"; /* If we shall get a ref (if possible) */
646 r
= bus_message_new_method_call(bus
, &m
, bus_home_mgr
, method
);
648 return pam_bus_log_create_error(handle
, r
);
650 r
= sd_bus_message_append(m
, "s", ur
->user_name
);
652 return pam_bus_log_create_error(handle
, r
);
655 r
= bus_message_append_secret(m
, secret
);
657 return pam_bus_log_create_error(handle
, r
);
660 r
= sd_bus_message_append(m
, "b", FLAGS_SET(flags
, ACQUIRE_PLEASE_SUSPEND
));
662 return pam_bus_log_create_error(handle
, r
);
664 r
= sd_bus_call(bus
, m
, HOME_SLOW_BUS_CALL_TIMEOUT_USEC
, &error
, &reply
);
666 if (sd_bus_error_has_names(&error
, BUS_ERROR_HOME_NOT_ACTIVE
, BUS_ERROR_HOME_BUSY
)) {
667 /* Only on RefHome(): We can't access the home directory currently, unless
668 * it's unlocked with a password. Hence, let's try this again, this time with
670 home_not_active
= true;
672 } else if (sd_bus_error_has_name(&error
, BUS_ERROR_HOME_LOCKED
)) {
673 home_locked
= true; /* Similar */
676 r
= handle_generic_user_record_error(handle
, ur
->user_name
, secret
, r
, &error
, debug
);
677 if (r
== PAM_CONV_ERR
) {
678 /* Password/PIN prompts will fail in certain environments, for example when
679 * we are called from OpenSSH's account or session hooks, or in systemd's
680 * per-service PAM logic. In that case, print a friendly message and accept
683 if (!FLAGS_SET(flags
, ACQUIRE_REF_ANYWAY
)) {
685 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Home of user %s is currently not active, please log in locally first."), ur
->user_name
);
687 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Home of user %s is currently locked, please unlock locally first."), ur
->user_name
);
689 if (FLAGS_SET(flags
, ACQUIRE_MUST_AUTHENTICATE
) || debug
)
690 pam_syslog(handle
, FLAGS_SET(flags
, ACQUIRE_MUST_AUTHENTICATE
) ? LOG_ERR
: LOG_DEBUG
, "Failed to prompt for password/prompt.");
692 return home_not_active
|| home_locked
? PAM_PERM_DENIED
: PAM_CONV_ERR
;
695 /* ref_anyway is true, hence let's now get a ref no matter what. */
698 } else if (r
!= PAM_SUCCESS
)
701 do_auth
= true; /* The issue was dealt with, some more information was collected. Let's try to authenticate, again. */
706 r
= sd_bus_message_read(reply
, "h", &fd
);
708 return pam_bus_log_parse_error(handle
, r
);
710 acquired_fd
= fcntl(fd
, F_DUPFD_CLOEXEC
, 3);
712 return pam_syslog_errno(handle
, LOG_ERR
, errno
,
713 "Failed to duplicate acquired fd: %m");
717 if (++n_attempts
>= 5) {
718 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
,
719 _("Too many unsuccessful login attempts for user %s, refusing."), ur
->user_name
);
720 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_MAXTRIES
,
721 "Failed to acquire home for user %s: %s", ur
->user_name
, bus_error_message(&error
, r
));
725 /* Later PAM modules may need the auth token, but only during pam_authenticate. */
726 if (FLAGS_SET(flags
, ACQUIRE_MUST_AUTHENTICATE
) && !strv_isempty(secret
->password
)) {
727 r
= pam_set_item(handle
, PAM_AUTHTOK
, *secret
->password
);
728 if (r
!= PAM_SUCCESS
)
729 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to set PAM auth token: @PAMERR@");
732 r
= pam_set_data(handle
, fd_field
, FD_TO_PTR(acquired_fd
), cleanup_home_fd
);
733 if (r
!= PAM_SUCCESS
)
734 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to set PAM bus data: @PAMERR@");
735 TAKE_FD(acquired_fd
);
738 /* We likely just activated the home directory, let's flush out the user record, since a
739 * newer embedded user record might have been acquired from the activation. */
741 r
= release_user_record(handle
, ur
->user_name
);
742 if (!IN_SET(r
, PAM_SUCCESS
, PAM_NO_MODULE_DATA
))
746 /* If we didn't actually manage to unlock the home directory, then we rely on the fallback-shell to
747 * unlock it for us. But until that happens we don't want that logind spawns the per-user service
748 * manager for us (since it would see an inaccessible home directory). Hence set an environment
749 * variable that pam_systemd looks for). */
751 r
= pam_putenv(handle
, "XDG_SESSION_INCOMPLETE=1");
752 if (r
!= PAM_SUCCESS
)
753 return pam_syslog_pam_error(handle
, LOG_WARNING
, r
, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@");
755 pam_syslog(handle
, LOG_NOTICE
, "Home for user %s acquired in incomplete mode, requires later activation.", ur
->user_name
);
757 pam_syslog(handle
, LOG_NOTICE
, "Home for user %s successfully acquired.", ur
->user_name
);
762 static int release_home_fd(pam_handle_t
*handle
, const char *username
) {
763 _cleanup_free_
char *fd_field
= NULL
;
764 const void *home_fd_ptr
= NULL
;
770 fd_field
= strjoin("systemd-home-fd-", username
);
772 return pam_log_oom(handle
);
774 r
= pam_get_data(handle
, fd_field
, &home_fd_ptr
);
775 if (r
== PAM_NO_MODULE_DATA
|| (r
== PAM_SUCCESS
&& PTR_TO_FD(home_fd_ptr
) < 0))
776 return PAM_NO_MODULE_DATA
;
777 if (r
!= PAM_SUCCESS
)
778 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to retrieve PAM home reference fd: @PAMERR@");
780 r
= pam_set_data(handle
, fd_field
, NULL
, NULL
);
781 if (r
!= PAM_SUCCESS
)
782 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to release PAM home reference fd: @PAMERR@");
787 _public_ PAM_EXTERN
int pam_sm_authenticate(
788 pam_handle_t
*handle
,
790 int argc
, const char **argv
) {
792 AcquireHomeFlags flags
= 0;
797 if (parse_env(handle
, &flags
) < 0)
800 if (parse_argv(handle
,
806 pam_debug_syslog(handle
, debug
, "pam-systemd-homed authenticating");
808 return acquire_home(handle
, ACQUIRE_MUST_AUTHENTICATE
|flags
, debug
, /* bus_data= */ NULL
);
811 _public_ PAM_EXTERN
int pam_sm_setcred(pam_handle_t
*pamh
, int sm_flags
, int argc
, const char **argv
) {
815 static int fallback_shell_can_work(
816 pam_handle_t
*handle
,
817 AcquireHomeFlags
*flags
) {
819 const char *tty
= NULL
, *display
= NULL
;
825 r
= pam_get_item_many(
828 PAM_XDISPLAY
, &display
);
829 if (r
!= PAM_SUCCESS
)
830 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get PAM items: @PAMERR@");
832 /* The fallback shell logic only works on TTY logins, hence only allow it if there's no X11 display
833 * set, and a TTY field is set that is neither "cron" (which is what crond sets, god knows why) not
834 * contains a colon (which is what various graphical X11 logins do). Note that ssh sets the tty to
835 * "ssh" here, which we allow (I mean, ssh is after all the primary reason we do all this). */
836 if (isempty(display
) &&
840 *flags
|= ACQUIRE_REF_ANYWAY
; /* Allow login even if we can only ref, not activate */
845 _public_ PAM_EXTERN
int pam_sm_open_session(
846 pam_handle_t
*handle
,
848 int argc
, const char **argv
) {
850 /* Let's release the D-Bus connection once this function exits, after all the session might live
851 * quite a long time, and we are not going to process the bus connection in that time, so let's
852 * better close before the daemon kicks us off because we are not processing anything. */
853 _cleanup_(pam_bus_data_disconnectp
) PamBusData
*d
= NULL
;
854 AcquireHomeFlags flags
= 0;
860 if (parse_env(handle
, &flags
) < 0)
861 return PAM_SESSION_ERR
;
863 if (parse_argv(handle
,
867 return PAM_SESSION_ERR
;
869 pam_debug_syslog(handle
, debug
, "pam-systemd-homed session start");
871 r
= fallback_shell_can_work(handle
, &flags
);
872 if (r
!= PAM_SUCCESS
)
875 /* Explicitly get saved PamBusData here. Otherwise, this function may succeed without setting 'd'
876 * even if there is an opened sd-bus connection, and it will be leaked. See issue #31375. */
877 r
= pam_get_bus_data(handle
, "pam-systemd-home", &d
);
878 if (r
!= PAM_SUCCESS
)
881 r
= acquire_home(handle
, flags
, debug
, &d
);
882 if (r
== PAM_USER_UNKNOWN
) /* Not managed by us? Don't complain. */
884 if (r
!= PAM_SUCCESS
)
887 r
= pam_putenv(handle
, "SYSTEMD_HOME=1");
888 if (r
!= PAM_SUCCESS
)
889 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
890 "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@");
892 r
= pam_putenv(handle
, FLAGS_SET(flags
, ACQUIRE_PLEASE_SUSPEND
) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
893 if (r
!= PAM_SUCCESS
)
894 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
895 "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
900 _public_ PAM_EXTERN
int pam_sm_close_session(
901 pam_handle_t
*handle
,
903 int argc
, const char **argv
) {
905 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
906 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
907 const char *username
= NULL
;
913 if (parse_argv(handle
,
917 return PAM_SESSION_ERR
;
919 pam_debug_syslog(handle
, debug
, "pam-systemd-homed session end");
921 r
= pam_get_user(handle
, &username
, NULL
);
922 if (r
!= PAM_SUCCESS
)
923 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get user name: @PAMERR@");
924 if (isempty(username
))
925 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SERVICE_ERR
, "User name not set.");
927 /* Let's explicitly drop the reference to the homed session, so that the subsequent ReleaseHome()
928 * call will be able to do its thing. */
929 r
= release_home_fd(handle
, username
);
930 if (r
== PAM_NO_MODULE_DATA
) /* Nothing to do, we never acquired an fd */
932 if (r
!= PAM_SUCCESS
)
935 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
936 r
= pam_acquire_bus_connection(handle
, "pam-systemd-home", debug
, &bus
, NULL
);
937 if (r
!= PAM_SUCCESS
)
940 r
= bus_message_new_method_call(bus
, &m
, bus_home_mgr
, "ReleaseHome");
942 return pam_bus_log_create_error(handle
, r
);
944 r
= sd_bus_message_append(m
, "s", username
);
946 return pam_bus_log_create_error(handle
, r
);
948 r
= sd_bus_call(bus
, m
, HOME_SLOW_BUS_CALL_TIMEOUT_USEC
, &error
, NULL
);
950 if (!sd_bus_error_has_name(&error
, BUS_ERROR_HOME_BUSY
))
951 return pam_syslog_pam_error(handle
, LOG_ERR
, PAM_SESSION_ERR
,
952 "Failed to release user home: %s", bus_error_message(&error
, r
));
954 pam_syslog(handle
, LOG_NOTICE
, "Not deactivating home directory of %s, as it is still used.", username
);
960 _public_ PAM_EXTERN
int pam_sm_acct_mgmt(
961 pam_handle_t
*handle
,
966 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
967 AcquireHomeFlags flags
= 0;
974 if (parse_env(handle
, &flags
) < 0)
977 if (parse_argv(handle
,
983 pam_debug_syslog(handle
, debug
, "pam-systemd-homed account management");
985 r
= fallback_shell_can_work(handle
, &flags
);
986 if (r
!= PAM_SUCCESS
)
989 r
= acquire_home(handle
, flags
, debug
, /* bus_data= */ NULL
);
990 if (r
!= PAM_SUCCESS
)
993 r
= acquire_user_record(handle
, /* username= */ NULL
, debug
, &ur
, /* bus_data= */ NULL
);
994 if (r
!= PAM_SUCCESS
)
997 r
= user_record_test_blocked(ur
);
1001 pam_syslog(handle
, LOG_WARNING
, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur
->user_name
);
1005 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("User record is blocked, prohibiting access."));
1006 return PAM_ACCT_EXPIRED
;
1009 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("User record is not valid yet, prohibiting access."));
1010 return PAM_ACCT_EXPIRED
;
1013 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("User record is not valid anymore, prohibiting access."));
1014 return PAM_ACCT_EXPIRED
;
1018 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("User record not valid, prohibiting access."));
1019 return PAM_ACCT_EXPIRED
;
1023 t
= user_record_ratelimit_next_try(ur
);
1024 if (t
!= USEC_INFINITY
) {
1025 usec_t n
= now(CLOCK_REALTIME
);
1028 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Too many logins, try again in %s."),
1029 FORMAT_TIMESPAN(t
- n
, USEC_PER_SEC
));
1031 return PAM_MAXTRIES
;
1035 r
= user_record_test_password_change_required(ur
);
1039 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password change required."));
1040 return PAM_NEW_AUTHTOK_REQD
;
1043 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password expired, change required."));
1044 return PAM_NEW_AUTHTOK_REQD
;
1046 /* Strictly speaking this is only about password expiration, and we might want to allow
1047 * authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
1049 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password is expired, but can't change, refusing login."));
1050 return PAM_AUTHTOK_EXPIRED
;
1053 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("Password will expire soon, please change."));
1057 /* If the system clock is wrong, let's log but continue */
1058 pam_syslog(handle
, LOG_WARNING
, "Couldn't check if password change is required, last change is in the future, system clock likely wrong.");
1062 /* All good, just means the password if we wanted to change we couldn't, but we don't need to */
1067 (void) pam_prompt_graceful(handle
, PAM_ERROR_MSG
, NULL
, _("User record not valid, prohibiting access."));
1068 return PAM_AUTHTOK_EXPIRED
;
1075 _public_ PAM_EXTERN
int pam_sm_chauthtok(
1076 pam_handle_t
*handle
,
1079 const char **argv
) {
1081 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
, *old_secret
= NULL
, *new_secret
= NULL
;
1082 const char *old_password
= NULL
, *new_password
= NULL
;
1083 unsigned n_attempts
= 0;
1089 if (parse_argv(handle
,
1093 return PAM_AUTH_ERR
;
1095 pam_debug_syslog(handle
, debug
, "pam-systemd-homed account management");
1097 r
= acquire_user_record(handle
, /* username= */ NULL
, debug
, &ur
, /* bus_data= */ NULL
);
1098 if (r
!= PAM_SUCCESS
)
1101 /* Start with cached credentials */
1102 r
= pam_get_item_many(
1104 PAM_OLDAUTHTOK
, &old_password
,
1105 PAM_AUTHTOK
, &new_password
);
1106 if (r
!= PAM_SUCCESS
)
1107 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get cached passwords: @PAMERR@");
1109 if (isempty(new_password
)) {
1110 /* No, it's not cached, then let's ask for the password and its verification, and cache
1113 r
= pam_get_authtok_noverify(handle
, &new_password
, "New password: ");
1114 if (r
!= PAM_SUCCESS
)
1115 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get new password: @PAMERR@");
1117 if (isempty(new_password
)) {
1118 pam_debug_syslog(handle
, debug
, "Password request aborted.");
1119 return PAM_AUTHTOK_ERR
;
1122 r
= pam_get_authtok_verify(handle
, &new_password
, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */
1123 if (r
!= PAM_SUCCESS
)
1124 return pam_syslog_pam_error(handle
, LOG_ERR
, r
, "Failed to get password again: @PAMERR@");
1126 // FIXME: pam_pwquality will ask for the password a third time. It really shouldn't do
1127 // that, and instead assume the password was already verified once when it is found to be
1128 // cached already. needs to be fixed in pam_pwquality
1131 /* Now everything is cached and checked, let's exit from the preliminary check */
1132 if (FLAGS_SET(sm_flags
, PAM_PRELIM_CHECK
))
1135 old_secret
= user_record_new();
1137 return pam_log_oom(handle
);
1139 if (!isempty(old_password
)) {
1140 r
= user_record_set_password(old_secret
, STRV_MAKE(old_password
), true);
1142 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store old password: %m");
1145 new_secret
= user_record_new();
1147 return pam_log_oom(handle
);
1149 r
= user_record_set_password(new_secret
, STRV_MAKE(new_password
), true);
1151 return pam_syslog_errno(handle
, LOG_ERR
, r
, "Failed to store new password: %m");
1153 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
1154 r
= pam_acquire_bus_connection(handle
, "pam-systemd-home", debug
, &bus
, NULL
);
1155 if (r
!= PAM_SUCCESS
)
1159 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1160 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
1162 r
= bus_message_new_method_call(bus
, &m
, bus_home_mgr
, "ChangePasswordHome");
1164 return pam_bus_log_create_error(handle
, r
);
1166 r
= sd_bus_message_append(m
, "s", ur
->user_name
);
1168 return pam_bus_log_create_error(handle
, r
);
1170 r
= bus_message_append_secret(m
, new_secret
);
1172 return pam_bus_log_create_error(handle
, r
);
1174 r
= bus_message_append_secret(m
, old_secret
);
1176 return pam_bus_log_create_error(handle
, r
);
1178 r
= sd_bus_call(bus
, m
, HOME_SLOW_BUS_CALL_TIMEOUT_USEC
, &error
, NULL
);
1180 r
= handle_generic_user_record_error(handle
, ur
->user_name
, old_secret
, r
, &error
, debug
);
1181 if (r
== PAM_CONV_ERR
)
1182 return pam_syslog_pam_error(handle
, LOG_ERR
, r
,
1183 "Failed to prompt for password/prompt.");
1184 if (r
!= PAM_SUCCESS
)
1187 return pam_syslog_pam_error(handle
, LOG_NOTICE
, PAM_SUCCESS
,
1188 "Successfully changed password for user %s.", ur
->user_name
);
1190 if (++n_attempts
>= 5)
1196 return pam_syslog_pam_error(handle
, LOG_NOTICE
, PAM_MAXTRIES
,
1197 "Failed to change password for user %s: @PAMERR@", ur
->user_name
);