]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pam_systemd_home: support login with alias names + user names with realms
authorLennart Poettering <lennart@poettering.net>
Thu, 16 Jan 2025 13:15:03 +0000 (14:15 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Jan 2025 08:59:02 +0000 (09:59 +0100)
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.

src/home/pam_systemd_home.c
src/login/pam_systemd.c

index 794438129efd0ab3c5484c37c3d321b6db15ff11..95f719d91206b2a21e5d176702cb83ed4a2af279 100644 (file)
@@ -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);
 
index dc8c727035d484e9c7d2f905c02003533118bdfd..7a457c5ac6e319fdeb5cd07857aa14e074548fa0 100644 (file)
@@ -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@");