return "/home";
}
+static int copy_struct_passwd(const struct passwd *pw, struct passwd **ret) {
+ assert(pw);
+ assert(ret);
+
+ size_t need_bytes = sizeof(struct passwd)
+ + strlen_ptr(pw->pw_name) + 1
+ + strlen_ptr(pw->pw_passwd) + 1
+ + strlen_ptr(pw->pw_gecos) + 1
+ + strlen_ptr(pw->pw_dir) + 1
+ + strlen_ptr(pw->pw_shell) + 1;
+
+ char *buf = malloc(need_bytes);
+ if (!buf)
+ return -ENOMEM;
+
+ struct passwd *newpw = (struct passwd *) buf;
+
+ /* The layout in our buffer:
+ * struct passwd, and then individual strings. */
+ char *p = buf + sizeof(struct passwd);
+
+ newpw->pw_name = p;
+ p = stpcpy(p, strempty(pw->pw_name)) + 1;
+
+ newpw->pw_passwd = p;
+ p = stpcpy(p, strempty(pw->pw_passwd)) + 1;
+
+ newpw->pw_uid = pw->pw_uid;
+ newpw->pw_gid = pw->pw_gid;
+
+ newpw->pw_gecos = p;
+ p = stpcpy(p, strempty(pw->pw_gecos)) + 1;
+
+ newpw->pw_dir = p;
+ p = stpcpy(p, strempty(pw->pw_dir)) + 1;
+
+ newpw->pw_shell = p;
+ p = stpcpy(p, strempty(pw->pw_shell)) + 1;
+
+ *ret = newpw;
+ return 0;
+}
+
+/* Iterate the given list of passwd-format files looking for an entry matching the predicate (by
+ * name if 'name' is non-NULL and by 'uid' if valid). Returns -ESRCH if no entry is found. */
+int lookup_pwent_in_files(
+ char * const *files,
+ const char *name,
+ uid_t uid,
+ struct passwd **ret) {
+
+ int r;
+
+ assert(files);
+ assert(name || uid_is_valid(uid));
+
+ STRV_FOREACH(fname, files) {
+ _cleanup_fclose_ FILE *f = NULL;
+ struct passwd *pw;
+
+ r = fopen_unlocked(*fname, "re", &f);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
+
+ while ((r = fgetpwent_sane(f, &pw)) > 0) {
+ if (name && !streq_ptr(pw->pw_name, name))
+ continue;
+ if (uid_is_valid(uid) && pw->pw_uid != uid)
+ continue;
+ if (ret)
+ return copy_struct_passwd(pw, ret);
+ return 0;
+ }
+ if (r < 0)
+ return r;
+ }
+
+ return -ESRCH;
+}
static size_t getpw_buffer_size(void) {
long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
return bufsize <= 0 ? 4096U : (size_t) bufsize;
}
}
+static int copy_struct_group(const struct group *gr, struct group **ret) {
+ assert(gr);
+ assert(ret);
+
+ size_t need_bytes = sizeof(struct group)
+ + strlen_ptr(gr->gr_name) + 1
+ + strlen_ptr(gr->gr_passwd) + 1,
+ n_mem = 0;
+ STRV_FOREACH(s, gr->gr_mem) {
+ need_bytes += sizeof(char*) + strlen(*s) + 1;
+ n_mem++;
+ }
+ need_bytes += sizeof(char*); /* NULL terminator for gr_mem */
+
+ char *buf = malloc(need_bytes);
+ if (!buf)
+ return -ENOMEM;
+
+ struct group *newgr = (struct group *) buf;
+
+ /* The layout in our buffer:
+ * struct group, ->gr_mem pointers terminated by NULL, ->gr_name, ->gr_passwd, ->gr_mem items */
+ /* The ->gr_mem array is first, because it needs alignment. */
+ assert_cc(sizeof(struct group) % alignof(char*) == 0);
+
+ char *p = buf + sizeof(struct group) + (n_mem + 1) * sizeof(char*);
+
+ newgr->gr_name = p;
+ p = stpcpy(p, strempty(gr->gr_name)) + 1;
+
+ newgr->gr_passwd = p;
+ p = stpcpy(p, strempty(gr->gr_passwd)) + 1;
+
+ newgr->gr_gid = gr->gr_gid;
+
+ newgr->gr_mem = (char**) (buf + sizeof(struct group));
+ for (size_t i = 0; i < n_mem; i++) {
+ newgr->gr_mem[i] = p;
+ p = stpcpy(p, gr->gr_mem[i]) + 1;
+ }
+ newgr->gr_mem[n_mem] = NULL;
+
+ *ret = newgr;
+ return 0;
+}
+
+/* See lookup_pwent_in_files() for the analogous passwd-file version. */
+int lookup_grent_in_files(
+ char * const *files,
+ const char *name,
+ gid_t gid,
+ struct group **ret) {
+
+ int r;
+
+ assert(files);
+ assert(name || gid_is_valid(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 (name && !streq_ptr(gr->gr_name, name))
+ continue;
+ if (gid_is_valid(gid) && gr->gr_gid != gid)
+ continue;
+ if (ret)
+ return copy_struct_group(gr, ret);
+ return 0;
+ }
+ if (r < 0)
+ return r;
+ }
+
+ return -ESRCH;
+}
+
static size_t getgr_buffer_size(void) {
long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
return bufsize <= 0 ? 4096U : (size_t) bufsize;
#include <unistd.h>
#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "format-util.h"
#include "log.h"
#include "memory-util.h"
#include "path-util.h"
#include "string-util.h"
#include "tests.h"
+#include "tmpfile-util.h"
#include "user-util.h"
static void test_uid_to_name_one(uid_t uid, const char *name) {
test_mangle_gecos_one("\xe2\x28\xa1", " ( ");
}
+TEST(lookup_pwent_in_files) {
+ _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-user-util-passwd-XXXXXX";
+ _cleanup_fclose_ FILE *f = NULL;
+
+ ASSERT_OK(fmkostemp_safe(fn, "w", &f));
+ ASSERT_OK(write_string_stream(
+ f,
+ "testuser1:x:1234:5678:Test User 1:/home/test1:/bin/sh\n"
+ "testuser2:x:5678:9999:Test User 2:/home/test2:/bin/bash\n",
+ /* flags= */ 0));
+ ASSERT_OK(fflush_and_check(f));
+
+ char **files = STRV_MAKE(fn);
+
+ /* Lookup by name */
+ _cleanup_free_ struct passwd *pw = NULL;
+ ASSERT_OK(lookup_pwent_in_files(files, "testuser1", UID_INVALID, &pw));
+ ASSERT_STREQ(pw->pw_name, "testuser1");
+ ASSERT_EQ(pw->pw_uid, 1234u);
+ ASSERT_EQ(pw->pw_gid, 5678u);
+ ASSERT_STREQ(pw->pw_gecos, "Test User 1");
+ ASSERT_STREQ(pw->pw_dir, "/home/test1");
+ ASSERT_STREQ(pw->pw_shell, "/bin/sh");
+ pw = mfree(pw);
+
+ ASSERT_OK(lookup_pwent_in_files(STRV_MAKE(fn, fn, fn, fn), "testuser2", UID_INVALID, &pw));
+ ASSERT_STREQ(pw->pw_name, "testuser2");
+ ASSERT_EQ(pw->pw_uid, 5678u);
+ ASSERT_STREQ(pw->pw_shell, "/bin/bash");
+ pw = mfree(pw);
+
+ /* Caller doesn't care about contents */
+ ASSERT_OK(lookup_pwent_in_files(files, "testuser1", UID_INVALID, /* ret= */ NULL));
+
+ /* Missing entry */
+ ASSERT_ERROR(lookup_pwent_in_files(files, "nosuchuser", UID_INVALID, /* ret= */ NULL), ESRCH);
+ ASSERT_ERROR(lookup_pwent_in_files(files, "nosuchuser", UID_INVALID, &pw), ESRCH);
+ ASSERT_NULL(pw);
+
+ /* Lookup by uid */
+ ASSERT_OK(lookup_pwent_in_files(files, /* name= */ NULL, 1234, &pw));
+ ASSERT_STREQ(pw->pw_name, "testuser1");
+ pw = mfree(pw);
+
+ ASSERT_OK(lookup_pwent_in_files(files, /* name= */ NULL, 5678, &pw));
+ ASSERT_STREQ(pw->pw_name, "testuser2");
+ pw = mfree(pw);
+
+ /* Missing uid */
+ ASSERT_ERROR(lookup_pwent_in_files(files, /* name= */ NULL, 424242, &pw), ESRCH);
+ ASSERT_ERROR(lookup_pwent_in_files(files, /* name= */ NULL, 424242, NULL), ESRCH);
+
+ /* Non-existent file in the list is skipped */
+ ASSERT_OK(lookup_pwent_in_files(
+ STRV_MAKE("/nonexistent-dir/passwd", fn),
+ "testuser1",
+ UID_INVALID,
+ &pw));
+ ASSERT_STREQ(pw->pw_name, "testuser1");
+ pw = mfree(pw);
+
+ /* All files missing → -ESRCH */
+ ASSERT_ERROR(lookup_pwent_in_files(
+ STRV_MAKE("/nonexistent-dir/passwd"),
+ "testuser1",
+ UID_INVALID,
+ &pw), ESRCH);
+
+ /* First match wins: same name in two files, but the first file's entry should be picked */
+ _cleanup_(unlink_tempfilep) char fn2[] = "/tmp/test-user-util-passwd2-XXXXXX";
+ _cleanup_fclose_ FILE *f2 = NULL;
+
+ ASSERT_OK(fmkostemp_safe(fn2, "w", &f2));
+ ASSERT_OK(write_string_stream(
+ f2,
+ "testuser1:x:111:222:First:/h/first:/bin/zsh\n",
+ /* flags= */ 0));
+ ASSERT_OK(fflush_and_check(f2));
+
+ ASSERT_OK(lookup_pwent_in_files(
+ STRV_MAKE(fn2, fn),
+ "testuser1",
+ UID_INVALID,
+ &pw));
+ ASSERT_EQ(pw->pw_uid, 111u);
+ ASSERT_STREQ(pw->pw_gecos, "First");
+}
+
+TEST(lookup_grent_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:\n",
+ /* flags= */ 0));
+ ASSERT_OK(fflush_and_check(f));
+
+ char **files = STRV_MAKE(fn);
+
+ /* Lookup by name */
+ _cleanup_free_ struct group *gr = NULL;
+ ASSERT_OK(lookup_grent_in_files(files, "testgroup1", GID_INVALID, &gr));
+ ASSERT_STREQ(gr->gr_name, "testgroup1");
+ ASSERT_EQ(gr->gr_gid, 100u);
+ ASSERT_STREQ(gr->gr_mem[0], "testuser1");
+ ASSERT_STREQ(gr->gr_mem[1], "testuser2");
+ ASSERT_NULL(gr->gr_mem[2]);
+ gr = mfree(gr);
+
+ ASSERT_OK(lookup_grent_in_files(files, "testgroup2", GID_INVALID, &gr));
+ ASSERT_STREQ(gr->gr_name, "testgroup2");
+ ASSERT_EQ(gr->gr_gid, 200u);
+ ASSERT_NULL(gr->gr_mem[0]);
+ gr = mfree(gr);
+
+ /* Caller doesn't care about contents */
+ ASSERT_OK(lookup_grent_in_files(files, "testgroup1", GID_INVALID, /* ret= */ NULL));
+
+ /* Missing entry */
+ ASSERT_ERROR(lookup_grent_in_files(files, "nosuchgroup", GID_INVALID, &gr), ESRCH);
+ ASSERT_NULL(gr);
+
+ /* Lookup by gid */
+ ASSERT_OK(lookup_grent_in_files(files, /* name= */ NULL, 100, &gr));
+ ASSERT_STREQ(gr->gr_name, "testgroup1");
+ gr = mfree(gr);
+
+ ASSERT_OK(lookup_grent_in_files(files, /* name= */ NULL, 200, &gr));
+ ASSERT_STREQ(gr->gr_name, "testgroup2");
+ gr = mfree(gr);
+
+ /* Missing gid */
+ ASSERT_ERROR(lookup_grent_in_files(files, /* name= */ NULL, 424242, &gr), ESRCH);
+
+ /* Non-existent file is skipped */
+ ASSERT_OK(lookup_grent_in_files(
+ STRV_MAKE("/nonexistent-dir/group", fn),
+ "testgroup1",
+ GID_INVALID,
+ &gr));
+ ASSERT_STREQ(gr->gr_name, "testgroup1");
+}
+
DEFINE_TEST_MAIN(LOG_INFO);