diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 869fabdebd..6252ecc4d9 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -1366,6 +1366,67 @@ int sysconf_ngroups_max(void) { return ngroups_max; } +ssize_t lookup_groups_in_files( + char * const *files, + const char *name, + gid_t gid, + gid_t **ret) { + + _cleanup_free_ gid_t *arr = NULL; + ssize_t n_arr = 0; + int r; + + assert(files); + assert(name); + assert(gid_is_valid(gid)); + + int ngroups_max = sysconf_ngroups_max(); + if (ngroups_max < 0) + return ngroups_max; + + if (!GREEDY_REALLOC(arr, n_arr + 1)) + return -ENOMEM; + arr[n_arr++] = gid; + + STRV_FOREACH(fname, files) { + _cleanup_fclose_ FILE *f = NULL; + struct group *gr; + + r = fopen_unlocked(*fname, "re", &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + while ((r = fgetgrent_sane(f, &gr)) > 0) { + if (gid == gr->gr_gid) + continue; + + /* Deduplicate groups, in case the group is doubly defined in the same or + * different files. */ + if (gid_list_has(arr, n_arr, gr->gr_gid)) + continue; + + if (!strv_contains(gr->gr_mem, name)) + continue; + + /* Do not allow more than _SC_NGROUPS_MAX. */ + if (n_arr >= ngroups_max) + return -ENOBUFS; + + if (!GREEDY_REALLOC(arr, n_arr + 1)) + return -ENOMEM; + arr[n_arr++] = gr->gr_gid; + } + if (r < 0) + return r; + } + + if (ret) + *ret = TAKE_PTR(arr); + return n_arr; +} + static size_t getgr_buffer_size(void) { long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); return bufsize <= 0 ? 4096U : (size_t) bufsize; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 12fe6f5f8a..e95ad038e5 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -185,6 +185,11 @@ int lookup_grent_in_files( const char *name, gid_t gid, struct group **ret); +ssize_t lookup_groups_in_files( + char * const *files, + const char *name, + gid_t gid, + gid_t **ret); int getpwuid_malloc(uid_t uid, struct passwd **ret); int getpwnam_malloc(const char *name, struct passwd **ret); diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index e0c8b68fc1..22eb8df085 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -596,4 +596,38 @@ TEST(lookup_grent_in_files) { ASSERT_STREQ(gr->gr_name, "testgroup1"); } +TEST(lookup_groups_in_files) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-user-util-group-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + ASSERT_OK(fmkostemp_safe(fn, "w", &f)); + ASSERT_OK(write_string_stream( + f, + "testgroup1:x:100:testuser1,testuser2\n" + "testgroup2:x:200:testuser1\n" + "testgroup3:x:300:\n" + "testgroup4:x:400:root,somebody,testuser1\n", + /* flags= */ 0)); + ASSERT_OK(fflush_and_check(f)); + + char **files = STRV_MAKE(fn); + + /* Lookup by name */ + _cleanup_free_ gid_t *arr = NULL; + + ASSERT_EQ(lookup_groups_in_files(files, "testuser1", 100, &arr), 3); + gid_t exp1[] = { 100, 200, 400 }; + ASSERT_EQ(memcmp(arr, exp1, sizeof(exp1)), 0); + arr = mfree(arr); + + ASSERT_EQ(lookup_groups_in_files(files, "testuser1", 0, &arr), 4); + gid_t exp2[] = { 0, 100, 200, 400 }; + ASSERT_EQ(memcmp(arr, exp2, sizeof(exp2)), 0); + arr = mfree(arr); + + ASSERT_EQ(lookup_groups_in_files(files, "testuser3", 300, &arr), 1); + gid_t exp3[] = { 300 }; + ASSERT_EQ(memcmp(arr, exp3, sizeof(exp3)), 0); +} + DEFINE_TEST_MAIN(LOG_INFO);