From: Lennart Poettering Date: Thu, 16 Jan 2025 13:15:03 +0000 (+0100) Subject: pam_systemd_home: support login with alias names + user names with realms X-Git-Tag: v258-rc1~1541^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a642f9d2d37945917bf4200a1095a43e6e7b6ea7;p=thirdparty%2Fsystemd.git pam_systemd_home: support login with alias names + user names with realms This in particular makes sure that we normalize the user name and update it in the PAM session, once we acquire it. This means that if you have a user with name "a" and alias "b", and the user logs in as "b" they end up properly with "a" as user name set, as intended by the PAM gods. Moreover, if you have a user "c" in a ralm "d", they may log in by specifying "c" or "c@d", with equivalent results. --- diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 794438129ef..95f719d9120 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -102,11 +102,6 @@ static int acquire_user_record( UserRecord **ret_record, PamBusData **bus_data) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - _cleanup_free_ char *homed_field = NULL; - const char *json = NULL; int r; assert(handle); @@ -124,13 +119,19 @@ static int acquire_user_record( if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0)) return PAM_USER_UNKNOWN; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *json = NULL; + bool fresh_data; + /* We cache the user record in the PAM context. We use a field name that includes the username, since * clients might change the user name associated with a PAM context underneath us. Notably, 'sudo' * creates a single PAM context and first authenticates it with the user set to the originating user, * then updates the user for the destination user and issues the session stack with the same PAM * context. We thus must be prepared that the user record changes between calls and we keep any * caching separate. */ - homed_field = strjoin("systemd-home-user-record-", username); + _cleanup_free_ char *homed_field = strjoin("systemd-home-user-record-", username); if (!homed_field) return pam_log_oom(handle); @@ -143,9 +144,10 @@ static int acquire_user_record( * negative cache indicator) */ if (json == POINTER_MAX) return PAM_USER_UNKNOWN; + + fresh_data = false; } 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", debug, &bus, bus_data); @@ -177,9 +179,42 @@ static int acquire_user_record( if (r < 0) return pam_bus_log_parse_error(handle, r); + fresh_data = true; + } + + r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL); + if (r < 0) + return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); + + ur = user_record_new(); + if (!ur) + return pam_log_oom(handle); + + r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); + if (r < 0) + return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); + + /* Safety check if cached record actually matches what we are looking for */ + if (!user_record_matches_user_name(ur, username)) + return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, + "Acquired user record does not match user name."); + + /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is + * going to invalidate the old version after all */ + username = ur->user_name; + + /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary, + * normalized name of the user record (i.e. not an alias or so). */ + r = pam_set_item(handle, PAM_USER, ur->user_name); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, + "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name); + + /* Everything seems to be good, let's cache this data now */ + if (fresh_data) { /* First copy: for the homed-specific data field, i.e. where we know the user record is from * homed */ - json_copy = strdup(json); + _cleanup_free_ char *json_copy = strdup(json); if (!json_copy) return pam_log_oom(handle); @@ -195,7 +230,7 @@ static int acquire_user_record( if (!json_copy) return pam_log_oom(handle); - generic_field = strjoin("systemd-user-record-", username); + _cleanup_free_ char *generic_field = strjoin("systemd-user-record-", username); if (!generic_field) return pam_log_oom(handle); @@ -207,23 +242,6 @@ static int acquire_user_record( TAKE_PTR(json_copy); } - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); - if (r < 0) - return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); - - ur = user_record_new(); - if (!ur) - return pam_log_oom(handle); - - r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); - if (r < 0) - return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); - - /* Safety check if cached record actually matches what we are looking for */ - if (!user_record_matches_user_name(ur, username)) - return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, - "Acquired user record does not match user name."); - if (ret_record) *ret_record = TAKE_PTR(ur); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index dc8c727035d..7a457c5ac6e 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -177,13 +177,11 @@ static int acquire_user_record( pam_handle_t *handle, UserRecord **ret_record) { - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - const char *username = NULL, *json = NULL; - _cleanup_free_ char *field = NULL; int r; assert(handle); + const char *username = NULL; r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); @@ -192,10 +190,12 @@ static int acquire_user_record( /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it * here. */ - field = strjoin("systemd-user-record-", username); + _cleanup_free_ char *field = strjoin("systemd-user-record-", username); if (!field) return pam_log_oom(handle); + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *json = NULL; r = pam_get_data(handle, field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@");