This is an automated email from the git hooks/post-receive script. guillem pushed a commit to branch main in repository dpkg.
View the commit online: https://git.dpkg.org/cgit/dpkg/dpkg.git/commit/?id=4dd216f377bee166fe6326e5f2d16db20a4ad188 commit 4dd216f377bee166fe6326e5f2d16db20a4ad188 Author: Guillem Jover <[email protected]> AuthorDate: Sun Feb 16 15:09:39 2025 +0100 libcompat: Add compatibility functions for fgetpwent() and fgetgrent() These functions are missing on some systems such as FreeBSD, NetBSD, OpenBSD, macOS or AIX. We add the unit tests into the libdpkg, because that's what contains the required infrastructure for the test suite. --- configure.ac | 2 + lib/compat/Makefile.am | 9 ++ lib/compat/compat.h | 24 ++++ lib/compat/getent.c | 226 ++++++++++++++++++++++++++++++++++++ lib/dpkg/Makefile.am | 11 ++ lib/dpkg/t/.gitignore | 1 + lib/dpkg/t/data/sysuser/group.base | 8 ++ lib/dpkg/t/data/sysuser/group.nis | 10 ++ lib/dpkg/t/data/sysuser/passwd.base | 8 ++ lib/dpkg/t/data/sysuser/passwd.nis | 10 ++ lib/dpkg/t/t-compat-getent.c | 191 ++++++++++++++++++++++++++++++ 11 files changed, 500 insertions(+) diff --git a/configure.ac b/configure.ac index af2f7d55d..de00727b7 100644 --- a/configure.ac +++ b/configure.ac @@ -203,6 +203,8 @@ DPKG_CHECK_COMPAT_FUNCS([\ strerror \ strsignal \ asprintf \ + fgetpwent \ + fgetgrent \ scandir \ alphasort \ unsetenv \ diff --git a/lib/compat/Makefile.am b/lib/compat/Makefile.am index 58a25240f..8304f8018 100644 --- a/lib/compat/Makefile.am +++ b/lib/compat/Makefile.am @@ -23,6 +23,7 @@ libcompat_test_la_SOURCES = \ strsignal.c \ snprintf.c vsnprintf.c \ asprintf.c vasprintf.c \ + getent.c \ alphasort.c \ scandir.c \ unsetenv.c \ @@ -83,6 +84,14 @@ if !HAVE_ASPRINTF libcompat_la_SOURCES += asprintf.c vasprintf.c endif +if !HAVE_FGETPWENT +libcompat_la_SOURCES += getent.c +else +if !HAVE_FGETGRENT +libcompat_la_SOURCES += getent.c +endif +endif + if !HAVE_ALPHASORT libcompat_la_SOURCES += alphasort.c endif diff --git a/lib/compat/compat.h b/lib/compat/compat.h index 8c643f524..372004285 100644 --- a/lib/compat/compat.h +++ b/lib/compat/compat.h @@ -39,6 +39,18 @@ #include <string.h> #endif +#if TEST_LIBCOMPAT || !defined(HAVE_FGETPWENT) || !defined(HAVE_FGETGRENT) +#include <stdio.h> +#endif + +#if TEST_LIBCOMPAT || !defined(HAVE_FGETPWENT) +#include <pwd.h> +#endif + +#if TEST_LIBCOMPAT || !defined(HAVE_FGETGRENT) +#include <grp.h> +#endif + /* Language definitions. */ /* Supported since gcc 5.1.0 and clang 2.9.0. For attributes that appeared @@ -137,6 +149,10 @@ extern "C" { #define strerror test_strerror #undef strsignal #define strsignal test_strsignal +#undef fgetpwent +#define fgetpwent test_fgetpwent +#undef fgetgrent +#define fgetgrent test_fgetgrent #undef scandir #define scandir test_scandir #undef alphasort @@ -183,6 +199,14 @@ const char *strerror(int); const char *strsignal(int); #endif +#if TEST_LIBCOMPAT || !defined(HAVE_FGETPWENT) +struct passwd *fgetpwent(FILE *fp); +#endif + +#if TEST_LIBCOMPAT || !defined(HAVE_FGETGRENT) +struct group *fgetgrent(FILE *fp); +#endif + #if TEST_LIBCOMPAT || !defined(HAVE_SCANDIR) struct dirent; int scandir(const char *dir, struct dirent ***namelist, diff --git a/lib/compat/getent.c b/lib/compat/getent.c new file mode 100644 index 000000000..e5c55a5f9 --- /dev/null +++ b/lib/compat/getent.c @@ -0,0 +1,226 @@ +/* + * libcompat - system compatibility library + * + * Copyright © 2025 Guillem Jover <[email protected]> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <sys/types.h> + +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> + +#include "compat.h" + +static char * +parse_field(char **start, int delim) +{ + char *field; + char *next; + + field = *start; + if (*field == '\0') + return NULL; + + next = strchrnul(field, delim); + if (next[0] == delim) { + next[0] = '\0'; + next++; + } + *start = next; + + return field; +} + +static char ** +alloc_subfields(size_t len, char ***list, size_t *list_len) +{ + char **elems; + + elems = realloc(*list, sizeof(char *) * (len + 1)); + if (elems == NULL) + return NULL; + *list_len = len; + *list = elems; + + return elems; +} + +static char ** +parse_subfields(char **start, int delim, char ***list, size_t *list_len) +{ + char *field; + char **elems; + size_t subfields = 1; + size_t i; + + for (field = *start; *field; field++) { + if (*field == delim) + subfields++; + } + + if (*list == NULL || *list_len < subfields) { + elems = alloc_subfields(subfields, list, list_len); + if (elems == NULL) + return NULL; + } else { + elems = *list; + } + + for (i = 0; i < subfields; i++) + elems[i] = parse_field(start, delim); + elems[subfields] = NULL; + + return elems; +} + +static intmax_t +parse_intmax(char *str) +{ + intmax_t val; + char *endp; + + errno = 0; + val = strtoimax(str, &endp, 10); + if (str == endp || *endp) + return -1; + if (val < 0 || errno == ERANGE) + return -1; + + return val; +} + +static intmax_t +parse_field_id(char **start, int delim) +{ + char *field; + intmax_t id = 0; + + field = parse_field(start, delim); + if (field == NULL) + return 0; + id = parse_intmax(field); + if (id < 0) + return 0; + + return id; +} + +#define PARSE_FIELD_STR(field) \ + field = parse_field(&cur, ':'); \ + if (field == NULL) \ + continue; + +#define PARSE_FIELD_ID(field) \ + field = parse_field_id(&cur, ':'); + +static char *ent_empty_str; + +struct passwd * +fgetpwent(FILE *fp) +{ + static char *line = NULL; + static size_t line_len = 0; + static struct passwd pw; + char *cur; + + while (1) { + ssize_t len; + + len = getline(&line, &line_len, fp); + if (len < 0) + return NULL; + if (len > 0 && line[len - 1] == '\n') + line[len - 1] = '\0'; + cur = line; + + PARSE_FIELD_STR(pw.pw_name); + + /* Special case NIS compat entries. */ + if (pw.pw_name[0] == '+' || pw.pw_name[0] == '-') { + if (ent_empty_str == NULL) + ent_empty_str = strdup(""); + + pw.pw_passwd = ent_empty_str; + pw.pw_uid = 0; + pw.pw_gid = 0; + pw.pw_gecos = ent_empty_str; + pw.pw_dir = ent_empty_str; + pw.pw_shell = ent_empty_str; + } else { + PARSE_FIELD_STR(pw.pw_passwd); + PARSE_FIELD_ID(pw.pw_uid); + PARSE_FIELD_ID(pw.pw_gid); + PARSE_FIELD_STR(pw.pw_gecos); + PARSE_FIELD_STR(pw.pw_dir); + PARSE_FIELD_STR(pw.pw_shell); + } + + return &pw; + } + + return NULL; +} + +struct group * +fgetgrent(FILE *fp) +{ + static char *line = NULL; + static size_t line_len = 0; + static struct group gr; + static char **gr_mem = NULL; + static size_t gr_mem_len = 0; + char *cur; + + while (1) { + ssize_t len; + + len = getline(&line, &line_len, fp); + if (len < 0) + return NULL; + if (len > 0 && line[len - 1] == '\n') + line[len - 1] = '\0'; + cur = line; + + PARSE_FIELD_STR(gr.gr_name); + + /* Special case NIS compat entries. */ + if (gr.gr_name[0] == '+' || gr.gr_name[0] == '-') { + if (ent_empty_str == NULL) + ent_empty_str = strdup(""); + + gr.gr_passwd = ent_empty_str; + gr.gr_gid = 0; + gr.gr_mem = alloc_subfields(0, &gr_mem, &gr_mem_len); + gr.gr_mem[0] = NULL; + } else { + PARSE_FIELD_STR(gr.gr_passwd); + PARSE_FIELD_ID(gr.gr_gid); + gr.gr_mem = parse_subfields(&cur, ',', &gr_mem, &gr_mem_len); + } + + return &gr; + } + + return NULL; +} diff --git a/lib/dpkg/Makefile.am b/lib/dpkg/Makefile.am index 3f609ac60..b5042f650 100644 --- a/lib/dpkg/Makefile.am +++ b/lib/dpkg/Makefile.am @@ -226,6 +226,7 @@ test_programs = \ t/t-test \ t/t-test-skip \ t/t-macros \ + t/t-compat-getent \ t/t-headers-cpp \ t/t-c-ctype \ t/t-namevalue \ @@ -277,6 +278,10 @@ test_data = \ t/data/meminfo-no-info \ t/data/meminfo-no-unit \ t/data/meminfo-ok \ + t/data/sysuser/group.base \ + t/data/sysuser/group.nis \ + t/data/sysuser/passwd.base \ + t/data/sysuser/passwd.nis \ # EOL EXTRA_DIST += \ @@ -289,8 +294,14 @@ BENCHMARK_LDADD_FLAGS = \ $(LDADD) \ # EOL +LIBCOMPAT_TEST_LDADD_FLAGS = \ + $(LDADD) \ + ../compat/libcompat-test.la \ + # EOL + t_b_fsys_hash_LDADD = $(BENCHMARK_LDADD_FLAGS) t_b_pkg_hash_LDADD = $(BENCHMARK_LDADD_FLAGS) +t_t_compat_getent_LDADD = $(LIBCOMPAT_TEST_LDADD_FLAGS) check_PROGRAMS = \ $(test_programs) \ diff --git a/lib/dpkg/t/.gitignore b/lib/dpkg/t/.gitignore index ae1d35596..db9568ba2 100644 --- a/lib/dpkg/t/.gitignore +++ b/lib/dpkg/t/.gitignore @@ -11,6 +11,7 @@ t-arch t-buffer t-c-ctype t-command +t-compat-getent t-deb-version t-ehandle t-error diff --git a/lib/dpkg/t/data/sysuser/group.base b/lib/dpkg/t/data/sysuser/group.base new file mode 100644 index 000000000..a34beac19 --- /dev/null +++ b/lib/dpkg/t/data/sysuser/group.base @@ -0,0 +1,8 @@ +root:x:0: +daemon:x:1: +bin:x:2: +sys:x:3: +adm:x:4: +users:x:100:someuser,otheruser +someuser:x:1000:someuser +otheruser:x:1001:otheruser diff --git a/lib/dpkg/t/data/sysuser/group.nis b/lib/dpkg/t/data/sysuser/group.nis new file mode 100644 index 000000000..81ce3fc89 --- /dev/null +++ b/lib/dpkg/t/data/sysuser/group.nis @@ -0,0 +1,10 @@ +root:x:0: +daemon:x:1: +bin:x:2: +sys:x:3: +adm:x:4: ++group-compat-1::: +users:x:100:someuser,otheruser +-group-compat-2::: +someuser:x:1000:someuser +otheruser:x:1001:otheruser diff --git a/lib/dpkg/t/data/sysuser/passwd.base b/lib/dpkg/t/data/sysuser/passwd.base new file mode 100644 index 000000000..7bfc97f97 --- /dev/null +++ b/lib/dpkg/t/data/sysuser/passwd.base @@ -0,0 +1,8 @@ +root:x:0:0:root:/root:/bin/sh +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +_sysuser:x:100:65534::/nonexistent:/usr/sbin/nologin +someuser:x:1000:1000:Some User,,,:/home/someuser:/bin/bash +otheruser:x:1001:1001:Other User,,,:/home/otheruser:/bin/sh diff --git a/lib/dpkg/t/data/sysuser/passwd.nis b/lib/dpkg/t/data/sysuser/passwd.nis new file mode 100644 index 000000000..4852ec86e --- /dev/null +++ b/lib/dpkg/t/data/sysuser/passwd.nis @@ -0,0 +1,10 @@ +root:x:0:0:root:/root:/bin/sh +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync ++user-compat-1:::::: +_sysuser:x:100:65534::/nonexistent:/usr/sbin/nologin +-user-compat-2:::::: +someuser:x:1000:1000:Some User,,,:/home/someuser:/bin/bash +otheruser:x:1001:1001:Other User,,,:/home/otheruser:/bin/sh diff --git a/lib/dpkg/t/t-compat-getent.c b/lib/dpkg/t/t-compat-getent.c new file mode 100644 index 000000000..09cf8005f --- /dev/null +++ b/lib/dpkg/t/t-compat-getent.c @@ -0,0 +1,191 @@ +/* + * libdpkg - Debian packaging suite library routines + * t-compat-getent.c - test compat getent handling code + * + * Copyright © 2025 Guillem Jover <[email protected]> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#undef fgetpwent +#define fgetpwent test_fgetpwent +#undef fgetgrent +#define fgetgrent test_fgetgrent + +#include <config.h> +#include <compat.h> + +#include <dpkg/test.h> +#include <dpkg/sysuser.h> + +#define test_pwent(name, pass, uid, gid, gecos, dir, shell) \ + do { \ + pw = fgetpwent(fp); \ + test_fail(pw == NULL); \ + if (pw == NULL) \ + continue; \ + test_str(pw->pw_name, ==, name); \ + test_str(pw->pw_passwd, ==, pass); \ + test_pass(pw->pw_uid == uid); \ + test_pass(pw->pw_gid == gid); \ + test_str(pw->pw_gecos, ==, gecos); \ + test_str(pw->pw_dir, ==, dir); \ + test_str(pw->pw_shell, ==, shell); \ + } while (0) + +#define test_pwent_nis(name) \ + test_pwent(name, "", 0, 0, "", "", ""); + +static void +test_compat_fgetpwent(void) +{ + char *passwdfile; + FILE *fp; + struct passwd *pw; + + passwdfile = test_data_file("sysuser/passwd.base"); + + fp = fopen(passwdfile, "r"); + if (fp == NULL) { + printf("# cannot open %s\n", passwdfile); + test_bail("cannot open sysuser/passwd.base"); + } + + test_pwent("root", "x", 0, 0, "root", "/root", "/bin/sh"); + test_pwent("daemon", "x", 1, 1, "daemon", "/usr/sbin", "/usr/sbin/nologin"); + test_pwent("bin", "x", 2, 2, "bin", "/bin", "/usr/sbin/nologin"); + test_pwent("sys", "x", 3, 3, "sys", "/dev", "/usr/sbin/nologin"); + test_pwent("sync", "x", 4, 65534, "sync", "/bin", "/bin/sync"); + test_pwent("_sysuser", "x", 100, 65534, "", "/nonexistent", "/usr/sbin/nologin"); + test_pwent("someuser", "x", 1000, 1000, "Some User,,,", "/home/someuser", "/bin/bash"); + test_pwent("otheruser", "x", 1001, 1001, "Other User,,,", "/home/otheruser", "/bin/sh"); + pw = fgetpwent(fp); + test_pass(pw == NULL); + + fclose(fp); + free(passwdfile); + + passwdfile = test_data_file("sysuser/passwd.nis"); + + fp = fopen(passwdfile, "r"); + if (fp == NULL) { + printf("# cannot open %s\n", passwdfile); + test_bail("cannot open sysuser/passwd.base"); + } + + test_pwent("root", "x", 0, 0, "root", "/root", "/bin/sh"); + test_pwent("daemon", "x", 1, 1, "daemon", "/usr/sbin", "/usr/sbin/nologin"); + test_pwent("bin", "x", 2, 2, "bin", "/bin", "/usr/sbin/nologin"); + test_pwent("sys", "x", 3, 3, "sys", "/dev", "/usr/sbin/nologin"); + test_pwent("sync", "x", 4, 65534, "sync", "/bin", "/bin/sync"); + test_pwent_nis("+user-compat-1"); + test_pwent("_sysuser", "x", 100, 65534, "", "/nonexistent", "/usr/sbin/nologin"); + test_pwent_nis("-user-compat-2"); + test_pwent("someuser", "x", 1000, 1000, "Some User,,,", "/home/someuser", "/bin/bash"); + test_pwent("otheruser", "x", 1001, 1001, "Other User,,,", "/home/otheruser", "/bin/sh"); + pw = fgetpwent(fp); + test_pass(pw == NULL); + + fclose(fp); + free(passwdfile); +} + +#define test_grent(name, pass, gid, mem) \ + do { \ + gr = fgetgrent(fp); \ + test_fail(gr == NULL); \ + if (gr == NULL) \ + continue; \ + test_str(gr->gr_name, ==, name); \ + test_str(gr->gr_passwd, ==, pass); \ + test_pass(gr->gr_gid == gid); \ + } while (0) + +#define test_grent_nis(name) \ + test_grent(name, "", 0, NULL); \ + test_pass(gr->gr_mem[0] == NULL); + +static void +test_compat_fgetgrent(void) +{ + char *groupfile; + FILE *fp; + struct group *gr; + + groupfile = test_data_file("sysuser/group.base"); + fp = fopen(groupfile, "r"); + if (fp == NULL) { + printf("# cannot open %s\n", groupfile); + test_bail("cannot open sysuser/group.base"); + } + + test_grent("root", "x", 0, NULL); + test_grent("daemon", "x", 1, NULL); + test_grent("bin", "x", 2, NULL); + test_grent("sys", "x", 3, NULL); + test_grent("adm", "x", 4, NULL); + test_grent("users", "x", 100, NULL); + test_str(gr->gr_mem[0], ==, "someuser"); + test_str(gr->gr_mem[1], ==, "otheruser"); + test_pass(gr->gr_mem[2] == NULL); + test_grent("someuser", "x", 1000, NULL); + test_str(gr->gr_mem[0], ==, "someuser"); + test_pass(gr->gr_mem[1] == NULL); + test_grent("otheruser", "x", 1001, NULL); + test_str(gr->gr_mem[0], ==, "otheruser"); + test_pass(gr->gr_mem[1] == NULL); + gr = fgetgrent(fp); + test_pass(gr == NULL); + + fclose(fp); + free(groupfile); + + groupfile = test_data_file("sysuser/group.nis"); + fp = fopen(groupfile, "r"); + if (fp == NULL) { + printf("# cannot open %s\n", groupfile); + test_bail("cannot open sysuser/group.base"); + } + + test_grent("root", "x", 0, NULL); + test_grent("daemon", "x", 1, NULL); + test_grent("bin", "x", 2, NULL); + test_grent("sys", "x", 3, NULL); + test_grent("adm", "x", 4, NULL); + test_grent_nis("+group-compat-1"); + test_grent("users", "x", 100, NULL); + test_str(gr->gr_mem[0], ==, "someuser"); + test_str(gr->gr_mem[1], ==, "otheruser"); + test_pass(gr->gr_mem[2] == NULL); + test_grent_nis("-group-compat-2"); + test_grent("someuser", "x", 1000, NULL); + test_str(gr->gr_mem[0], ==, "someuser"); + test_pass(gr->gr_mem[1] == NULL); + test_grent("otheruser", "x", 1001, NULL); + test_str(gr->gr_mem[0], ==, "otheruser"); + test_pass(gr->gr_mem[1] == NULL); + gr = fgetgrent(fp); + test_pass(gr == NULL); + + fclose(fp); + free(groupfile); +} + +TEST_ENTRY(test) +{ + test_plan(236); + + test_compat_fgetpwent(); + test_compat_fgetgrent(); +} -- Dpkg.Org's dpkg

