userdbctl: add some basic client-side filtering
This adds some basic client-side user/group filtering to "userdbctl": 1. by uid/gid min/max 2. by user "disposition" (i.e. show only regular users with "userdbctl user -R") 3. by fuzzy name (i.e. search by substring/levenshtein of user name, real name, and other identifiers of the user/group record). In the long run we also want to support this server side, but let's start out with doing this client-side, since many backends won't support server-side filtering anytime soon anyway, so we need it in either case.
This commit is contained in:
@@ -326,3 +326,28 @@ int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int group_record_match(GroupRecord *h, const UserDBMatch *match) {
|
||||
assert(h);
|
||||
assert(match);
|
||||
|
||||
if (h->gid < match->gid_min || h->gid > match->gid_max)
|
||||
return false;
|
||||
|
||||
if (!FLAGS_SET(match->disposition_mask, UINT64_C(1) << group_record_disposition(h)))
|
||||
return false;
|
||||
|
||||
if (!strv_isempty(match->fuzzy_names)) {
|
||||
const char* names[] = {
|
||||
h->group_name,
|
||||
group_record_group_name_and_realm(h),
|
||||
h->description,
|
||||
};
|
||||
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -43,5 +43,7 @@ int group_record_load(GroupRecord *h, sd_json_variant *v, UserRecordLoadFlags fl
|
||||
int group_record_build(GroupRecord **ret, ...);
|
||||
int group_record_clone(GroupRecord *g, UserRecordLoadFlags flags, GroupRecord **ret);
|
||||
|
||||
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);
|
||||
|
||||
@@ -2401,6 +2401,72 @@ int suitable_blob_filename(const char *name) {
|
||||
name[0] != '.';
|
||||
}
|
||||
|
||||
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) {
|
||||
assert(names || n_names == 0);
|
||||
|
||||
/* Checks if any of the user record strings in the names[] array matches any of the search strings in
|
||||
* the matches** strv fuzzily. */
|
||||
|
||||
FOREACH_ARRAY(n, names, n_names) {
|
||||
if (!*n)
|
||||
continue;
|
||||
|
||||
_cleanup_free_ char *lcn = strdup(*n);
|
||||
if (!lcn)
|
||||
return -ENOMEM;
|
||||
|
||||
ascii_strlower(lcn);
|
||||
|
||||
STRV_FOREACH(i, matches) {
|
||||
_cleanup_free_ char *lc = strdup(*i);
|
||||
if (!lc)
|
||||
return -ENOMEM;
|
||||
|
||||
ascii_strlower(lc);
|
||||
|
||||
/* First do substring check */
|
||||
if (strstr(lcn, lc))
|
||||
return true;
|
||||
|
||||
/* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */
|
||||
if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int user_record_match(UserRecord *u, const UserDBMatch *match) {
|
||||
assert(u);
|
||||
assert(match);
|
||||
|
||||
if (u->uid < match->uid_min || u->uid > match->uid_max)
|
||||
return false;
|
||||
|
||||
if (!FLAGS_SET(match->disposition_mask, UINT64_C(1) << user_record_disposition(u)))
|
||||
return false;
|
||||
|
||||
if (!strv_isempty(match->fuzzy_names)) {
|
||||
|
||||
/* Note this array of names is sparse, i.e. various entries listed in it will be
|
||||
* NULL. Because of that we are not using a NULL terminated strv here, but a regular
|
||||
* array. */
|
||||
const char* names[] = {
|
||||
u->user_name,
|
||||
user_record_user_name_and_realm(u),
|
||||
u->real_name,
|
||||
u->email_address,
|
||||
u->cifs_user_name,
|
||||
};
|
||||
|
||||
if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
|
||||
[USER_CLASSIC] = "classic",
|
||||
[USER_LUKS] = "luks",
|
||||
|
||||
@@ -462,6 +462,24 @@ int user_group_record_mangle(sd_json_variant *v, UserRecordLoadFlags load_flags,
|
||||
#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB)
|
||||
int suitable_blob_filename(const char *name);
|
||||
|
||||
typedef struct UserDBMatch {
|
||||
char **fuzzy_names;
|
||||
uint64_t disposition_mask;
|
||||
union {
|
||||
uid_t uid_min;
|
||||
gid_t gid_min;
|
||||
};
|
||||
union {
|
||||
uid_t uid_max;
|
||||
gid_t gid_max;
|
||||
};
|
||||
} UserDBMatch;
|
||||
|
||||
#define USER_DISPOSITION_MASK_MAX ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1))
|
||||
|
||||
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches);
|
||||
int user_record_match(UserRecord *u, const UserDBMatch *match);
|
||||
|
||||
const char* user_storage_to_string(UserStorage t) _const_;
|
||||
UserStorage user_storage_from_string(const char *s) _pure_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user