summaryrefslogtreecommitdiffstats
path: root/lib/mgetgroups.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mgetgroups.c')
-rw-r--r--lib/mgetgroups.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/lib/mgetgroups.c b/lib/mgetgroups.c
new file mode 100644
index 0000000..fdf9d59
--- /dev/null
+++ b/lib/mgetgroups.c
@@ -0,0 +1,197 @@
+/* mgetgroups.c -- return a list of the groups a user or current process is in
+
+ Copyright (C) 2007-2023 Free Software Foundation, Inc.
+
+ This file 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.
+
+ This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Extracted from coreutils' src/id.c. */
+
+#include <config.h>
+
+#include "mgetgroups.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#if HAVE_GETGROUPLIST
+# include <grp.h>
+#endif
+
+#include "getugroups.h"
+#include "xalloc-oversized.h"
+
+/* Work around an incompatibility of OS X 10.11: getgrouplist
+ accepts int *, not gid_t *, and int and gid_t differ in sign. */
+#if 4 < __GNUC__ + (3 <= __GNUC_MINOR__) || defined __clang__
+# pragma GCC diagnostic ignored "-Wpointer-sign"
+#endif
+
+static gid_t *
+realloc_groupbuf (gid_t *g, size_t num)
+{
+ if (xalloc_oversized (num, sizeof *g))
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return realloc (g, num * sizeof *g);
+}
+
+/* Like getugroups, but store the result in malloc'd storage.
+ Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
+ is a member. If GID is not -1, store it first. GID should be the
+ group ID (pw_gid) obtained from getpwuid, in case USERNAME is not
+ listed in the groups database (e.g., /etc/groups). If USERNAME is
+ NULL, store the supplementary groups of the current process, and GID
+ should be -1 or the effective group ID (getegid). Upon failure,
+ don't modify *GROUPS, set errno, and return -1. Otherwise, return
+ the number of groups. The resulting list may contain duplicates,
+ but adjacent members will be distinct. */
+
+int
+mgetgroups (char const *username, gid_t gid, gid_t **groups)
+{
+ int max_n_groups;
+ int ng;
+ gid_t *g;
+
+#if HAVE_GETGROUPLIST
+ /* We prefer to use getgrouplist if available, because it has better
+ performance characteristics.
+
+ In glibc 2.3.2, getgrouplist is buggy. If you pass a zero as the
+ length of the output buffer, getgrouplist will still write to the
+ buffer. Contrary to what some versions of the getgrouplist
+ manpage say, this doesn't happen with nonzero buffer sizes.
+ Therefore our usage here just avoids a zero sized buffer. */
+ if (username)
+ {
+ enum { N_GROUPS_INIT = 10 };
+ max_n_groups = N_GROUPS_INIT;
+
+ g = realloc_groupbuf (NULL, max_n_groups);
+ if (g == NULL)
+ return -1;
+
+ while (1)
+ {
+ gid_t *h;
+ int last_n_groups = max_n_groups;
+
+ /* getgrouplist updates max_n_groups to num required. */
+ ng = getgrouplist (username, gid, g, &max_n_groups);
+
+ /* Some systems (like Darwin) have a bug where they
+ never increase max_n_groups. */
+ if (ng < 0 && last_n_groups == max_n_groups)
+ max_n_groups *= 2;
+
+ if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
+ {
+ free (g);
+ return -1;
+ }
+ g = h;
+
+ if (0 <= ng)
+ {
+ *groups = g;
+ /* On success some systems just return 0 from getgrouplist,
+ so return max_n_groups rather than ng. */
+ return max_n_groups;
+ }
+ }
+ }
+ /* else no username, so fall through and use getgroups. */
+#endif
+
+ max_n_groups = (username
+ ? getugroups (0, NULL, username, gid)
+ : getgroups (0, NULL));
+
+ /* If we failed to count groups because there is no supplemental
+ group support, then return an array containing just GID.
+ Otherwise, we fail for the same reason. */
+ if (max_n_groups < 0)
+ {
+ if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1)))
+ {
+ *groups = g;
+ *g = gid;
+ return gid != (gid_t) -1;
+ }
+ return -1;
+ }
+
+ if (max_n_groups == 0 || (!username && gid != (gid_t) -1))
+ max_n_groups++;
+ g = realloc_groupbuf (NULL, max_n_groups);
+ if (g == NULL)
+ return -1;
+
+ ng = (username
+ ? getugroups (max_n_groups, g, username, gid)
+ : getgroups (max_n_groups - (gid != (gid_t) -1),
+ g + (gid != (gid_t) -1)));
+
+ if (ng < 0)
+ {
+ /* Failure is unexpected, but handle it anyway. */
+ free (g);
+ return -1;
+ }
+
+ if (!username && gid != (gid_t) -1)
+ {
+ *g = gid;
+ ng++;
+ }
+ *groups = g;
+
+ /* Reduce the number of duplicates. On some systems, getgroups
+ returns the effective gid twice: once as the first element, and
+ once in its position within the supplementary groups. On other
+ systems, getgroups does not return the effective gid at all,
+ which is why we provide a GID argument. Meanwhile, the GID
+ argument, if provided, is typically any member of the
+ supplementary groups, and not necessarily the effective gid. So,
+ the most likely duplicates are the first element with an
+ arbitrary other element, or pair-wise duplication between the
+ first and second elements returned by getgroups. It is possible
+ that this O(n) pass will not remove all duplicates, but it is not
+ worth the effort to slow down to an O(n log n) algorithm that
+ sorts the array in place, nor the extra memory needed for
+ duplicate removal via an O(n) hash-table. Hence, this function
+ is only documented as guaranteeing no pair-wise duplicates,
+ rather than returning the minimal set. */
+ if (1 < ng)
+ {
+ gid_t first = *g;
+ gid_t *next;
+ gid_t *groups_end = g + ng;
+
+ for (next = g + 1; next < groups_end; next++)
+ {
+ if (*next == first || *next == *g)
+ ng--;
+ else
+ *++g = *next;
+ }
+ }
+
+ return ng;
+}