202 lines
6.2 KiB
C
202 lines
6.2 KiB
C
|
/* mgetgroups.c -- return a list of the groups a user or current process is in
|
||
|
|
||
|
Copyright (C) 2007-2020 Free Software Foundation, Inc.
|
||
|
|
||
|
This program 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 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program 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/>. */
|
||
|
|
||
|
/* 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__)
|
||
|
# 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)
|
||
|
{
|
||
|
int saved_errno = errno;
|
||
|
free (g);
|
||
|
errno = saved_errno;
|
||
|
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. */
|
||
|
int saved_errno = errno;
|
||
|
free (g);
|
||
|
errno = saved_errno;
|
||
|
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;
|
||
|
}
|