From: Lennart Poettering Date: Thu, 16 Jan 2025 13:08:51 +0000 (+0100) Subject: homed: support user record aliases X-Git-Tag: v258-rc1~1541^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=40fd0e0423ed9e4ae47d7f1adba83c0487d7decd;p=thirdparty%2Fsystemd.git homed: support user record aliases --- diff --git a/src/home/homectl.c b/src/home/homectl.c index 104c35454b5..afea68622cd 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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)); diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index 80e2773447f..a3e6a321627 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -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; diff --git a/src/home/homed-home.c b/src/home/homed-home.c index a6f99713dd6..96477b3061a 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -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; diff --git a/src/home/homed-home.h b/src/home/homed-home.h index 8c92e39fe5f..93689563d32 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -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 */ diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index 08c917aee2e..a08cc3803cd 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -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); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index a0d38eb7014..447d8949cca 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -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; +} diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h index eadb0b155da..9fea6210311 100644 --- a/src/home/homed-manager.h +++ b/src/home/homed-manager.h @@ -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); diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c index cfd46ea51a6..ef30ea7eaff 100644 --- a/src/home/homed-varlink.c +++ b/src/home/homed-varlink.c @@ -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) {