1 /* mgetgroups.c -- return a list of the groups a user or current process is in
2 
3    Copyright (C) 2007-2018 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 <https://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-oversized.h"
35 
36 /* Work around an incompatibility of OS X 10.11: getgrouplist
37    accepts int *, not gid_t *, and int and gid_t differ in sign.  */
38 #if 4 < __GNUC__ + (3 <= __GNUC_MINOR__)
39 # pragma GCC diagnostic ignored "-Wpointer-sign"
40 #endif
41 
42 static gid_t *
realloc_groupbuf(gid_t * g,size_t num)43 realloc_groupbuf (gid_t *g, size_t num)
44 {
45   if (xalloc_oversized (num, sizeof *g))
46     {
47       errno = ENOMEM;
48       return NULL;
49     }
50 
51   return realloc (g, num * sizeof *g);
52 }
53 
54 /* Like getugroups, but store the result in malloc'd storage.
55    Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
56    is a member.  If GID is not -1, store it first.  GID should be the
57    group ID (pw_gid) obtained from getpwuid, in case USERNAME is not
58    listed in the groups database (e.g., /etc/groups).  If USERNAME is
59    NULL, store the supplementary groups of the current process, and GID
60    should be -1 or the effective group ID (getegid).  Upon failure,
61    don't modify *GROUPS, set errno, and return -1.  Otherwise, return
62    the number of groups.  The resulting list may contain duplicates,
63    but adjacent members will be distinct.  */
64 
65 int
mgetgroups(char const * username,gid_t gid,gid_t ** groups)66 mgetgroups (char const *username, gid_t gid, gid_t **groups)
67 {
68   int max_n_groups;
69   int ng;
70   gid_t *g;
71 
72 #if HAVE_GETGROUPLIST
73   /* We prefer to use getgrouplist if available, because it has better
74      performance characteristics.
75 
76      In glibc 2.3.2, getgrouplist is buggy.  If you pass a zero as the
77      length of the output buffer, getgrouplist will still write to the
78      buffer.  Contrary to what some versions of the getgrouplist
79      manpage say, this doesn't happen with nonzero buffer sizes.
80      Therefore our usage here just avoids a zero sized buffer.  */
81   if (username)
82     {
83       enum { N_GROUPS_INIT = 10 };
84       max_n_groups = N_GROUPS_INIT;
85 
86       g = realloc_groupbuf (NULL, max_n_groups);
87       if (g == NULL)
88         return -1;
89 
90       while (1)
91         {
92           gid_t *h;
93           int last_n_groups = max_n_groups;
94 
95           /* getgrouplist updates max_n_groups to num required.  */
96           ng = getgrouplist (username, gid, g, &max_n_groups);
97 
98           /* Some systems (like Darwin) have a bug where they
99              never increase max_n_groups.  */
100           if (ng < 0 && last_n_groups == max_n_groups)
101             max_n_groups *= 2;
102 
103           if ((h = realloc_groupbuf (g, max_n_groups)) == NULL)
104             {
105               int saved_errno = errno;
106               free (g);
107               errno = saved_errno;
108               return -1;
109             }
110           g = h;
111 
112           if (0 <= ng)
113             {
114               *groups = g;
115               /* On success some systems just return 0 from getgrouplist,
116                  so return max_n_groups rather than ng.  */
117               return max_n_groups;
118             }
119         }
120     }
121   /* else no username, so fall through and use getgroups. */
122 #endif
123 
124   max_n_groups = (username
125                   ? getugroups (0, NULL, username, gid)
126                   : getgroups (0, NULL));
127 
128   /* If we failed to count groups because there is no supplemental
129      group support, then return an array containing just GID.
130      Otherwise, we fail for the same reason.  */
131   if (max_n_groups < 0)
132     {
133       if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1)))
134         {
135           *groups = g;
136           *g = gid;
137           return gid != (gid_t) -1;
138         }
139       return -1;
140     }
141 
142   if (max_n_groups == 0 || (!username && gid != (gid_t) -1))
143     max_n_groups++;
144   g = realloc_groupbuf (NULL, max_n_groups);
145   if (g == NULL)
146     return -1;
147 
148   ng = (username
149         ? getugroups (max_n_groups, g, username, gid)
150         : getgroups (max_n_groups - (gid != (gid_t) -1),
151                                 g + (gid != (gid_t) -1)));
152 
153   if (ng < 0)
154     {
155       /* Failure is unexpected, but handle it anyway.  */
156       int saved_errno = errno;
157       free (g);
158       errno = saved_errno;
159       return -1;
160     }
161 
162   if (!username && gid != (gid_t) -1)
163     {
164       *g = gid;
165       ng++;
166     }
167   *groups = g;
168 
169   /* Reduce the number of duplicates.  On some systems, getgroups
170      returns the effective gid twice: once as the first element, and
171      once in its position within the supplementary groups.  On other
172      systems, getgroups does not return the effective gid at all,
173      which is why we provide a GID argument.  Meanwhile, the GID
174      argument, if provided, is typically any member of the
175      supplementary groups, and not necessarily the effective gid.  So,
176      the most likely duplicates are the first element with an
177      arbitrary other element, or pair-wise duplication between the
178      first and second elements returned by getgroups.  It is possible
179      that this O(n) pass will not remove all duplicates, but it is not
180      worth the effort to slow down to an O(n log n) algorithm that
181      sorts the array in place, nor the extra memory needed for
182      duplicate removal via an O(n) hash-table.  Hence, this function
183      is only documented as guaranteeing no pair-wise duplicates,
184      rather than returning the minimal set.  */
185   if (1 < ng)
186     {
187       gid_t first = *g;
188       gid_t *next;
189       gid_t *groups_end = g + ng;
190 
191       for (next = g + 1; next < groups_end; next++)
192         {
193           if (*next == first || *next == *g)
194             ng--;
195           else
196             *++g = *next;
197         }
198     }
199 
200   return ng;
201 }
202