/* 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 . */ /* Extracted from coreutils' src/id.c. */ #include #include "mgetgroups.h" #include #include #include #include #include #if HAVE_GETGROUPLIST # include #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; }