diff options
Diffstat (limited to 'lib/mgetgroups.c')
-rw-r--r-- | lib/mgetgroups.c | 197 |
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; +} |