diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /src/home/pam_systemd_home.c | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/home/pam_systemd_home.c')
-rw-r--r-- | src/home/pam_systemd_home.c | 333 |
1 files changed, 215 insertions, 118 deletions
diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index ba8d8f6..4616f08 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -20,10 +20,16 @@ #include "user-record.h" #include "user-util.h" +typedef enum AcquireHomeFlags { + ACQUIRE_MUST_AUTHENTICATE = 1 << 0, + ACQUIRE_PLEASE_SUSPEND = 1 << 1, + ACQUIRE_REF_ANYWAY = 1 << 2, +} AcquireHomeFlags; + static int parse_argv( pam_handle_t *handle, int argc, const char **argv, - bool *please_suspend, + AcquireHomeFlags *flags, bool *debug) { assert(argc >= 0); @@ -38,8 +44,8 @@ static int parse_argv( k = parse_boolean(v); if (k < 0) pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); - else if (please_suspend) - *please_suspend = k; + else if (flags) + SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k); } else if (streq(argv[i], "debug")) { if (debug) @@ -62,7 +68,7 @@ static int parse_argv( static int parse_env( pam_handle_t *handle, - bool *please_suspend) { + AcquireHomeFlags *flags) { const char *v; int r; @@ -83,8 +89,8 @@ static int parse_env( r = parse_boolean(v); if (r < 0) pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); - else if (please_suspend) - *please_suspend = r; + else if (flags) + SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r); return 0; } @@ -99,7 +105,6 @@ static int acquire_user_record( _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *homed_field = NULL; const char *json = NULL; int r; @@ -142,6 +147,7 @@ static int acquire_user_record( } else { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *generic_field = NULL, *json_copy = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); if (r != PAM_SUCCESS) @@ -275,21 +281,21 @@ static int handle_generic_user_record_error( const sd_bus_error *error, bool debug) { + int r; + assert(user_name); assert(error); - int r; - /* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */ if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret)); } else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret)); @@ -301,10 +307,10 @@ static int handle_generic_user_record_error( /* This didn't work? Ask for an (additional?) password */ if (strv_isempty(secret->password)) - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: ")); else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -326,10 +332,10 @@ static int handle_generic_user_record_error( /* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */ if (strv_isempty(secret->password)) - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: ")); else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -349,11 +355,11 @@ static int handle_generic_user_record_error( assert(secret); if (strv_isempty(secret->password)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); } else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); + (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); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -363,7 +369,6 @@ static int handle_generic_user_record_error( return PAM_AUTHTOK_ERR; } - r = user_record_set_password(secret, STRV_MAKE(newp), true); if (r < 0) return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m"); @@ -373,7 +378,7 @@ static int handle_generic_user_record_error( assert(secret); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -390,7 +395,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name); r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true); if (r < 0) @@ -401,7 +406,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name); r = user_record_set_fido2_user_presence_permitted(secret, true); if (r < 0) @@ -412,7 +417,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name); r = user_record_set_fido2_user_verification_permitted(secret, true); if (r < 0) @@ -421,7 +426,7 @@ static int handle_generic_user_record_error( } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)")); + (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.)")); return PAM_SERVICE_ERR; } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) { @@ -429,8 +434,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -448,8 +453,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -467,8 +472,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -490,14 +495,12 @@ static int handle_generic_user_record_error( static int acquire_home( pam_handle_t *handle, - bool please_authenticate, - bool please_suspend, + AcquireHomeFlags flags, bool debug, PamBusData **bus_data) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL; - bool do_auth = please_authenticate, home_not_active = false, home_locked = false; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false, unrestricted = false; _cleanup_close_ int acquired_fd = -EBADF; _cleanup_free_ char *fd_field = NULL; const void *home_fd_ptr = NULL; @@ -507,13 +510,27 @@ static int acquire_home( assert(handle); - /* This acquires a reference to a home directory in one of two ways: if please_authenticate is true, - * then we'll call AcquireHome() after asking the user for a password. Otherwise it tries to call - * RefHome() and if that fails queries the user for a password and uses AcquireHome(). + /* This acquires a reference to a home directory in the following ways: + * + * 1. If please_authenticate is false, it tries to call RefHome() first — which + * will get us a reference to the home without authentication (which will work for homes that are + * not encrypted, or that already are activated). If this works, we are done. Yay! * - * The idea is that the PAM authentication hook sets please_authenticate and thus always - * authenticates, while the other PAM hooks unset it so that they can a ref of their own without - * authentication if possible, but with authentication if necessary. */ + * 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a + * reference. If this works, we are done. Yay! + * + * 3. if ref_anyway, we'll call RefHomeUnrestricted() — which will give us a reference in any case + * (even if the activation failed!). + * + * The idea is that please_authenticate is set to false for the PAM session hooks (since for those + * authentication doesn't matter), and true for the PAM authentication hooks (since for those + * authentication is essential). And ref_anyway should be set if we are pretty sure that we can later + * activate the home directory via our fallback shell logic, and hence are OK if we can't activate + * things here. Usecase for that are SSH logins where SSH does the authentication and thus only the + * session hooks are called. But from the session hooks SSH doesn't allow asking questions, hence we + * simply allow the login attempt to continue but then invoke our fallback shell that will prompt the + * user for the missing unlock credentials, and then chainload the real shell. + */ r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) @@ -534,25 +551,26 @@ static int acquire_home( if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0) return PAM_SUCCESS; - r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); - if (r != PAM_SUCCESS) - return r; - r = acquire_user_record(handle, username, debug, &ur, bus_data); if (r != PAM_SUCCESS) return r; /* Implement our own retry loop here instead of relying on the PAM client's one. That's because it - * might happen that the record we stored on the host does not match the encryption password of - * the LUKS image in case the image was used in a different system where the password was - * changed. In that case it will happen that the LUKS password and the host password are - * different, and we handle that by collecting and passing multiple passwords in that case. Hence we - * treat bad passwords as a request to collect one more password and pass the new all all previously - * used passwords again. */ + * might happen that the record we stored on the host does not match the encryption password of the + * LUKS image in case the image was used in a different system where the password was changed. In + * that case it will happen that the LUKS password and the host password are different, and we handle + * that by collecting and passing multiple passwords in that case. Hence we treat bad passwords as a + * request to collect one more password and pass the new and all previously used passwords again. */ + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); + if (r != PAM_SUCCESS) + return r; for (;;) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method = NULL; if (do_auth && !secret) { const char *cached_password = NULL; @@ -576,7 +594,14 @@ static int acquire_home( } } - r = bus_message_new_method_call(bus, &m, bus_home_mgr, do_auth ? "AcquireHome" : "RefHome"); + if (do_auth) + method = "AcquireHome"; /* If we shall authenticate no matter what */ + else if (unrestricted) + method = "RefHomeUnrestricted"; /* If we shall get a ref no matter what */ + else + method = "RefHome"; /* If we shall get a ref (if possible) */ + + r = bus_message_new_method_call(bus, &m, bus_home_mgr, method); if (r < 0) return pam_bus_log_create_error(handle, r); @@ -590,21 +615,22 @@ static int acquire_home( return pam_bus_log_create_error(handle, r); } - r = sd_bus_message_append(m, "b", please_suspend); + r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND)); if (r < 0) return pam_bus_log_create_error(handle, r); r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); if (r < 0) { - - if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_ACTIVE)) + if (sd_bus_error_has_names(&error, BUS_ERROR_HOME_NOT_ACTIVE, BUS_ERROR_HOME_BUSY)) { /* Only on RefHome(): We can't access the home directory currently, unless * it's unlocked with a password. Hence, let's try this again, this time with * authentication. */ home_not_active = true; - else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) + do_auth = true; + } else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) { home_locked = true; /* Similar */ - else { + do_auth = true; + } else { r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error, debug); if (r == PAM_CONV_ERR) { /* Password/PIN prompts will fail in certain environments, for example when @@ -612,20 +638,26 @@ static int acquire_home( * per-service PAM logic. In that case, print a friendly message and accept * failure. */ - if (home_not_active) - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name); - if (home_locked) - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); + if (!FLAGS_SET(flags, ACQUIRE_REF_ANYWAY)) { + if (home_not_active) + (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); + if (home_locked) + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); + + if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug) + pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt."); - if (please_authenticate || debug) - pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt."); + return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR; + } - return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR; - } - if (r != PAM_SUCCESS) + /* ref_anyway is true, hence let's now get a ref no matter what. */ + unrestricted = true; + do_auth = false; + } else if (r != PAM_SUCCESS) return r; + else + do_auth = true; /* The issue was dealt with, some more information was collected. Let's try to authenticate, again. */ } - } else { int fd; @@ -641,18 +673,15 @@ static int acquire_home( } if (++n_attempts >= 5) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES, "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r)); } - - /* Try again, this time with authentication if we didn't do that before. */ - do_auth = true; } /* Later PAM modules may need the auth token, but only during pam_authenticate. */ - if (please_authenticate && !strv_isempty(secret->password)) { + if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) { r = pam_set_item(handle, PAM_AUTHTOK, *secret->password); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); @@ -672,7 +701,19 @@ static int acquire_home( return r; } - pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + /* If we didn't actually manage to unlock the home directory, then we rely on the fallback-shell to + * unlock it for us. But until that happens we don't want that logind spawns the per-user service + * manager for us (since it would see an inaccessible home directory). Hence set an environment + * variable that pam_systemd looks for). */ + if (unrestricted) { + r = pam_putenv(handle, "XDG_SESSION_INCOMPLETE=1"); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@"); + + pam_syslog(handle, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); + } else + pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + return PAM_SUCCESS; } @@ -703,53 +744,99 @@ static int release_home_fd(pam_handle_t *handle, const char *username) { _public_ PAM_EXTERN int pam_sm_authenticate( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { - bool debug = false, suspend_please = false; + AcquireHomeFlags flags = 0; + bool debug = false; - if (parse_env(handle, &suspend_please) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_AUTH_ERR; if (parse_argv(handle, argc, argv, - &suspend_please, + &flags, &debug) < 0) return PAM_AUTH_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating"); - return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL); + return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL); } -_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { +_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) { + return PAM_SUCCESS; +} + +static int fallback_shell_can_work( + pam_handle_t *handle, + AcquireHomeFlags *flags) { + + const char *tty = NULL, *display = NULL; + int r; + + assert(handle); + assert(flags); + + r = pam_get_item_many( + handle, + PAM_TTY, &tty, + PAM_XDISPLAY, &display); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); + + /* The fallback shell logic only works on TTY logins, hence only allow it if there's no X11 display + * set, and a TTY field is set that is neither "cron" (which is what crond sets, god knows why) not + * contains a colon (which is what various graphical X11 logins do). Note that ssh sets the tty to + * "ssh" here, which we allow (I mean, ssh is after all the primary reason we do all this). */ + if (isempty(display) && + tty && + !strchr(tty, ':') && + !streq(tty, "cron")) + *flags |= ACQUIRE_REF_ANYWAY; /* Allow login even if we can only ref, not activate */ + return PAM_SUCCESS; } _public_ PAM_EXTERN int pam_sm_open_session( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { /* Let's release the D-Bus connection once this function exits, after all the session might live * quite a long time, and we are not going to process the bus connection in that time, so let's * better close before the daemon kicks us off because we are not processing anything. */ _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; - bool debug = false, suspend_please = false; + AcquireHomeFlags flags = 0; + bool debug = false; int r; - if (parse_env(handle, &suspend_please) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_SESSION_ERR; if (parse_argv(handle, argc, argv, - &suspend_please, + &flags, &debug) < 0) return PAM_SESSION_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed session start"); - r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d); + r = fallback_shell_can_work(handle, &flags); + if (r != PAM_SUCCESS) + return r; + + /* Explicitly get saved PamBusData here. Otherwise, this function may succeed without setting 'd' + * even if there is an opened sd-bus connection, and it will be leaked. See issue #31375. */ + r = pam_get_bus_data(handle, "pam-systemd-home", &d); + if (r != PAM_SUCCESS) + return r; + + r = acquire_home(handle, flags, debug, &d); if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */ return PAM_SUCCESS; if (r != PAM_SUCCESS) @@ -760,7 +847,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@"); - r = pam_putenv(handle, suspend_please ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); + r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@"); @@ -770,16 +857,17 @@ _public_ PAM_EXTERN int pam_sm_open_session( _public_ PAM_EXTERN int pam_sm_close_session( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; const char *username = NULL; bool debug = false; int r; + pam_log_setup(); + if (parse_argv(handle, argc, argv, NULL, @@ -803,6 +891,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (r != PAM_SUCCESS) return r; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); if (r != PAM_SUCCESS) return r; @@ -829,27 +918,34 @@ _public_ PAM_EXTERN int pam_sm_close_session( _public_ PAM_EXTERN int pam_sm_acct_mgmt( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - bool debug = false, please_suspend = false; + AcquireHomeFlags flags = 0; + bool debug = false; usec_t t; int r; - if (parse_env(handle, &please_suspend) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_AUTH_ERR; if (parse_argv(handle, argc, argv, - &please_suspend, + &flags, &debug) < 0) return PAM_AUTH_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed account management"); - r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL); + r = fallback_shell_can_work(handle, &flags); + if (r != PAM_SUCCESS) + return r; + + r = acquire_home(handle, flags, debug, /* bus_data= */ NULL); if (r != PAM_SUCCESS) return r; @@ -865,20 +961,20 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( break; case -ENOLCK: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access.")); return PAM_ACCT_EXPIRED; case -EL2HLT: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access.")); return PAM_ACCT_EXPIRED; case -EL3HLT: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access.")); return PAM_ACCT_EXPIRED; default: if (r < 0) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); return PAM_ACCT_EXPIRED; } @@ -890,7 +986,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( usec_t n = now(CLOCK_REALTIME); if (t > n) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."), + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."), FORMAT_TIMESPAN(t - n, USEC_PER_SEC)); return PAM_MAXTRIES; @@ -901,21 +997,21 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -EKEYREVOKED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password change required.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required.")); return PAM_NEW_AUTHTOK_REQD; case -EOWNERDEAD: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required.")); return PAM_NEW_AUTHTOK_REQD; /* Strictly speaking this is only about password expiration, and we might want to allow * authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */ case -EKEYREJECTED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login.")); return PAM_AUTHTOK_EXPIRED; case -EKEYEXPIRED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change.")); break; case -ESTALE: @@ -929,7 +1025,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( default: if (r < 0) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); return PAM_AUTHTOK_EXPIRED; } @@ -941,17 +1037,18 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( _public_ PAM_EXTERN int pam_sm_chauthtok( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL; const char *old_password = NULL, *new_password = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; unsigned n_attempts = 0; bool debug = false; int r; + pam_log_setup(); + if (parse_argv(handle, argc, argv, NULL, @@ -960,22 +1057,17 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( pam_debug_syslog(handle, debug, "pam-systemd-homed account management"); - r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); - if (r != PAM_SUCCESS) - return r; - r = acquire_user_record(handle, NULL, debug, &ur, NULL); if (r != PAM_SUCCESS) return r; /* Start with cached credentials */ - r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get old password: @PAMERR@"); - - r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached password: @PAMERR@"); + r = pam_get_item_many( + handle, + PAM_OLDAUTHTOK, &old_password, + PAM_AUTHTOK, &new_password); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached passwords: @PAMERR@"); if (isempty(new_password)) { /* No, it's not cached, then let's ask for the password and its verification, and cache @@ -1000,7 +1092,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( } /* Now everything is cached and checked, let's exit from the preliminary check */ - if (FLAGS_SET(flags, PAM_PRELIM_CHECK)) + if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK)) return PAM_SUCCESS; old_secret = user_record_new(); @@ -1021,6 +1113,11 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( if (r < 0) return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store new password: %m"); + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); + if (r != PAM_SUCCESS) + return r; + for (;;) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |