summaryrefslogtreecommitdiffstats
path: root/src/shared
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2024-10-24 22:36:22 +0200
committerGitHub <noreply@github.com>2024-10-24 22:36:22 +0200
commit210fb8626fe205c417403523596ebbc3c3a16da4 (patch)
tree0056fe9baa0a95e4f2a534f5cac17c7df2dde94e /src/shared
parentuser-util: tighten shell validation a tiny bit (diff)
parentci: give new userdbctl some CI exposure (diff)
downloadsystemd-210fb8626fe205c417403523596ebbc3c3a16da4.tar.xz
systemd-210fb8626fe205c417403523596ebbc3c3a16da4.zip
Merge pull request #34875 from poettering/userdbctl-filter
userdbctl: add some basic client-side filtering
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/group-record.c25
-rw-r--r--src/shared/group-record.h2
-rw-r--r--src/shared/user-record-nss.c48
-rw-r--r--src/shared/user-record.c66
-rw-r--r--src/shared/user-record.h18
5 files changed, 135 insertions, 24 deletions
diff --git a/src/shared/group-record.c b/src/shared/group-record.c
index a297272fab..7b401bf064 100644
--- a/src/shared/group-record.c
+++ b/src/shared/group-record.c
@@ -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;
+
+}
diff --git a/src/shared/group-record.h b/src/shared/group-record.h
index 054849b409..a2cef81c8a 100644
--- a/src/shared/group-record.h
+++ b/src/shared/group-record.h
@@ -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);
diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c
index a37957ee96..9223a2e6ca 100644
--- a/src/shared/user-record-nss.c
+++ b/src/shared/user-record-nss.c
@@ -104,37 +104,37 @@ int nss_passwd_to_user_record(
* just a password instead of the whole account, but that's mostly pointless in times of
* password-less authorization, hence let's not bother. */
- SET_IF(hr->locked,
- spwd && spwd->sp_expire >= 0,
- spwd->sp_expire <= 1, -1);
+ SET_IF(hr->locked,
+ spwd && spwd->sp_expire >= 0,
+ spwd->sp_expire <= 1, -1);
- SET_IF(hr->not_after_usec,
- spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_expire * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->not_after_usec,
+ spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_expire * USEC_PER_DAY, UINT64_MAX);
- SET_IF(hr->password_change_now,
- spwd && spwd->sp_lstchg >= 0,
- spwd->sp_lstchg == 0, -1);
+ SET_IF(hr->password_change_now,
+ spwd && spwd->sp_lstchg >= 0,
+ spwd->sp_lstchg == 0, -1);
- SET_IF(hr->last_password_change_usec,
- spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->last_password_change_usec,
+ spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX);
- SET_IF(hr->password_change_min_usec,
- spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_min * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->password_change_min_usec,
+ spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_min * USEC_PER_DAY, UINT64_MAX);
- SET_IF(hr->password_change_max_usec,
- spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_max * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->password_change_max_usec,
+ spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_max * USEC_PER_DAY, UINT64_MAX);
- SET_IF(hr->password_change_warn_usec,
- spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_warn * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->password_change_warn_usec,
+ spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_warn * USEC_PER_DAY, UINT64_MAX);
- SET_IF(hr->password_change_inactive_usec,
- spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY,
- spwd->sp_inact * USEC_PER_DAY, UINT64_MAX);
+ SET_IF(hr->password_change_inactive_usec,
+ spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY,
+ spwd->sp_inact * USEC_PER_DAY, UINT64_MAX);
hr->json = sd_json_variant_unref(hr->json);
r = sd_json_buildo(
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index f14a38e03b..12447a9337 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -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",
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index 2a0e92d69a..0443820890 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -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_;