user-record/home: add concept of alias names for accounts (#36041)
This commit is contained in:
@@ -262,12 +262,6 @@ Features:
|
||||
suffix the escape sequence with one more decimal digit, because compilers
|
||||
think you might actually specify a value outside the 8bit range with that.
|
||||
|
||||
* homed: allow login via username + realm on getty/login prompt. Then rewrite
|
||||
the user name in the PAM stack
|
||||
|
||||
* homed/userdb: add "aliases" field to user record, which can alternatively be
|
||||
used for logging in. Rewrite user name in the PAM stack once acquired.
|
||||
|
||||
* confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/,
|
||||
insert an intermediary bind mount on itself there. This has the benefit that
|
||||
services where mount propagation from the root fs is off, an still have
|
||||
@@ -381,11 +375,6 @@ Features:
|
||||
* Clean up "reboot argument" handling, i.e. set it through some IPC service
|
||||
instead of directly via /run/, so that it can be sensible set remotely.
|
||||
|
||||
* userdb: add concept for user "aliases", to cover for cases where you can log
|
||||
in under the name lennart@somenetworkfsserver, and it would automatically
|
||||
generate a local user, and from the one both names can be used to allow
|
||||
logins into the same account.
|
||||
|
||||
* systemd-tpm2-support: add a some logic that detects if system is in DA
|
||||
lockout mode, and queries the user for TPM recovery PIN then.
|
||||
|
||||
|
||||
@@ -226,6 +226,14 @@ This field is optional, when unset the user should not be considered part of any
|
||||
A user record with a realm set is never compatible (for the purpose of updates,
|
||||
see above) with a user record without one set, even if the `userName` field matches.
|
||||
|
||||
`aliases` → An array of strings, each being a valid UNIX user name. If
|
||||
specified, a list of additional UNIX user names this record shall be known
|
||||
under. These are *alias* names only, the name in `userName` is always the
|
||||
primary name. Typically, a user record that carries this field shall be
|
||||
retrievable and resolvable under every name listed here, pretty much everywhere
|
||||
the primary user name is. If logging in is attempted via an alias name it
|
||||
should be normalized to the primary name.
|
||||
|
||||
`blobDirectory` → The absolute path to a world-readable copy of the user's blob
|
||||
directory. See [Blob Directories](/USER_RECORD_BLOB_DIRS) for more details.
|
||||
|
||||
|
||||
@@ -226,6 +226,16 @@
|
||||
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--alias=<replaceable>NAME</replaceable><optional>,<replaceable>NAME…</replaceable></optional></option></term>
|
||||
|
||||
<listitem><para>Additional names for the user. Takes one or more valid UNIX user names, separated by
|
||||
commas. May be used multiple times to define multiple aliases. An alias username may be specified
|
||||
wherever the primary user name may be specified, and resolves to the same user record.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--email-address=<replaceable>EMAIL</replaceable></option></term>
|
||||
|
||||
|
||||
@@ -861,6 +861,26 @@ int strv_compare(char * const *a, char * const *b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool strv_equal_ignore_order(char **a, char **b) {
|
||||
|
||||
/* Just like strv_equal(), but doesn't care about the order of elements or about redundant entries
|
||||
* (i.e. it's even ok if the number of entries in the array differ, as long as the difference just
|
||||
* consists of repititions) */
|
||||
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
STRV_FOREACH(i, a)
|
||||
if (!strv_contains(b, *i))
|
||||
return false;
|
||||
|
||||
STRV_FOREACH(i, b)
|
||||
if (!strv_contains(a, *i))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void strv_print_full(char * const *l, const char *prefix) {
|
||||
STRV_FOREACH(s, l)
|
||||
printf("%s%s\n", strempty(prefix), *s);
|
||||
|
||||
@@ -96,6 +96,8 @@ static inline bool strv_equal(char * const *a, char * const *b) {
|
||||
return strv_compare(a, b) == 0;
|
||||
}
|
||||
|
||||
bool strv_equal_ignore_order(char **a, char **b);
|
||||
|
||||
char** strv_new_internal(const char *x, ...) _sentinel_;
|
||||
char** strv_new_ap(const char *x, va_list ap);
|
||||
#define strv_new(...) strv_new_internal(__VA_ARGS__, NULL)
|
||||
|
||||
+52
-10
@@ -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));
|
||||
@@ -2764,6 +2756,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
"\n%4$sGeneral User Record Properties:%5$s\n"
|
||||
" -c --real-name=REALNAME Real name for user\n"
|
||||
" --realm=REALM Realm to create user in\n"
|
||||
" --alias=ALIAS Define alias usernames for this account\n"
|
||||
" --email-address=EMAIL Email address for user\n"
|
||||
" --location=LOCATION Set location of user on earth\n"
|
||||
" --icon-name=NAME Icon name for user\n"
|
||||
@@ -2908,6 +2901,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_NO_ASK_PASSWORD,
|
||||
ARG_OFFLINE,
|
||||
ARG_REALM,
|
||||
ARG_ALIAS,
|
||||
ARG_EMAIL_ADDRESS,
|
||||
ARG_DISK_SIZE,
|
||||
ARG_ACCESS_MODE,
|
||||
@@ -2999,6 +2993,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "real-name", required_argument, NULL, 'c' },
|
||||
{ "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */
|
||||
{ "realm", required_argument, NULL, ARG_REALM },
|
||||
{ "alias", required_argument, NULL, ARG_ALIAS },
|
||||
{ "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS },
|
||||
{ "location", required_argument, NULL, ARG_LOCATION },
|
||||
{ "password-hint", required_argument, NULL, ARG_PASSWORD_HINT },
|
||||
@@ -3154,6 +3149,53 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
break;
|
||||
|
||||
case ARG_ALIAS: {
|
||||
if (isempty(optarg)) {
|
||||
r = drop_from_identity("aliases");
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const char *p = optarg;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
|
||||
r = extract_first_word(&p, &word, ",", 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse alias list: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (!valid_user_group_name(word, 0))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid alias user name %s.", word);
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *av =
|
||||
sd_json_variant_ref(sd_json_variant_by_key(arg_identity_extra, "aliases"));
|
||||
|
||||
_cleanup_strv_free_ char **list = NULL;
|
||||
r = sd_json_variant_strv(av, &list);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse group list: %m");
|
||||
|
||||
r = strv_extend(&list, word);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
strv_sort_uniq(list);
|
||||
|
||||
av = sd_json_variant_unref(av);
|
||||
r = sd_json_variant_new_array_strv(&av, list);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create alias list JSON: %m");
|
||||
|
||||
r = sd_json_variant_set_field(&arg_identity_extra, "aliases", av);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update alias list: %m");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'd': {
|
||||
_cleanup_free_ char *hd = NULL;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+27
-1
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+19
-13
@@ -62,7 +62,7 @@ static bool home_user_match_lookup_parameters(LookupParameters *p, Home *h) {
|
||||
assert(p);
|
||||
assert(h);
|
||||
|
||||
if (p->user_name && !streq(p->user_name, h->user_name))
|
||||
if (p->user_name && !user_record_matches_user_name(h->record, p->user_name))
|
||||
return false;
|
||||
|
||||
if (uid_is_valid(p->uid) && h->uid != p->uid)
|
||||
@@ -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;
|
||||
@@ -175,7 +177,7 @@ static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) {
|
||||
assert(p);
|
||||
assert(h);
|
||||
|
||||
if (p->group_name && !streq(h->user_name, p->group_name))
|
||||
if (p->group_name && !user_record_matches_user_name(h->record, p->group_name))
|
||||
return false;
|
||||
|
||||
if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid)
|
||||
@@ -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) {
|
||||
|
||||
+47
-32
@@ -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);
|
||||
@@ -115,7 +110,6 @@ static int acquire_user_record(
|
||||
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@");
|
||||
|
||||
if (isempty(username))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
|
||||
}
|
||||
@@ -125,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);
|
||||
|
||||
@@ -144,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);
|
||||
@@ -178,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);
|
||||
|
||||
@@ -196,35 +230,18 @@ 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);
|
||||
|
||||
r = pam_set_data(handle, generic_field, json_copy, pam_cleanup_free);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set PAM user record data '%s': @PAMERR@", homed_field);
|
||||
"Failed to set PAM user record data '%s': @PAMERR@", generic_field);
|
||||
|
||||
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 (!streq_ptr(username, ur->user_name))
|
||||
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);
|
||||
|
||||
@@ -535,7 +552,6 @@ static int acquire_home(
|
||||
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@");
|
||||
|
||||
if (isempty(username))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
|
||||
|
||||
@@ -879,7 +895,6 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
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@");
|
||||
|
||||
if (isempty(username))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
|
||||
|
||||
@@ -949,7 +964,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = acquire_user_record(handle, NULL, debug, &ur, NULL);
|
||||
r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
@@ -1057,7 +1072,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
|
||||
|
||||
pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
|
||||
|
||||
r = acquire_user_record(handle, NULL, debug, &ur, NULL);
|
||||
r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
|
||||
+11
-12
@@ -177,26 +177,25 @@ 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@");
|
||||
|
||||
if (isempty(username))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not valid.");
|
||||
|
||||
/* 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@");
|
||||
@@ -217,14 +216,14 @@ static int acquire_user_record(
|
||||
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 (!streq_ptr(username, ur->user_name))
|
||||
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.");
|
||||
} else {
|
||||
_cleanup_free_ char *formatted = NULL;
|
||||
|
||||
/* Request the record ourselves */
|
||||
r = userdb_by_name(username, 0, &ur);
|
||||
r = userdb_by_name(username, /* flags= */ 0, &ur);
|
||||
if (r < 0) {
|
||||
pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m");
|
||||
return PAM_USER_UNKNOWN;
|
||||
@@ -1468,12 +1467,12 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
/* class= */ NULL,
|
||||
/* type= */ NULL,
|
||||
/* deskop= */ NULL,
|
||||
&debug,
|
||||
NULL,
|
||||
NULL) < 0)
|
||||
/* default_capability_bounding_set */ NULL,
|
||||
/* default_capability_ambient_set= */ NULL) < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
|
||||
pam_debug_syslog(handle, debug, "pam-systemd shutting down");
|
||||
|
||||
@@ -331,6 +331,19 @@ int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool group_record_matches_group_name(const GroupRecord *g, const char *group_name) {
|
||||
assert(g);
|
||||
assert(group_name);
|
||||
|
||||
if (streq_ptr(g->group_name, group_name))
|
||||
return true;
|
||||
|
||||
if (streq_ptr(g->group_name_and_realm_auto, group_name))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int group_record_match(GroupRecord *h, const UserDBMatch *match) {
|
||||
assert(h);
|
||||
assert(match);
|
||||
|
||||
@@ -47,3 +47,5 @@ int group_record_match(GroupRecord *h, const UserDBMatch *match);
|
||||
|
||||
const char* group_record_group_name_and_realm(GroupRecord *h);
|
||||
UserDisposition group_record_disposition(GroupRecord *h);
|
||||
|
||||
bool group_record_matches_group_name(const GroupRecord *g, const char *groupname);
|
||||
|
||||
@@ -65,6 +65,13 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
||||
printf(" User name: %s\n",
|
||||
user_record_user_name_and_realm(hr));
|
||||
|
||||
if (!strv_isempty(hr->aliases)) {
|
||||
STRV_FOREACH(i, hr->aliases)
|
||||
printf(i == hr->aliases ?
|
||||
" Alias: %s" : ", %s", *i);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
if (hr->state) {
|
||||
const char *color;
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ static UserRecord* user_record_free(UserRecord *h) {
|
||||
free(h->user_name);
|
||||
free(h->realm);
|
||||
free(h->user_name_and_realm_auto);
|
||||
strv_free(h->aliases);
|
||||
free(h->real_name);
|
||||
free(h->email_address);
|
||||
erase_and_free(h->password_hint);
|
||||
@@ -1538,6 +1539,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load
|
||||
|
||||
static const sd_json_dispatch_field user_dispatch_table[] = {
|
||||
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), SD_JSON_RELAX },
|
||||
{ "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, aliases), SD_JSON_RELAX },
|
||||
{ "realm", SD_JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
|
||||
{ "blobDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), SD_JSON_STRICT },
|
||||
{ "blobManifest", SD_JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
|
||||
@@ -2625,6 +2627,28 @@ int user_record_is_nobody(const UserRecord *u) {
|
||||
return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody");
|
||||
}
|
||||
|
||||
bool user_record_matches_user_name(const UserRecord *u, const char *user_name) {
|
||||
assert(u);
|
||||
assert(user_name);
|
||||
|
||||
if (streq_ptr(u->user_name, user_name))
|
||||
return true;
|
||||
|
||||
if (streq_ptr(u->user_name_and_realm_auto, user_name))
|
||||
return true;
|
||||
|
||||
if (strv_contains(u->aliases, user_name))
|
||||
return true;
|
||||
|
||||
const char *realm = strrchr(user_name, '@');
|
||||
if (realm && streq_ptr(realm+1, u->realm))
|
||||
STRV_FOREACH(a, u->aliases)
|
||||
if (startswith(user_name, *a) == realm)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int suitable_blob_filename(const char *name) {
|
||||
/* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
|
||||
return filename_is_valid(name) &&
|
||||
@@ -2691,7 +2715,8 @@ int user_record_match(UserRecord *u, const UserDBMatch *match) {
|
||||
u->cifs_user_name,
|
||||
};
|
||||
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names))
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) &&
|
||||
!user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,7 @@ typedef struct UserRecord {
|
||||
char *user_name;
|
||||
char *realm;
|
||||
char *user_name_and_realm_auto; /* the user_name field concatenated with '@' and the realm, if the latter is defined */
|
||||
char **aliases;
|
||||
char *real_name;
|
||||
char *email_address;
|
||||
char *password_hint;
|
||||
@@ -490,6 +491,8 @@ typedef struct UserDBMatch {
|
||||
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches);
|
||||
int user_record_match(UserRecord *u, const UserDBMatch *match);
|
||||
|
||||
bool user_record_matches_user_name(const UserRecord *u, const char *username);
|
||||
|
||||
const char* user_storage_to_string(UserStorage t) _const_;
|
||||
UserStorage user_storage_from_string(const char *s) _pure_;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "group-record.h"
|
||||
#include "path-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "user-util.h"
|
||||
@@ -87,7 +88,7 @@ static int load_user(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (name && !streq_ptr(name, u->user_name))
|
||||
if (name && !user_record_matches_user_name(u, name))
|
||||
return -EINVAL;
|
||||
|
||||
if (uid_is_valid(uid) && uid != u->uid)
|
||||
@@ -231,7 +232,7 @@ static int load_group(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (name && !streq_ptr(name, g->group_name))
|
||||
if (name && !group_record_matches_group_name(g, name))
|
||||
return -EINVAL;
|
||||
|
||||
if (gid_is_valid(gid) && gid != g->gid)
|
||||
|
||||
@@ -1255,4 +1255,26 @@ TEST(strv_find_closest) {
|
||||
ASSERT_NULL(strv_find_closest(l, "sfajosajfosdjaofjdsaf"));
|
||||
}
|
||||
|
||||
TEST(strv_equal_ignore_order) {
|
||||
|
||||
ASSERT_TRUE(strv_equal_ignore_order(NULL, NULL));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(NULL, STRV_MAKE(NULL)));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE(NULL), NULL));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE(NULL), STRV_MAKE(NULL)));
|
||||
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), NULL));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE(NULL)));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(NULL, STRV_MAKE("foo")));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE(NULL), STRV_MAKE("foo")));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE("foo")));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE("foo", "bar")));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo", "bar"), STRV_MAKE("foo")));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("foo", "bar"), STRV_MAKE("foo", "bar")));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("foo", "bar")));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("foo", "bar", "quux")));
|
||||
ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("bar", "foo", "quux"), STRV_MAKE("foo", "bar")));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo", "quux"), STRV_MAKE("quux", "foo", "bar")));
|
||||
ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("bar", "foo", "bar", "foo", "foo")));
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
@@ -215,7 +215,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
|
||||
}
|
||||
|
||||
if ((uid_is_valid(p.uid) && hr->uid != p.uid) ||
|
||||
(p.user_name && !streq(hr->user_name, p.user_name)))
|
||||
(p.user_name && !user_record_matches_user_name(hr, p.user_name)))
|
||||
return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_user_json(link, hr, &v);
|
||||
@@ -345,7 +345,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
|
||||
}
|
||||
|
||||
if ((uid_is_valid(p.gid) && g->gid != p.gid) ||
|
||||
(p.group_name && !streq(g->group_name, p.group_name)))
|
||||
(p.group_name && !group_record_matches_group_name(g, p.group_name)))
|
||||
return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_group_json(link, g, &v);
|
||||
|
||||
@@ -629,6 +629,29 @@ EOF
|
||||
wait_for_state homedsshtest inactive
|
||||
fi
|
||||
|
||||
NEWPASSWORD=hunter4711 homectl create aliastest --storage=directory --alias=aliastest2 --alias=aliastest3 --realm=myrealm
|
||||
|
||||
homectl inspect aliastest
|
||||
homectl inspect aliastest2
|
||||
homectl inspect aliastest3
|
||||
homectl inspect aliastest@myrealm
|
||||
homectl inspect aliastest2@myrealm
|
||||
homectl inspect aliastest3@myrealm
|
||||
|
||||
userdbctl user aliastest
|
||||
userdbctl user aliastest2
|
||||
userdbctl user aliastest3
|
||||
userdbctl user aliastest@myrealm
|
||||
userdbctl user aliastest2@myrealm
|
||||
userdbctl user aliastest3@myrealm
|
||||
|
||||
getent passwd aliastest
|
||||
getent passwd aliastest2
|
||||
getent passwd aliastest3
|
||||
getent passwd aliastest@myrealm
|
||||
getent passwd aliastest2@myrealm
|
||||
getent passwd aliastest3@myrealm
|
||||
|
||||
systemd-analyze log-level info
|
||||
|
||||
touch /testok
|
||||
|
||||
Reference in New Issue
Block a user