1/* mgetgroups.c -- return a list of the groups a user or current process is in
2
3   Copyright (C) 2007-2010 Free Software Foundation, Inc.
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18/* Extracted from coreutils' src/id.c. */
19
20#include <config.h>
21
22#include "mgetgroups.h"
23
24#include <stdlib.h>
25#include <unistd.h>
26#include <stdint.h>
27#include <string.h>
28#include <errno.h>
29#if HAVE_GETGROUPLIST
30# include <grp.h>
31#endif
32
33#include "getugroups.h"
34#include "xalloc.h"
35
36static gid_t *
37realloc_groupbuf (gid_t *g, size_t num)
38{
39  if (xalloc_oversized (num, sizeof *g))
40    {
41      errno = ENOMEM;
42      return NULL;
43    }
44
45  return realloc (g, num * sizeof *g);
46}
47
48/* Like getugroups, but store the result in malloc'd storage.
49   Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
50   is a member.  If GID is not -1, store it first.  GID should be the
51   group ID (pw_gid) obtained from getpwuid, in case USERNAME is not
52   listed in the groups database (e.g., /etc/groups).  If USERNAME is
53   NULL, store the supplementary groups of the current process, and GID
54   should be -1 or the effective group ID (getegid).  Upon failure,
55   don't modify *GROUPS, set errno, and return -1.  Otherwise, return
56   the number of groups.  The resulting list may contain duplicates,
57   but adjacent members will be distinct.  */
58
59int
60mgetgroups (char const *username, gid_t gid, gid_t **groups)
61{
62  int max_n_groups;
63  int ng;
64  gid_t *g;
65
66#if HAVE_GETGROUPLIST
67  /* We prefer to use getgrouplist if available, because it has better
68     performance characteristics.
69
70     In glibc 2.3.2, getgrouplist is buggy.  If you pass a zero as the
71     length of the output buffer, getgrouplist will still write to the
72     buffer.  Contrary to what some versions of the getgrouplist
73     manpage say, this doesn't happen with nonzero buffer sizes.
74     Therefore our usage here just avoids a zero sized buffer.  */
75  if (username)
76    {
77      enum { N_GROUPS_INIT = 10 };
78      max_n_groups = N_GROUPS_INIT;
79
80      g = realloc_groupbuf (NULL, max_n_groups);
81      if (g == NULL)
82        return -1;
83
84      while (1)
85        {
86          gid_t *h;
87          int last_n_groups = max_n_groups;
88
89          /* getgrouplist updates max_n_groups to num required.  */
90          ng = getgrouplist (username, gid, g, &max_n_groups);
91
92          /* Some systems (like Darwin) have a bug where they
93             never increase max_n_groups.  */
94          if (ng < 0 && last_n_groups == max_n_groups)
95            max_n_groups *= 2;
96
97          if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
98            {
99              int saved_errno = errno;
100              free (g);
101              errno = saved_errno;
102              return -1;
103            }
104          g = h;
105
106          if (0 <= ng)
107            {
108              *groups = g;
109              /* On success some systems just return 0 from getgrouplist,
110                 so return max_n_groups rather than ng.  */
111              return max_n_groups;
112            }
113        }
114    }
115  /* else no username, so fall through and use getgroups. */
116#endif
117
118  max_n_groups = (username
119                  ? getugroups (0, NULL, username, gid)
120                  : getgroups (0, NULL));
121
122  /* If we failed to count groups because there is no supplemental
123     group support, then return an array containing just GID.
124     Otherwise, we fail for the same reason.  */
125  if (max_n_groups < 0)
126    {
127      if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1)))
128        {
129          *groups = g;
130          *g = gid;
131          return gid != (gid_t) -1;
132        }
133      return -1;
134    }
135
136  if (!username && gid != (gid_t) -1)
137    max_n_groups++;
138  g = realloc_groupbuf (NULL, max_n_groups);
139  if (g == NULL)
140    return -1;
141
142  ng = (username
143        ? getugroups (max_n_groups, g, username, gid)
144        : getgroups (max_n_groups - (gid != (gid_t) -1),
145                                g + (gid != (gid_t) -1)));
146
147  if (ng < 0)
148    {
149      /* Failure is unexpected, but handle it anyway.  */
150      int saved_errno = errno;
151      free (g);
152      errno = saved_errno;
153      return -1;
154    }
155
156  if (!username && gid != (gid_t) -1)
157    {
158      *g = gid;
159      ng++;
160    }
161  *groups = g;
162
163  /* Reduce the number of duplicates.  On some systems, getgroups
164     returns the effective gid twice: once as the first element, and
165     once in its position within the supplementary groups.  On other
166     systems, getgroups does not return the effective gid at all,
167     which is why we provide a GID argument.  Meanwhile, the GID
168     argument, if provided, is typically any member of the
169     supplementary groups, and not necessarily the effective gid.  So,
170     the most likely duplicates are the first element with an
171     arbitrary other element, or pair-wise duplication between the
172     first and second elements returned by getgroups.  It is possible
173     that this O(n) pass will not remove all duplicates, but it is not
174     worth the effort to slow down to an O(n log n) algorithm that
175     sorts the array in place, nor the extra memory needed for
176     duplicate removal via an O(n) hash-table.  Hence, this function
177     is only documented as guaranteeing no pair-wise duplicates,
178     rather than returning the minimal set.  */
179  if (1 < ng)
180    {
181      gid_t first = *g;
182      gid_t *next;
183      gid_t *groups_end = g + ng;
184
185      for (next = g + 1; next < groups_end; next++)
186        {
187          if (*next == first || *next == *g)
188            ng--;
189          else
190            *++g = *next;
191        }
192    }
193
194  return ng;
195}
196
197/* Like mgetgroups, but call xalloc_die on allocation failure.  */
198
199int
200xgetgroups (char const *username, gid_t gid, gid_t **groups)
201{
202  int result = mgetgroups (username, gid, groups);
203  if (result == -1 && errno == ENOMEM)
204    xalloc_die ();
205  return result;
206}
207