]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: support user record aliases
authorLennart Poettering <lennart@poettering.net>
Thu, 16 Jan 2025 13:08:51 +0000 (14:08 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Jan 2025 08:59:05 +0000 (09:59 +0100)
src/home/homectl.c
src/home/homed-home-bus.c
src/home/homed-home.c
src/home/homed-home.h
src/home/homed-manager-bus.c
src/home/homed-manager.c
src/home/homed-manager.h
src/home/homed-varlink.c

index 104c35454b517f24180a80d31b573eed4c06c6da..afea68622cdacd8a47f5e5caaa189b2b077e58f1 100644 (file)
@@ -750,17 +750,9 @@ static int inspect_home(int argc, char *argv[], void *userdata) {
                 uid_t uid;
 
                 r = parse_uid(*i, &uid);
-                if (r < 0) {
-                        if (!valid_user_group_name(*i, 0)) {
-                                log_error("Invalid user name '%s'.", *i);
-                                if (ret == 0)
-                                        ret = -EINVAL;
-
-                                continue;
-                        }
-
+                if (r < 0)
                         r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", *i);
-                else
+                else
                         r = bus_call_method(bus, bus_mgr, "GetUserRecordByUID", &error, &reply, "u", (uint32_t) uid);
                 if (r < 0) {
                         log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r));
index 80e2773447fda970898bbc429214ae2cd080dc47..a3e6a321627d2f6f323271e1190d69282b4f002c 100644 (file)
@@ -799,8 +799,11 @@ static int bus_home_object_find(
 
         if (parse_uid(e, &uid) >= 0)
                 h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
-        else
-                h = hashmap_get(m->homes_by_name, e);
+        else {
+                r = manager_get_home_by_name(m, e, &h);
+                if (r < 0)
+                        return r;
+        }
         if (!h)
                 return 0;
 
index a6f99713dd62e8387e192428c19ed463857abbff..96477b3061afb363c024044cc8266ab97831df71 100644 (file)
@@ -101,6 +101,7 @@ static int suitable_home_record(UserRecord *hr) {
 int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
         _cleanup_(home_freep) Home *home = NULL;
         _cleanup_free_ char *nm = NULL, *ns = NULL, *blob = NULL;
+        _cleanup_strv_free_ char **aliases = NULL;
         int r;
 
         assert(m);
@@ -113,19 +114,29 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
         if (hashmap_contains(m->homes_by_name, hr->user_name))
                 return -EBUSY;
 
+        STRV_FOREACH(a, hr->aliases)
+                if (hashmap_contains(m->homes_by_name, *a))
+                        return -EBUSY;
+
         if (hashmap_contains(m->homes_by_uid, UID_TO_PTR(hr->uid)))
                 return -EBUSY;
 
         if (sysfs && hashmap_contains(m->homes_by_sysfs, sysfs))
                 return -EBUSY;
 
-        if (hashmap_size(m->homes_by_name) >= HOME_USERS_MAX)
+        if (hashmap_size(m->homes_by_uid) >= HOME_USERS_MAX)
                 return -EUSERS;
 
         nm = strdup(hr->user_name);
         if (!nm)
                 return -ENOMEM;
 
+        if (!strv_isempty(hr->aliases)) {
+                aliases = strv_copy(hr->aliases);
+                if (!aliases)
+                        return -ENOMEM;
+        }
+
         if (sysfs) {
                 ns = strdup(sysfs);
                 if (!ns)
@@ -139,6 +150,7 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
         *home = (Home) {
                 .manager = m,
                 .user_name = TAKE_PTR(nm),
+                .aliases = TAKE_PTR(aliases),
                 .uid = hr->uid,
                 .state = _HOME_STATE_INVALID,
                 .worker_stdout_fd = -EBADF,
@@ -152,6 +164,12 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
         if (r < 0)
                 return r;
 
+        STRV_FOREACH(a, home->aliases) {
+                r = hashmap_put(m->homes_by_name, *a, home);
+                if (r < 0)
+                        return r;
+        }
+
         r = hashmap_put(m->homes_by_uid, UID_TO_PTR(home->uid), home);
         if (r < 0)
                 return r;
@@ -197,6 +215,9 @@ Home *home_free(Home *h) {
                 if (h->user_name)
                         (void) hashmap_remove_value(h->manager->homes_by_name, h->user_name, h);
 
+                STRV_FOREACH(a, h->aliases)
+                        (void) hashmap_remove_value(h->manager->homes_by_name, *a, h);
+
                 if (uid_is_valid(h->uid))
                         (void) hashmap_remove_value(h->manager->homes_by_uid, UID_TO_PTR(h->uid), h);
 
@@ -218,6 +239,7 @@ Home *home_free(Home *h) {
         h->worker_event_source = sd_event_source_disable_unref(h->worker_event_source);
         safe_close(h->worker_stdout_fd);
         free(h->user_name);
+        strv_free(h->aliases);
         free(h->sysfs);
 
         h->ref_event_source_please_suspend = sd_event_source_disable_unref(h->ref_event_source_please_suspend);
@@ -257,6 +279,10 @@ int home_set_record(Home *h, UserRecord *hr) {
         if (!user_record_compatible(h->record, hr))
                 return -EREMCHG;
 
+        /* For now do not allow changing list of aliases */
+        if (!strv_equal_ignore_order(h->aliases, hr->aliases))
+                return -EREMCHG;
+
         if (!FLAGS_SET(hr->mask, USER_RECORD_REGULAR) ||
             FLAGS_SET(hr->mask, USER_RECORD_SECRET))
                 return -EINVAL;
index 8c92e39fe5f4ebf6ebd39ba33eecc4e56b0317e6..93689563d32d1a4c917abc1936596c0b42b27c60 100644 (file)
@@ -109,7 +109,12 @@ static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) {
 
 struct Home {
         Manager *manager;
+
+        /* The fields this record can be looked up by. This is kinda redundant, as the same information is
+         * available in the .record field, but we keep separate copies of these keys to make memory
+         * management for the hashmaps easier. */
         char *user_name;
+        char **aliases;
         uid_t uid;
 
         char *sysfs; /* When found via plugged in device, the sysfs path to it */
index 08c917aee2e95f62e5989d49e8dd0f0172cd1b9d..a08cc3803cd5d08785144138621911352d0c3094 100644 (file)
@@ -37,7 +37,7 @@ static int property_get_auto_login(
         if (r < 0)
                 return r;
 
-        HASHMAP_FOREACH(h, m->homes_by_name) {
+        HASHMAP_FOREACH(h, m->homes_by_uid) {
                 _cleanup_strv_free_ char **seats = NULL;
                 _cleanup_free_ char *home_path = NULL;
 
@@ -97,11 +97,9 @@ static int lookup_user_name(
                         return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid);
 
         } else {
-
-                if (!valid_user_group_name(user_name, 0))
-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
-
-                h = hashmap_get(m->homes_by_name, user_name);
+                r = manager_get_home_by_name(m, user_name, &h);
+                if (r < 0)
+                        return r;
                 if (!h)
                         return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
         }
@@ -342,6 +340,31 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu
         return generic_home_method(userdata, message, bus_home_method_deactivate, error);
 }
 
+static int check_for_conflicts(Manager *m, const char *name, sd_bus_error *error) {
+        int r;
+
+        assert(m);
+        assert(name);
+
+        Home *other = hashmap_get(m->homes_by_name, name);
+        if (other)
+                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", name);
+
+        r = getpwnam_malloc(name, /* ret= */ NULL);
+        if (r >= 0)
+                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", name);
+        if (r != -ESRCH)
+                return r;
+
+        r = getgrnam_malloc(name, /* ret= */ NULL);
+        if (r >= 0)
+                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", name);
+        if (r != -ESRCH)
+                return r;
+
+        return 0;
+}
+
 static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) {
         _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
         bool signed_locally;
@@ -356,21 +379,32 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs
         if (r < 0)
                 return r;
 
-        other = hashmap_get(m->homes_by_name, hr->user_name);
-        if (other)
-                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name);
-
-        r = getpwnam_malloc(hr->user_name, /* ret= */ NULL);
-        if (r >= 0)
-                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name);
-        if (r != -ESRCH)
+        r = check_for_conflicts(m, hr->user_name, error);
+        if (r < 0)
                 return r;
 
-        r = getgrnam_malloc(hr->user_name, /* ret= */ NULL);
-        if (r >= 0)
-                return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name);
-        if (r != -ESRCH)
-                return r;
+        if (hr->realm) {
+                r = check_for_conflicts(m, user_record_user_name_and_realm(hr), error);
+                if (r < 0)
+                        return r;
+        }
+
+        STRV_FOREACH(a, hr->aliases) {
+                r = check_for_conflicts(m, *a, error);
+                if (r < 0)
+                        return r;
+
+                if (hr->realm) {
+                        _cleanup_free_ char *alias_with_realm = NULL;
+                        alias_with_realm = strjoin(*a, "@", hr->realm);
+                        if (!alias_with_realm)
+                                return -ENOMEM;
+
+                        r = check_for_conflicts(m, alias_with_realm, error);
+                        if (r < 0)
+                                return r;
+                }
+        }
 
         if (blobs) {
                 const char *failed = NULL;
@@ -637,7 +671,7 @@ static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus
          * for every suitable home we have and only when all of them completed we send a reply indicating
          * completion. */
 
-        HASHMAP_FOREACH(h, m->homes_by_name) {
+        HASHMAP_FOREACH(h, m->homes_by_uid) {
 
                 if (!home_shall_suspend(h))
                         continue;
@@ -676,7 +710,7 @@ static int method_deactivate_all_homes(sd_bus_message *message, void *userdata,
          * systemd-homed.service itself since we want to allow restarting of it without tearing down all home
          * directories. */
 
-        HASHMAP_FOREACH(h, m->homes_by_name) {
+        HASHMAP_FOREACH(h, m->homes_by_uid) {
 
                 if (!o) {
                         o = operation_new(OPERATION_DEACTIVATE_ALL, message);
index a0d38eb7014a18723a6798be628341f5c9ac34d5..447d8949cca7e7faa2b63cc04833fb182e95d124 100644 (file)
@@ -76,7 +76,6 @@ static bool uid_is_home(uid_t uid) {
 #define UID_CLAMP_INTO_HOME_RANGE(rnd) (((uid_t) (rnd) % (HOME_UID_MAX - HOME_UID_MIN + 1)) + HOME_UID_MIN)
 
 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_uid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free);
-DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_name_hash_ops, char, string_hash_func, string_compare_func, Home, home_free);
 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_worker_pid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free);
 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, path_hash_func, path_compare, Home, home_free);
 
@@ -192,7 +191,7 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
                         log_debug("%s has been moved away, revalidating.", j);
 
                 h = hashmap_get(m->homes_by_name, n);
-                if (h) {
+                if (h && streq(h->user_name, n)) {
                         manager_revalidate_image(m, h);
                         (void) bus_manager_emit_auto_login_changed(m);
                 }
@@ -243,7 +242,7 @@ int manager_new(Manager **ret) {
         if (!m->homes_by_uid)
                 return -ENOMEM;
 
-        m->homes_by_name = hashmap_new(&homes_by_name_hash_ops);
+        m->homes_by_name = hashmap_new(&string_hash_ops);
         if (!m->homes_by_name)
                 return -ENOMEM;
 
@@ -698,6 +697,11 @@ static int manager_add_home_by_image(
         if (h) {
                 bool same;
 
+                if (!streq(h->user_name, user_name)) {
+                        log_debug("Found an image for user %s which already is an alias for another user, skipping.", user_name);
+                        return 0; /* Ignore images that would synthesize a user that conflicts with an alias of another user */
+                }
+
                 if (h->state != HOME_UNFIXATED) {
                         log_debug("Found an image for user %s which already has a record, skipping.", user_name);
                         return 0; /* ignore images that synthesize a user we already have a record for */
@@ -1721,7 +1725,7 @@ int manager_gc_images(Manager *m) {
         } else {
                 /* Gc all */
 
-                HASHMAP_FOREACH(h, m->homes_by_name)
+                HASHMAP_FOREACH(h, m->homes_by_uid)
                         manager_revalidate_image(m, h);
         }
 
@@ -1741,12 +1745,14 @@ static int manager_gc_blob(Manager *m) {
                 return log_error_errno(errno, "Failed to open %s: %m", home_system_blob_dir());
         }
 
-        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m"))
-                if (!hashmap_contains(m->homes_by_name, de->d_name)) {
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m")) {
+                Home *found = hashmap_get(m->homes_by_name, de->d_name);
+                if (!found || !streq(found->user_name, de->d_name)) {
                         r = rm_rf_at(dirfd(d), de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
                         if (r < 0)
                                 log_warning_errno(r, "Failed to delete blob dir for missing user '%s', ignoring: %m", de->d_name);
                 }
+        }
 
         return 0;
 }
@@ -1841,7 +1847,7 @@ static bool manager_shall_rebalance(Manager *m) {
         if (IN_SET(m->rebalance_state, REBALANCE_PENDING, REBALANCE_SHRINKING, REBALANCE_GROWING))
                 return true;
 
-        HASHMAP_FOREACH(h, m->homes_by_name)
+        HASHMAP_FOREACH(h, m->homes_by_uid)
                 if (home_shall_rebalance(h))
                         return true;
 
@@ -1887,7 +1893,7 @@ static int manager_rebalance_calculate(Manager *m) {
                                                 * (home dirs get 100 by default, i.e. 5x more). This weight
                                                 * is not configurable, the per-home weights are. */
 
-        HASHMAP_FOREACH(h, m->homes_by_name) {
+        HASHMAP_FOREACH(h, m->homes_by_uid) {
                 statfs_f_type_t fstype;
                 h->rebalance_pending = false; /* First, reset the flag, we only want it to be true for the
                                                * homes that qualify for rebalancing */
@@ -2024,7 +2030,7 @@ static int manager_rebalance_apply(Manager *m) {
 
         assert(m);
 
-        HASHMAP_FOREACH(h, m->homes_by_name) {
+        HASHMAP_FOREACH(h, m->homes_by_uid) {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
 
                 if (!h->rebalance_pending)
@@ -2265,3 +2271,29 @@ int manager_reschedule_rebalance(Manager *m) {
 
         return 1;
 }
+
+int manager_get_home_by_name(Manager *m, const char *user_name, Home **ret) {
+        assert(m);
+        assert(user_name);
+
+        Home *h = hashmap_get(m->homes_by_name, user_name);
+        if (!h) {
+                /* Also search by username and realm. For that simply chop off realm, then look for the home, and verify it afterwards. */
+                const char *realm = strrchr(user_name, '@');
+                if (realm) {
+                        _cleanup_free_ char *prefix = strndup(user_name, realm - user_name);
+                        if (!prefix)
+                                return -ENOMEM;
+
+                        Home *j;
+                        j = hashmap_get(m->homes_by_name, prefix);
+                        if (j && user_record_matches_user_name(j->record, user_name))
+                                h = j;
+                }
+        }
+
+        if (ret)
+                *ret = h;
+
+        return !!h;
+}
index eadb0b155dae7cb40a9cdb39e4b0df9f5c9dc17c..9fea621031155dc3c755b9371a09595df2213e53 100644 (file)
@@ -91,3 +91,5 @@ int manager_acquire_key_pair(Manager *m);
 int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error);
 
 int bus_manager_emit_auto_login_changed(Manager *m);
+
+int manager_get_home_by_name(Manager *m, const char *user_name, Home **ret);
index cfd46ea51a6131eb6a7254c456cf4476020af294..ef30ea7eaff305f08ed61a35c884ed864a337ffe 100644 (file)
@@ -100,15 +100,17 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_
 
         if (uid_is_valid(p.uid))
                 h = hashmap_get(m->homes_by_uid, UID_TO_PTR(p.uid));
-        else if (p.user_name)
-                h = hashmap_get(m->homes_by_name, p.user_name);
-        else {
+        else if (p.user_name) {
+                r = manager_get_home_by_name(m, p.user_name, &h);
+                if (r < 0)
+                        return r;
+        } else {
 
                 /* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify()
                  * for all entries but the last, so that clients can stream the results, and easily process
                  * them piecemeal. */
 
-                HASHMAP_FOREACH(h, m->homes_by_name) {
+                HASHMAP_FOREACH(h, m->homes_by_uid) {
 
                         if (!home_user_match_lookup_parameters(&p, h))
                                 continue;
@@ -212,11 +214,13 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd
 
         if (gid_is_valid(p.gid))
                 h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid));
-        else if (p.group_name)
-                h = hashmap_get(m->homes_by_name, p.group_name);
-        else {
+        else if (p.group_name) {
+                r = manager_get_home_by_name(m, p.group_name, &h);
+                if (r < 0)
+                        return r;
+        } else {
 
-                HASHMAP_FOREACH(h, m->homes_by_name) {
+                HASHMAP_FOREACH(h, m->homes_by_uid) {
 
                         if (!home_group_match_lookup_parameters(&p, h))
                                 continue;
@@ -279,7 +283,9 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_
         if (p.user_name) {
                 const char *last = NULL;
 
-                h = hashmap_get(m->homes_by_name, p.user_name);
+                r = manager_get_home_by_name(m, p.user_name, &h);
+                if (r < 0)
+                        return r;
                 if (!h)
                         return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
 
@@ -315,7 +321,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_
         } else if (p.group_name) {
                 const char *last = NULL;
 
-                HASHMAP_FOREACH(h, m->homes_by_name) {
+                HASHMAP_FOREACH(h, m->homes_by_uid) {
 
                         if (!strv_contains(h->record->member_of, p.group_name))
                                 continue;
@@ -340,7 +346,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_
         } else {
                 const char *last_user_name = NULL, *last_group_name = NULL;
 
-                HASHMAP_FOREACH(h, m->homes_by_name)
+                HASHMAP_FOREACH(h, m->homes_by_uid)
                         STRV_FOREACH(j, h->record->member_of) {
 
                                 if (last_user_name) {