aboutsummaryrefslogtreecommitdiff
path: root/nss
diff options
context:
space:
mode:
Diffstat (limited to 'nss')
-rw-r--r--nss/Makefile43
-rw-r--r--nss/Versions30
-rw-r--r--nss/fgetgrent.c87
-rw-r--r--nss/fgetgrent_r.c65
-rw-r--r--nss/getgrent.c28
-rw-r--r--nss/getgrent_r.c28
-rw-r--r--nss/getgrgid.c28
-rw-r--r--nss/getgrgid_r.c31
-rw-r--r--nss/getgrnam.c28
-rw-r--r--nss/getgrnam_r.c31
-rw-r--r--nss/grp-merge.c200
-rw-r--r--nss/grp-merge.h35
-rw-r--r--nss/grp.h207
-rw-r--r--nss/initgroups-fallback.c116
-rw-r--r--nss/initgroups.c218
-rw-r--r--nss/putgrent.c76
-rw-r--r--nss/testgrp.c41
-rw-r--r--nss/tst-initgroups1.c56
-rw-r--r--nss/tst-initgroups1.root/etc/group7
-rw-r--r--nss/tst-initgroups1.root/etc/nsswitch.conf1
-rw-r--r--nss/tst-initgroups1.root/etc/passwd1
-rw-r--r--nss/tst-initgroups2.c21
-rw-r--r--nss/tst-initgroups2.root/etc/group7
-rw-r--r--nss/tst-initgroups2.root/etc/nsswitch.conf2
-rw-r--r--nss/tst-initgroups2.root/etc/passwd1
-rw-r--r--nss/tst-putgrent.c167
-rw-r--r--nss/tst_fgetgrent.c126
-rw-r--r--nss/tst_fgetgrent.sh40
28 files changed, 1720 insertions, 1 deletions
diff --git a/nss/Makefile b/nss/Makefile
index 32764b74c0..baf7d9d0ab 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -23,6 +23,7 @@ subdir := nss
include ../Makeconfig
headers := \
+ grp.h \
nss.h \
# headers
@@ -50,6 +51,34 @@ routines = \
valid_list_field \
# routines
+# grp routines:
+routines += \
+ fgetgrent \
+ fgetgrent_r \
+ getgrent \
+ getgrent_r \
+ getgrgid \
+ getgrgid_r \
+ getgrnam \
+ getgrnam_r \
+ grp-merge \
+ initgroups \
+ putgrent \
+ # routines
+
+ifeq ($(have-thread-library),yes)
+CFLAGS-fgetgrent.c += -fexceptions
+CFLAGS-fgetgrent_r.c += -fexceptions $(libio-mtsafe)
+CFLAGS-getgrent.c += -fexceptions
+CFLAGS-getgrent_r.c += -fexceptions
+CFLAGS-getgrgid.c += -fexceptions
+CFLAGS-getgrgid_r.c += -fexceptions
+CFLAGS-getgrnam.c += -fexceptions
+CFLAGS-getgrnam_r.c += -fexceptions
+CFLAGS-initgroups.c += -fexceptions
+CFLAGS-putgrent.c += -fexceptions $(libio-mtsafe)
+endif
+
# These are the databases that go through nss dispatch.
# Caution: if you add a database here, you must add its real name
# in databases.def, too.
@@ -88,6 +117,7 @@ tests := \
bug17079 \
test-digits-dots \
test-netdb \
+ testgrp \
tst-nss-getpwent \
tst-nss-hash \
tst-nss-test1 \
@@ -95,11 +125,14 @@ tests := \
tst-nss-test4 \
tst-nss-test5 \
tst-nss-test_errno \
+ tst-putgrent \
# tests
xtests = bug-erange
tests-container := \
+ tst-initgroups1 \
+ tst-initgroups2 \
tst-nss-compat1 \
tst-nss-db-endgrent \
tst-nss-db-endpwent \
@@ -112,13 +145,21 @@ tests-container := \
tst-reload2 \
# tests-container
-# Tests which need libdl
ifeq (yes,$(build-shared))
+# Tests which need libdl
tests += tst-nss-files-hosts-erange
tests += tst-nss-files-hosts-multi
tests += tst-nss-files-hosts-getent
tests += tst-nss-files-alias-leak
tests += tst-nss-files-alias-truncated
+# tst_fgetgrent currently only works with shared libraries
+test-srcs := tst_fgetgrent
+ifeq ($(run-built-tests),yes)
+tests-special += $(objpfx)tst_fgetgrent.out
+$(objpfx)tst_fgetgrent.out: tst_fgetgrent.sh $(objpfx)tst_fgetgrent
+ $(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
+ $(evaluate-test)
+endif
endif
# If we have a thread library then we can test cancellation against
diff --git a/nss/Versions b/nss/Versions
index e551524aa9..5401829911 100644
--- a/nss/Versions
+++ b/nss/Versions
@@ -5,10 +5,38 @@ libc {
# Functions exported as no-op compat symbols.
__nss_passwd_lookup; __nss_group_lookup; __nss_hosts_lookup; __nss_next;
__nss_database_lookup;
+
+ # e*
+ endgrent;
+
+ # f*
+ fgetgrent; fgetgrent_r;
+
+ # g*
+ getgrent; getgrent_r; getgrgid; getgrgid_r; getgrnam; getgrnam_r;
+ getgroups;
+
+ # i*
+ initgroups;
+
+ # s*
+ setgrent;
+ }
+ GLIBC_2.1 {
+ # p*
+ putgrent;
+ }
+ GLIBC_2.1.2 {
+ # g*
+ getgrent_r; getgrgid_r; getgrnam_r;
}
GLIBC_2.2.2 {
__nss_hostname_digits_dots;
}
+ GLIBC_2.2.4 {
+ # g*
+ getgrouplist;
+ }
GLIBC_2.27 {
}
GLIBC_PRIVATE {
@@ -107,6 +135,8 @@ libc {
_nss_files_initgroups_dyn;
_nss_files_init;
+
+ __merge_grp; __copy_grp;
}
}
diff --git a/nss/fgetgrent.c b/nss/fgetgrent.c
new file mode 100644
index 0000000000..2e7c7fe5fb
--- /dev/null
+++ b/nss/fgetgrent.c
@@ -0,0 +1,87 @@
+/* Copyright (C) 1991-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <grp.h>
+#include <libc-lock.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <set-freeres.h>
+
+
+/* We need to protect the dynamic buffer handling. */
+__libc_lock_define_initialized (static, lock);
+
+static char *buffer;
+
+/* Read one entry from the given stream. */
+struct group *
+fgetgrent (FILE *stream)
+{
+ static size_t buffer_size;
+ static struct group resbuf;
+ fpos_t pos;
+ struct group *result;
+ int save;
+
+ if (__builtin_expect (fgetpos (stream, &pos), 0) != 0)
+ return NULL;
+
+ /* Get lock. */
+ __libc_lock_lock (lock);
+
+ /* Allocate buffer if not yet available. */
+ if (buffer == NULL)
+ {
+ buffer_size = NSS_BUFLEN_GROUP;
+ buffer = malloc (buffer_size);
+ }
+
+ while (buffer != NULL
+ && (__fgetgrent_r (stream, &resbuf, buffer, buffer_size, &result)
+ == ERANGE))
+ {
+ char *new_buf;
+ buffer_size += NSS_BUFLEN_GROUP;
+ new_buf = realloc (buffer, buffer_size);
+ if (__glibc_unlikely (new_buf == NULL))
+ {
+ /* We are out of memory. Free the current buffer so that the
+ process gets a chance for a normal termination. */
+ save = errno;
+ free (buffer);
+ __set_errno (save);
+ }
+ buffer = new_buf;
+
+ /* Reset the stream. */
+ if (fsetpos (stream, &pos) != 0)
+ buffer = NULL;
+ }
+
+ if (buffer == NULL)
+ result = NULL;
+
+ /* Release lock. Preserve error value. */
+ save = errno;
+ __libc_lock_unlock (lock);
+ __set_errno (save);
+
+ return result;
+}
+
+weak_alias (buffer, __libc_fgetgrent_freemem_ptr)
diff --git a/nss/fgetgrent_r.c b/nss/fgetgrent_r.c
new file mode 100644
index 0000000000..5ce70230f1
--- /dev/null
+++ b/nss/fgetgrent_r.c
@@ -0,0 +1,65 @@
+/* Copyright (C) 1991-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+
+/* Define a line parsing function using the common code
+ used in the nss_files module. */
+
+#define STRUCTURE group
+#define ENTNAME grent
+struct grent_data {};
+
+#define TRAILING_LIST_MEMBER gr_mem
+#define TRAILING_LIST_SEPARATOR_P(c) ((c) == ',')
+#include <nss/nss_files/files-parse.c>
+LINE_PARSER
+(,
+ STRING_FIELD (result->gr_name, ISCOLON, 0);
+ if (line[0] == '\0'
+ && (result->gr_name[0] == '+' || result->gr_name[0] == '-'))
+ {
+ result->gr_passwd = NULL;
+ result->gr_gid = 0;
+ }
+ else
+ {
+ STRING_FIELD (result->gr_passwd, ISCOLON, 0);
+ if (result->gr_name[0] == '+' || result->gr_name[0] == '-')
+ INT_FIELD_MAYBE_NULL (result->gr_gid, ISCOLON, 0, 10, , 0)
+ else
+ INT_FIELD (result->gr_gid, ISCOLON, 0, 10,)
+ }
+ )
+
+
+/* Read one entry from the given stream. */
+int
+__fgetgrent_r (FILE *stream, struct group *resbuf, char *buffer, size_t buflen,
+ struct group **result)
+{
+ int ret = __nss_fgetent_r (stream, resbuf, buffer, buflen, parse_line);
+ if (ret == 0)
+ *result = resbuf;
+ else
+ *result = NULL;
+ return ret;
+}
+weak_alias (__fgetgrent_r, fgetgrent_r)
diff --git a/nss/getgrent.c b/nss/getgrent.c
new file mode 100644
index 0000000000..6e09987318
--- /dev/null
+++ b/nss/getgrent.c
@@ -0,0 +1,28 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+
+#define LOOKUP_TYPE struct group
+#define SETFUNC_NAME setgrent
+#define GETFUNC_NAME getgrent
+#define ENDFUNC_NAME endgrent
+#define DATABASE_NAME group
+#define BUFLEN NSS_BUFLEN_GROUP
+
+#include "../nss/getXXent.c"
diff --git a/nss/getgrent_r.c b/nss/getgrent_r.c
new file mode 100644
index 0000000000..ea3d0e89c1
--- /dev/null
+++ b/nss/getgrent_r.c
@@ -0,0 +1,28 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+
+#define LOOKUP_TYPE struct group
+#define SETFUNC_NAME setgrent
+#define GETFUNC_NAME getgrent
+#define ENDFUNC_NAME endgrent
+#define DATABASE_NAME group
+#define BUFLEN NSS_BUFLEN_GROUP
+
+#include "../nss/getXXent_r.c"
diff --git a/nss/getgrgid.c b/nss/getgrgid.c
new file mode 100644
index 0000000000..db50f8fdac
--- /dev/null
+++ b/nss/getgrgid.c
@@ -0,0 +1,28 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrgid
+#define DATABASE_NAME group
+#define ADD_PARAMS gid_t gid
+#define ADD_VARIABLES gid
+#define BUFLEN NSS_BUFLEN_GROUP
+
+#include "../nss/getXXbyYY.c"
diff --git a/nss/getgrgid_r.c b/nss/getgrgid_r.c
new file mode 100644
index 0000000000..dab5f85ef3
--- /dev/null
+++ b/nss/getgrgid_r.c
@@ -0,0 +1,31 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+#include <grp-merge.h>
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrgid
+#define DATABASE_NAME group
+#define ADD_PARAMS gid_t gid
+#define ADD_VARIABLES gid
+#define BUFLEN NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN __copy_grp
+#define MERGE_FN __merge_grp
+
+#include <nss/getXXbyYY_r.c>
diff --git a/nss/getgrnam.c b/nss/getgrnam.c
new file mode 100644
index 0000000000..98d637b2bc
--- /dev/null
+++ b/nss/getgrnam.c
@@ -0,0 +1,28 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrnam
+#define DATABASE_NAME group
+#define ADD_PARAMS const char *name
+#define ADD_VARIABLES name
+#define BUFLEN NSS_BUFLEN_GROUP
+
+#include "../nss/getXXbyYY.c"
diff --git a/nss/getgrnam_r.c b/nss/getgrnam_r.c
new file mode 100644
index 0000000000..ed5649c8d8
--- /dev/null
+++ b/nss/getgrnam_r.c
@@ -0,0 +1,31 @@
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+
+#include <grp-merge.h>
+
+#define LOOKUP_TYPE struct group
+#define FUNCTION_NAME getgrnam
+#define DATABASE_NAME group
+#define ADD_PARAMS const char *name
+#define ADD_VARIABLES name
+
+#define DEEPCOPY_FN __copy_grp
+#define MERGE_FN __merge_grp
+
+#include <nss/getXXbyYY_r.c>
diff --git a/nss/grp-merge.c b/nss/grp-merge.c
new file mode 100644
index 0000000000..991abf0252
--- /dev/null
+++ b/nss/grp-merge.c
@@ -0,0 +1,200 @@
+/* Group merging implementation.
+ Copyright (C) 2016-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include <grp-merge.h>
+
+#define BUFCHECK(size) \
+ ({ \
+ do \
+ { \
+ if (c + (size) > buflen) \
+ { \
+ free (members); \
+ return ERANGE; \
+ } \
+ } \
+ while (0); \
+ })
+
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+ struct group *destgrp, char *destbuf, char **endptr)
+{
+ size_t i;
+ size_t c = 0;
+ size_t len;
+ size_t memcount;
+ char **members = NULL;
+
+ /* Copy the GID. */
+ destgrp->gr_gid = srcgrp.gr_gid;
+
+ /* Copy the name. */
+ len = strlen (srcgrp.gr_name) + 1;
+ BUFCHECK (len);
+ memcpy (&destbuf[c], srcgrp.gr_name, len);
+ destgrp->gr_name = &destbuf[c];
+ c += len;
+
+ /* Copy the password. */
+ len = strlen (srcgrp.gr_passwd) + 1;
+ BUFCHECK (len);
+ memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+ destgrp->gr_passwd = &destbuf[c];
+ c += len;
+
+ /* Count all of the members. */
+ for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+ ;
+
+ /* Allocate a temporary holding area for the pointers to the member
+ contents, including space for a NULL-terminator. */
+ members = malloc (sizeof (char *) * (memcount + 1));
+ if (members == NULL)
+ return ENOMEM;
+
+ /* Copy all of the group members to destbuf and add a pointer to each of
+ them into the 'members' array. */
+ for (i = 0; srcgrp.gr_mem[i]; i++)
+ {
+ len = strlen (srcgrp.gr_mem[i]) + 1;
+ BUFCHECK (len);
+ memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+ members[i] = &destbuf[c];
+ c += len;
+ }
+ members[i] = NULL;
+
+ /* Align for pointers. We can't simply align C because we need to
+ align destbuf[c]. */
+ if ((((uintptr_t)destbuf + c) & (__alignof__(char **) - 1)) != 0)
+ {
+ uintptr_t mis_align = ((uintptr_t)destbuf + c) & (__alignof__(char **) - 1);
+ c += __alignof__(char **) - mis_align;
+ }
+
+ /* Copy the pointers from the members array into the buffer and assign them
+ to the gr_mem member of destgrp. */
+ destgrp->gr_mem = (char **) &destbuf[c];
+ len = sizeof (char *) * (memcount + 1);
+ BUFCHECK (len);
+ memcpy (&destbuf[c], members, len);
+ c += len;
+ free (members);
+ members = NULL;
+
+ /* Save the count of members at the end. */
+ BUFCHECK (sizeof (size_t));
+ memcpy (&destbuf[c], &memcount, sizeof (size_t));
+ c += sizeof (size_t);
+
+ if (endptr)
+ *endptr = destbuf + c;
+ return 0;
+}
+libc_hidden_def (__copy_grp)
+
+/* Check that the name, GID and passwd fields match, then
+ copy in the gr_mem array. */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+ size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+ size_t c, i, len;
+ size_t savedmemcount;
+ size_t memcount;
+ size_t membersize;
+ char **members = NULL;
+
+ /* We only support merging members of groups with identical names and
+ GID values. If we hit this case, we need to overwrite the current
+ buffer with the saved one (which is functionally equivalent to
+ treating the new lookup as NSS_STATUS_NOTFOUND). */
+ if (mergegrp->gr_gid != savedgrp->gr_gid
+ || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+ return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+ /* Get the count of group members from the last sizeof (size_t) bytes in the
+ mergegrp buffer. */
+ savedmemcount = *(size_t *) (savedend - sizeof (size_t));
+
+ /* Get the count of new members to add. */
+ for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+ ;
+
+ /* Create a temporary array to hold the pointers to the member values from
+ both the saved and merge groups. */
+ membersize = savedmemcount + memcount + 1;
+ members = malloc (sizeof (char *) * membersize);
+ if (members == NULL)
+ return ENOMEM;
+
+ /* Copy in the existing member pointers from the saved group
+ Note: this is not NULL-terminated yet. */
+ memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+ /* Back up into the savedbuf until we get back to the NULL-terminator of the
+ group member list. (This means walking back savedmemcount + 1 (char *) pointers
+ and the member count value.
+ The value of c is going to be the used length of the buffer backed up by
+ the member count and further backed up by the size of the pointers. */
+ c = savedend - savedbuf
+ - sizeof (size_t)
+ - sizeof (char *) * (savedmemcount + 1);
+
+ /* Add all the new group members, overwriting the old NULL-terminator while
+ adding the new pointers to the temporary array. */
+ for (i = 0; mergegrp->gr_mem[i]; i++)
+ {
+ len = strlen (mergegrp->gr_mem[i]) + 1;
+ BUFCHECK (len);
+ memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+ members[savedmemcount + i] = &savedbuf[c];
+ c += len;
+ }
+ /* Add the NULL-terminator. */
+ members[savedmemcount + memcount] = NULL;
+
+ /* Align for pointers. We can't simply align C because we need to
+ align savedbuf[c]. */
+ if ((((uintptr_t)savedbuf + c) & (__alignof__(char **) - 1)) != 0)
+ {
+ uintptr_t mis_align = ((uintptr_t)savedbuf + c) & (__alignof__(char **) - 1);
+ c += __alignof__(char **) - mis_align;
+ }
+
+ /* Copy the member array back into the buffer after the member list and free
+ the member array. */
+ savedgrp->gr_mem = (char **) &savedbuf[c];
+ len = sizeof (char *) * membersize;
+ BUFCHECK (len);
+ memcpy (&savedbuf[c], members, len);
+ c += len;
+
+ free (members);
+ members = NULL;
+
+ /* Finally, copy the results back into mergebuf, since that's the buffer
+ that we were provided by the caller. */
+ return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
+libc_hidden_def (__merge_grp)
diff --git a/nss/grp-merge.h b/nss/grp-merge.h
new file mode 100644
index 0000000000..9e1f75cfc7
--- /dev/null
+++ b/nss/grp-merge.h
@@ -0,0 +1,35 @@
+/* Group merging implementation.
+ Copyright (C) 2016-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+ calling function must free(newbuf). */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+ struct group *destgrp, char *destbuf, char **endptr);
+
+/* Merge the member lists of two grp structs together. */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+ size_t buflen, struct group *mergegrp, char *mergebuf);
+
+#endif /* _GRP_MERGE_H */
diff --git a/nss/grp.h b/nss/grp.h
new file mode 100644
index 0000000000..c88964797c
--- /dev/null
+++ b/nss/grp.h
@@ -0,0 +1,207 @@
+/* Copyright (C) 1991-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/*
+ * POSIX Standard: 9.2.1 Group Database Access <grp.h>
+ */
+
+#ifndef _GRP_H
+#define _GRP_H 1
+
+#include <features.h>
+
+__BEGIN_DECLS
+
+#include <bits/types.h>
+
+#define __need_size_t
+#include <stddef.h>
+
+
+/* For the Single Unix specification we must define this type here. */
+#if (defined __USE_XOPEN || defined __USE_XOPEN2K) && !defined __gid_t_defined
+typedef __gid_t gid_t;
+# define __gid_t_defined
+#endif
+
+/* The group structure. */
+struct group
+ {
+ char *gr_name; /* Group name. */
+ char *gr_passwd; /* Password. */
+ __gid_t gr_gid; /* Group ID. */
+ char **gr_mem; /* Member list. */
+ };
+
+
+#ifdef __USE_MISC
+# include <bits/types/FILE.h>
+#endif
+
+
+#if defined __USE_MISC || defined __USE_XOPEN_EXTENDED
+/* Rewind the group-file stream.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern void setgrent (void);
+
+/* Close the group-file stream.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern void endgrent (void);
+
+/* Read an entry from the group-file stream, opening it if necessary.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern struct group *getgrent (void);
+#endif
+
+#ifdef __USE_MISC
+/* Read a group entry from STREAM.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+extern struct group *fgetgrent (FILE *__stream);
+#endif
+
+#ifdef __USE_GNU
+/* Write the given entry onto the given stream.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+extern int putgrent (const struct group *__restrict __p,
+ FILE *__restrict __f);
+#endif
+
+/* Search for an entry with a matching group ID.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern struct group *getgrgid (__gid_t __gid);
+
+/* Search for an entry with a matching group name.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern struct group *getgrnam (const char *__name);
+
+#ifdef __USE_POSIX
+
+# ifdef __USE_MISC
+/* Reasonable value for the buffer sized used in the reentrant
+ functions below. But better use `sysconf'. */
+# define NSS_BUFLEN_GROUP 1024
+# endif
+
+/* Reentrant versions of some of the functions above.
+
+ PLEASE NOTE: the `getgrent_r' function is not (yet) standardized.
+ The interface may change in later versions of this library. But
+ the interface is designed following the principals used for the
+ other reentrant functions so the chances are good this is what the
+ POSIX people would choose.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+
+# ifdef __USE_GNU
+extern int getgrent_r (struct group *__restrict __resultbuf,
+ char *__restrict __buffer, size_t __buflen,
+ struct group **__restrict __result)
+ __attr_access ((__write_only__, 2, 3));
+# endif
+
+/* Search for an entry with a matching group ID.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern int getgrgid_r (__gid_t __gid, struct group *__restrict __resultbuf,
+ char *__restrict __buffer, size_t __buflen,
+ struct group **__restrict __result)
+ __attr_access ((__write_only__, 3, 4));
+
+/* Search for an entry with a matching group name.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern int getgrnam_r (const char *__restrict __name,
+ struct group *__restrict __resultbuf,
+ char *__restrict __buffer, size_t __buflen,
+ struct group **__restrict __result)
+ __attr_access ((__write_only__, 3, 4));
+
+# ifdef __USE_MISC
+/* Read a group entry from STREAM. This function is not standardized
+ an probably never will.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+extern int fgetgrent_r (FILE *__restrict __stream,
+ struct group *__restrict __resultbuf,
+ char *__restrict __buffer, size_t __buflen,
+ struct group **__restrict __result)
+ __attr_access ((__write_only__, 3, 4));
+# endif
+
+#endif /* POSIX or reentrant */
+
+
+#ifdef __USE_MISC
+
+# define __need_size_t
+# include <stddef.h>
+
+/* Set the group set for the current user to GROUPS (N of them). */
+extern int setgroups (size_t __n, const __gid_t *__groups) __THROW;
+
+/* Store at most *NGROUPS members of the group set for USER into
+ *GROUPS. Also include GROUP. The actual number of groups found is
+ returned in *NGROUPS. Return -1 if the if *NGROUPS is too small.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+extern int getgrouplist (const char *__user, __gid_t __group,
+ __gid_t *__groups, int *__ngroups);
+
+/* Initialize the group set for the current user
+ by reading the group database and using all groups
+ of which USER is a member. Also include GROUP.
+
+ This function is not part of POSIX and therefore no official
+ cancellation point. But due to similarity with an POSIX interface
+ or due to the implementation it is a cancellation point and
+ therefore not marked with __THROW. */
+extern int initgroups (const char *__user, __gid_t __group);
+
+#endif /* Use misc. */
+
+__END_DECLS
+
+#endif /* grp.h */
diff --git a/nss/initgroups-fallback.c b/nss/initgroups-fallback.c
new file mode 100644
index 0000000000..9df940767b
--- /dev/null
+++ b/nss/initgroups-fallback.c
@@ -0,0 +1,116 @@
+/* Prototype for the setgrent functions we use here. */
+typedef enum nss_status (*set_function) (void);
+
+/* Prototype for the endgrent functions we use here. */
+typedef enum nss_status (*end_function) (void);
+
+/* Prototype for the setgrent functions we use here. */
+typedef enum nss_status (*get_function) (struct group *, char *,
+ size_t, int *);
+
+
+static enum nss_status
+compat_call (nss_action_list nip, const char *user, gid_t group, long int *start,
+ long int *size, gid_t **groupsp, long int limit, int *errnop)
+{
+ struct group grpbuf;
+ enum nss_status status;
+ set_function setgrent_fct;
+ get_function getgrent_fct;
+ end_function endgrent_fct;
+ gid_t *groups = *groupsp;
+
+ getgrent_fct = __nss_lookup_function (nip, "getgrent_r");
+ if (getgrent_fct == NULL)
+ return NSS_STATUS_UNAVAIL;
+
+ setgrent_fct = __nss_lookup_function (nip, "setgrent");
+ if (setgrent_fct)
+ {
+ status = DL_CALL_FCT (setgrent_fct, ());
+ if (status != NSS_STATUS_SUCCESS)
+ return status;
+ }
+
+ endgrent_fct = __nss_lookup_function (nip, "endgrent");
+
+ struct scratch_buffer tmpbuf;
+ scratch_buffer_init (&tmpbuf);
+ enum nss_status result = NSS_STATUS_SUCCESS;
+
+ do
+ {
+ while ((status = DL_CALL_FCT (getgrent_fct,
+ (&grpbuf, tmpbuf.data, tmpbuf.length,
+ errnop)),
+ status == NSS_STATUS_TRYAGAIN)
+ && *errnop == ERANGE)
+ {
+ if (!scratch_buffer_grow (&tmpbuf))
+ {
+ result = NSS_STATUS_TRYAGAIN;
+ goto done;
+ }
+ }
+
+ if (status != NSS_STATUS_SUCCESS)
+ goto done;
+
+ if (grpbuf.gr_gid != group)
+ {
+ char **m;
+
+ for (m = grpbuf.gr_mem; *m != NULL; ++m)
+ if (strcmp (*m, user) == 0)
+ {
+ /* Check whether the group is already on the list. */
+ long int cnt;
+ for (cnt = 0; cnt < *start; ++cnt)
+ if (groups[cnt] == grpbuf.gr_gid)
+ break;
+
+ if (cnt == *start)
+ {
+ /* Matches user and not yet on the list. Insert
+ this group. */
+ if (__glibc_unlikely (*start == *size))
+ {
+ /* Need a bigger buffer. */
+ gid_t *newgroups;
+ long int newsize;
+
+ if (limit > 0 && *size == limit)
+ /* We reached the maximum. */
+ goto done;
+
+ if (limit <= 0)
+ newsize = 2 * *size;
+ else
+ newsize = MIN (limit, 2 * *size);
+
+ newgroups = realloc (groups,
+ newsize * sizeof (*groups));
+ if (newgroups == NULL)
+ goto done;
+ *groupsp = groups = newgroups;
+ *size = newsize;
+ }
+
+ groups[*start] = grpbuf.gr_gid;
+ *start += 1;
+ }
+
+ break;
+ }
+ }
+ }
+ while (status == NSS_STATUS_SUCCESS);
+
+ done:
+ scratch_buffer_free (&tmpbuf);
+
+ if (endgrent_fct)
+ DL_CALL_FCT (endgrent_fct, ());
+
+ return result;
+}
diff --git a/nss/initgroups.c b/nss/initgroups.c
new file mode 100644
index 0000000000..e803cecebc
--- /dev/null
+++ b/nss/initgroups.c
@@ -0,0 +1,218 @@
+/* Copyright (C) 1989, 1991-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <nsswitch.h>
+#include <scratch_buffer.h>
+#include <config.h>
+
+#include "../nscd/nscd-client.h"
+#include "../nscd/nscd_proto.h"
+
+/* Type of the lookup function. */
+typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
+ long int *, long int *,
+ gid_t **, long int, int *);
+
+static bool use_initgroups_entry;
+
+
+#include "initgroups-fallback.c"
+
+
+static int
+internal_getgrouplist (const char *user, gid_t group, long int *size,
+ gid_t **groupsp, long int limit)
+{
+#ifdef USE_NSCD
+ if (__nss_not_use_nscd_group > 0
+ && ++__nss_not_use_nscd_group > NSS_NSCD_RETRY)
+ __nss_not_use_nscd_group = 0;
+ if (!__nss_not_use_nscd_group
+ && !__nss_database_custom[NSS_DBSIDX_group])
+ {
+ int n = __nscd_getgrouplist (user, group, size, groupsp, limit);
+ if (n >= 0)
+ return n;
+
+ /* nscd is not usable. */
+ __nss_not_use_nscd_group = 1;
+ }
+#endif
+
+ enum nss_status status = NSS_STATUS_UNAVAIL;
+
+ /* Never store more than the starting *SIZE number of elements. */
+ assert (*size > 0);
+ (*groupsp)[0] = group;
+ /* Start is one, because we have the first group as parameter. */
+ long int start = 1;
+
+ nss_action_list nip;
+
+ if (__nss_database_get (nss_database_initgroups, &nip)
+ && nip != NULL)
+ {
+ use_initgroups_entry = true;
+ }
+ else if (__nss_database_get (nss_database_group, &nip)
+ && nip != NULL)
+ {
+ use_initgroups_entry = false;
+ }
+ else
+ {
+ nip = __nss_action_parse ("files");
+ use_initgroups_entry = false;
+ }
+
+ while (nip && nip->module)
+ {
+ long int prev_start = start;
+
+ initgroups_dyn_function fct = __nss_lookup_function (nip,
+ "initgroups_dyn");
+ if (fct == NULL)
+ status = compat_call (nip, user, group, &start, size, groupsp,
+ limit, &errno);
+ else
+ status = DL_CALL_FCT (fct, (user, group, &start, size, groupsp,
+ limit, &errno));
+
+ /* Remove duplicates. */
+ long int cnt = prev_start;
+ while (cnt < start)
+ {
+ long int inner;
+ for (inner = 0; inner < prev_start; ++inner)
+ if ((*groupsp)[inner] == (*groupsp)[cnt])
+ break;
+
+ if (inner < prev_start)
+ (*groupsp)[cnt] = (*groupsp)[--start];
+ else
+ ++cnt;
+ }
+
+ /* This is really only for debugging. */
+ if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
+ __libc_fatal ("Illegal status in internal_getgrouplist.\n");
+
+ /* For compatibility reason we will continue to look for more
+ entries using the next service even though data has already
+ been found if the nsswitch.conf file contained only a 'groups'
+ line and no 'initgroups' line. If the latter is available
+ we always respect the status. This means that the default
+ for successful lookups is to return. */
+ if ((use_initgroups_entry || status != NSS_STATUS_SUCCESS)
+ && nss_next_action (nip, status) == NSS_ACTION_RETURN)
+ break;
+
+ nip++;
+ }
+
+ return start;
+}
+
+/* Store at most *NGROUPS members of the group set for USER into
+ *GROUPS. Also include GROUP. The actual number of groups found is
+ returned in *NGROUPS. Return -1 if the if *NGROUPS is too small. */
+int
+getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups)
+{
+ long int size = MAX (1, *ngroups);
+
+ gid_t *newgroups = (gid_t *) malloc (size * sizeof (gid_t));
+ if (__glibc_unlikely (newgroups == NULL))
+ /* No more memory. */
+ // XXX This is wrong. The user provided memory, we have to use
+ // XXX it. The internal functions must be called with the user
+ // XXX provided buffer and not try to increase the size if it is
+ // XXX too small. For initgroups a flag could say: increase size.
+ return -1;
+
+ int total = internal_getgrouplist (user, group, &size, &newgroups, -1);
+
+ memcpy (groups, newgroups, MIN (*ngroups, total) * sizeof (gid_t));
+
+ free (newgroups);
+
+ int retval = total > *ngroups ? -1 : total;
+ *ngroups = total;
+
+ return retval;
+}
+
+nss_interface_function (getgrouplist)
+
+/* Initialize the group set for the current user
+ by reading the group database and using all groups
+ of which USER is a member. Also include GROUP. */
+int
+initgroups (const char *user, gid_t group)
+{
+#if defined NGROUPS_MAX && NGROUPS_MAX == 0
+
+ /* No extra groups allowed. */
+ return 0;
+
+#else
+
+ long int size;
+ gid_t *groups;
+ int ngroups;
+ int result;
+
+ /* We always use sysconf even if NGROUPS_MAX is defined. That way, the
+ limit can be raised in the kernel configuration without having to
+ recompile libc. */
+ long int limit = __sysconf (_SC_NGROUPS_MAX);
+
+ if (limit > 0)
+ /* We limit the size of the initially allocated array. */
+ size = MIN (limit, 64);
+ else
+ /* No fixed limit on groups. Pick a starting buffer size. */
+ size = 16;
+
+ groups = (gid_t *) malloc (size * sizeof (gid_t));
+ if (__glibc_unlikely (groups == NULL))
+ /* No more memory. */
+ return -1;
+
+ ngroups = internal_getgrouplist (user, group, &size, &groups, limit);
+
+ /* Try to set the maximum number of groups the kernel can handle. */
+ do
+ result = setgroups (ngroups, groups);
+ while (result == -1 && errno == EINVAL && --ngroups > 0);
+
+ free (groups);
+
+ return result;
+#endif
+}
+
+nss_interface_function (initgroups)
diff --git a/nss/putgrent.c b/nss/putgrent.c
new file mode 100644
index 0000000000..93caea5071
--- /dev/null
+++ b/nss/putgrent.c
@@ -0,0 +1,76 @@
+/* Copyright (C) 1991-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <nss.h>
+#include <stdio.h>
+#include <string.h>
+#include <grp.h>
+
+#define flockfile(s) _IO_flockfile (s)
+#define funlockfile(s) _IO_funlockfile (s)
+
+#define _S(x) x ? x : ""
+
+/* Write an entry to the given stream.
+ This must know the format of the group file. */
+int
+putgrent (const struct group *gr, FILE *stream)
+{
+ int retval;
+
+ if (__glibc_unlikely (gr == NULL) || __glibc_unlikely (stream == NULL)
+ || gr->gr_name == NULL || !__nss_valid_field (gr->gr_name)
+ || !__nss_valid_field (gr->gr_passwd)
+ || !__nss_valid_list_field (gr->gr_mem))
+ {
+ __set_errno (EINVAL);
+ return -1;
+ }
+
+ flockfile (stream);
+
+ if (gr->gr_name[0] == '+' || gr->gr_name[0] == '-')
+ retval = fprintf (stream, "%s:%s::",
+ gr->gr_name, _S (gr->gr_passwd));
+ else
+ retval = fprintf (stream, "%s:%s:%lu:",
+ gr->gr_name, _S (gr->gr_passwd),
+ (unsigned long int) gr->gr_gid);
+ if (__builtin_expect (retval, 0) < 0)
+ {
+ funlockfile (stream);
+ return -1;
+ }
+
+ if (gr->gr_mem != NULL)
+ {
+ for (size_t i = 0; gr->gr_mem[i] != NULL; i++)
+ if (fprintf (stream, i == 0 ? "%s" : ",%s", gr->gr_mem[i]) < 0)
+ {
+ /* What else can we do? */
+ funlockfile (stream);
+ return -1;
+ }
+ }
+
+ retval = fputc_unlocked ('\n', stream);
+
+ funlockfile (stream);
+
+ return retval < 0 ? -1 : 0;
+}
diff --git a/nss/testgrp.c b/nss/testgrp.c
new file mode 100644
index 0000000000..892cfaaa21
--- /dev/null
+++ b/nss/testgrp.c
@@ -0,0 +1,41 @@
+#include <grp.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int
+main (int argc, char *argv[])
+{
+ uid_t me;
+ struct passwd *my_passwd;
+ struct group *my_group = NULL;
+ char **members;
+
+ me = getuid ();
+ my_passwd = getpwuid (me);
+ if (my_passwd == NULL)
+ printf ("Cannot find user entry for UID %d\n", me);
+ else
+ {
+ printf ("My login name is %s.\n", my_passwd->pw_name);
+ printf ("My uid is %d.\n", (int)(my_passwd->pw_uid));
+ printf ("My home directory is %s.\n", my_passwd->pw_dir);
+ printf ("My default shell is %s.\n", my_passwd->pw_shell);
+
+ my_group = getgrgid (my_passwd->pw_gid);
+ if (my_group == NULL)
+ printf ("No data for group %d found\n", my_passwd->pw_gid);
+ else
+ {
+ printf ("My default group is %s (%d).\n",
+ my_group->gr_name, (int)(my_passwd->pw_gid));
+ printf ("The members of this group are:\n");
+ for (members = my_group->gr_mem; *members != NULL; ++members)
+ printf (" %s\n", *members);
+ }
+ }
+
+ return my_passwd && my_group ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/nss/tst-initgroups1.c b/nss/tst-initgroups1.c
new file mode 100644
index 0000000000..3f1238875e
--- /dev/null
+++ b/nss/tst-initgroups1.c
@@ -0,0 +1,56 @@
+/* Test that initgroups works.
+ Copyright (C) 2020-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <nss.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include <grp.h>
+
+#include <support/support.h>
+#include <support/check.h>
+
+/* Test that initgroups includes secondary groups.
+ https://bugzilla.redhat.com/show_bug.cgi?id=1906066 */
+
+/* This version uses the wrapper around the groups module. */
+
+#define EXPECTED_N_GROUPS 4
+static gid_t expected_groups[] =
+ { 20, 30, 50, 51 };
+
+static int
+do_test (void)
+{
+ gid_t mygroups [50];
+ int i, n;
+
+ n = 50;
+ getgrouplist ("dj", 20, mygroups, &n);
+
+ TEST_COMPARE (n, EXPECTED_N_GROUPS);
+ for (i=0; i<n; i++)
+ TEST_COMPARE (mygroups[i], expected_groups[i]);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nss/tst-initgroups1.root/etc/group b/nss/tst-initgroups1.root/etc/group
new file mode 100644
index 0000000000..0dac1cc2ba
--- /dev/null
+++ b/nss/tst-initgroups1.root/etc/group
@@ -0,0 +1,7 @@
+abc:x:10:
+def:x:20:
+ghi:x:30:dj
+jkl:x:40:
+m:x:50:not,dj
+n:x:51:dj,not
+np:x:60:djx
diff --git a/nss/tst-initgroups1.root/etc/nsswitch.conf b/nss/tst-initgroups1.root/etc/nsswitch.conf
new file mode 100644
index 0000000000..8d0a1aea13
--- /dev/null
+++ b/nss/tst-initgroups1.root/etc/nsswitch.conf
@@ -0,0 +1 @@
+group : files
diff --git a/nss/tst-initgroups1.root/etc/passwd b/nss/tst-initgroups1.root/etc/passwd
new file mode 100644
index 0000000000..5e3a2a5eea
--- /dev/null
+++ b/nss/tst-initgroups1.root/etc/passwd
@@ -0,0 +1 @@
+dj:x:84:20:DJ:/:/bin/sh
diff --git a/nss/tst-initgroups2.c b/nss/tst-initgroups2.c
new file mode 100644
index 0000000000..4e8b8c5cd0
--- /dev/null
+++ b/nss/tst-initgroups2.c
@@ -0,0 +1,21 @@
+/* Test that initgroups works.
+ Copyright (C) 2020-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include "tst-initgroups1.c"
+
+/* This version uses the initgroups built in to the files module. */
diff --git a/nss/tst-initgroups2.root/etc/group b/nss/tst-initgroups2.root/etc/group
new file mode 100644
index 0000000000..0dac1cc2ba
--- /dev/null
+++ b/nss/tst-initgroups2.root/etc/group
@@ -0,0 +1,7 @@
+abc:x:10:
+def:x:20:
+ghi:x:30:dj
+jkl:x:40:
+m:x:50:not,dj
+n:x:51:dj,not
+np:x:60:djx
diff --git a/nss/tst-initgroups2.root/etc/nsswitch.conf b/nss/tst-initgroups2.root/etc/nsswitch.conf
new file mode 100644
index 0000000000..c61f3624f6
--- /dev/null
+++ b/nss/tst-initgroups2.root/etc/nsswitch.conf
@@ -0,0 +1,2 @@
+initgroups : files
+group : notfiles
diff --git a/nss/tst-initgroups2.root/etc/passwd b/nss/tst-initgroups2.root/etc/passwd
new file mode 100644
index 0000000000..5e3a2a5eea
--- /dev/null
+++ b/nss/tst-initgroups2.root/etc/passwd
@@ -0,0 +1 @@
+dj:x:84:20:DJ:/:/bin/sh
diff --git a/nss/tst-putgrent.c b/nss/tst-putgrent.c
new file mode 100644
index 0000000000..79c14862bd
--- /dev/null
+++ b/nss/tst-putgrent.c
@@ -0,0 +1,167 @@
+/* Test for processing of invalid group entries. [BZ #18724]
+ Copyright (C) 2015-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool errors;
+
+static void
+check (struct group e, const char *expected)
+{
+ char *buf;
+ size_t buf_size;
+ FILE *f = open_memstream (&buf, &buf_size);
+
+ if (f == NULL)
+ {
+ printf ("open_memstream: %m\n");
+ errors = true;
+ return;
+ }
+
+ int ret = putgrent (&e, f);
+
+ if (expected == NULL)
+ {
+ if (ret == -1)
+ {
+ if (errno != EINVAL)
+ {
+ printf ("putgrent: unexpected error code: %m\n");
+ errors = true;
+ }
+ }
+ else
+ {
+ printf ("putgrent: unexpected success (\"%s\", \"%s\")\n",
+ e.gr_name, e.gr_passwd);
+ errors = true;
+ }
+ }
+ else
+ {
+ /* Expect success. */
+ size_t expected_length = strlen (expected);
+ if (ret == 0)
+ {
+ long written = ftell (f);
+
+ if (written <= 0 || fflush (f) < 0)
+ {
+ printf ("stream error: %m\n");
+ errors = true;
+ }
+ else if (buf[written - 1] != '\n')
+ {
+ printf ("FAILED: \"%s\" without newline\n", expected);
+ errors = true;
+ }
+ else if (strncmp (buf, expected, written - 1) != 0
+ || written - 1 != expected_length)
+ {
+ buf[written - 1] = '\0';
+ printf ("FAILED: \"%s\" (%ld), expected \"%s\" (%zu)\n",
+ buf, written - 1, expected, expected_length);
+ errors = true;
+ }
+ }
+ else
+ {
+ printf ("FAILED: putgrent (expected \"%s\"): %m\n", expected);
+ errors = true;
+ }
+ }
+
+ fclose (f);
+ free (buf);
+}
+
+static int
+do_test (void)
+{
+ check ((struct group) {
+ .gr_name = (char *) "root",
+ },
+ "root::0:");
+ check ((struct group) {
+ .gr_name = (char *) "root",
+ .gr_passwd = (char *) "password",
+ .gr_gid = 1234,
+ .gr_mem = (char *[2]) {(char *) "member1", NULL}
+ },
+ "root:password:1234:member1");
+ check ((struct group) {
+ .gr_name = (char *) "root",
+ .gr_passwd = (char *) "password",
+ .gr_gid = 1234,
+ .gr_mem = (char *[3]) {(char *) "member1", (char *) "member2", NULL}
+ },
+ "root:password:1234:member1,member2");
+
+ /* Bad values. */
+ {
+ static const char *const bad_strings[] = {
+ ":",
+ "\n",
+ ":bad",
+ "\nbad",
+ "b:ad",
+ "b\nad",
+ "bad:",
+ "bad\n",
+ "b:a\nd"
+ ",",
+ "\n,",
+ ":,",
+ ",bad",
+ "b,ad",
+ "bad,",
+ NULL
+ };
+ for (const char *const *bad = bad_strings; *bad != NULL; ++bad)
+ {
+ char *members[]
+ = {(char *) "first", (char *) *bad, (char *) "last", NULL};
+ if (strpbrk (*bad, ":\n") != NULL)
+ {
+ check ((struct group) {
+ .gr_name = (char *) *bad,
+ }, NULL);
+ check ((struct group) {
+ .gr_name = (char *) "root",
+ .gr_passwd = (char *) *bad,
+ }, NULL);
+ }
+ check ((struct group) {
+ .gr_name = (char *) "root",
+ .gr_passwd = (char *) "password",
+ .gr_mem = members,
+ }, NULL);
+ }
+ }
+
+ return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nss/tst_fgetgrent.c b/nss/tst_fgetgrent.c
new file mode 100644
index 0000000000..be41191824
--- /dev/null
+++ b/nss/tst_fgetgrent.c
@@ -0,0 +1,126 @@
+/* Copyright (C) 1999-2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <grp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static int errors;
+
+static void
+write_users (FILE *f, int large_pos, int pos)
+{
+ int i;
+
+ if (pos == large_pos)
+ {
+ if (large_pos == 3)
+ fprintf (f, ":three");
+
+ /* we need more than 2048 bytes for proper testing. */
+ for (i = 0; i < 500; i++)
+ fprintf (f, ",user%03d", i);
+ }
+ fprintf (f, "\n");
+
+}
+
+static void
+write_group (const char *filename, int pos)
+{
+ FILE *f;
+
+ f = fopen (filename, "w");
+ fprintf (f, "one:x:1:one");
+ write_users (f, pos, 1);
+ fprintf (f, "two:x:2:two");
+ write_users (f, pos, 2);
+ fprintf (f, "three:x:3");
+ write_users (f, pos, 3);
+ fclose (f);
+}
+
+static void
+test_entry (const char *name, gid_t gid, struct group *g)
+{
+ if (!g)
+ {
+ printf ("Error: Entry is empty\n");
+ errors++;
+ return;
+ }
+
+ if ((g->gr_gid == gid) && (strcmp (g->gr_name, name) == 0))
+ printf ("Ok: %s: %d\n", g->gr_name, g->gr_gid);
+ else
+ {
+ printf ("Error: %s: %d should be: %s: %d\n", g->gr_name, g->gr_gid,
+ name, gid);
+ errors++;
+ }
+}
+
+
+static void
+test_fgetgrent (const char *filename)
+{
+ struct group *g;
+ FILE *f;
+
+ f = fopen (filename,"r");
+
+ g = fgetgrent (f);
+ test_entry ("one", 1, g);
+ g = fgetgrent (f);
+ test_entry ("two", 2, g);
+ g = fgetgrent (f);
+ test_entry ("three", 3, g);
+ fclose (f);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ char file[] = "/tmp/tst_fgetgrent.XXXXXX";
+ int fd = mkstemp (file);
+ if (fd == -1)
+ {
+ printf ("mkstemp failed: %m\n");
+ return 1;
+ }
+ close (fd);
+ int i = 0;
+
+ if (argc > 1)
+ i = atoi (argv[1]);
+ if (i > 3)
+ i = 3;
+ if (i)
+ printf ("Large group is group: %d\n", i);
+ else
+ printf ("Not using a large group\n");
+ write_group (file, i);
+ test_fgetgrent (file);
+
+ remove (file);
+
+ return (errors != 0);
+}
diff --git a/nss/tst_fgetgrent.sh b/nss/tst_fgetgrent.sh
new file mode 100644
index 0000000000..fb6b0c4179
--- /dev/null
+++ b/nss/tst_fgetgrent.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Copyright (C) 1999-2023 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library 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
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+set -e
+
+common_objpfx=$1; shift
+test_program_prefix=$1; shift
+
+testout=${common_objpfx}/nss/tst_fgetgrent.out
+
+result=0
+
+${test_program_prefix} \
+${common_objpfx}nss/tst_fgetgrent 0 > ${testout} || result=1
+
+${test_program_prefix} \
+${common_objpfx}nss/tst_fgetgrent 1 >> ${testout} || result=1
+
+${test_program_prefix} \
+${common_objpfx}nss/tst_fgetgrent 2 >> ${testout} || result=1
+
+${test_program_prefix} \
+${common_objpfx}nss/tst_fgetgrent 3 >> ${testout} || result=1
+
+exit $result