mgetgroups: Avoid undefined behaviour when ng == 0.
[gnulib.git] / lib / mgetgroups.c
1 /* mgetgroups.c -- return a list of the groups a user or current process is in
2
3    Copyright (C) 2007-2009 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
36 static gid_t *
37 realloc_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
59 int
60 mgetgroups (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, g + (gid != (gid_t) -1)));
145
146   if (ng < 0)
147     {
148       /* Failure is unexpected, but handle it anyway.  */
149       int saved_errno = errno;
150       free (g);
151       errno = saved_errno;
152       return -1;
153     }
154
155   if (!username && gid != (gid_t) -1)
156     {
157       *g = gid;
158       ng++;
159     }
160   *groups = g;
161
162   /* Reduce the number of duplicates.  On some systems, getgroups
163      returns the effective gid twice: once as the first element, and
164      once in its position within the supplementary groups.  On other
165      systems, getgroups does not return the effective gid at all,
166      which is why we provide a GID argument.  Meanwhile, the GID
167      argument, if provided, is typically any member of the
168      supplementary groups, and not necessarily the effective gid.  So,
169      the most likely duplicates are the first element with an
170      arbitrary other element, or pair-wise duplication between the
171      first and second elements returned by getgroups.  It is possible
172      that this O(n) pass will not remove all duplicates, but it is not
173      worth the effort to slow down to an O(n log n) algorithm that
174      sorts the array in place, nor the extra memory needed for
175      duplicate removal via an O(n) hash-table.  Hence, this function
176      is only documented as guaranteeing no pair-wise duplicates,
177      rather than returning the minimal set.  */
178   if (1 < ng)
179     {
180       gid_t first = *g;
181       gid_t *next;
182       gid_t *groups_end = g + ng;
183
184       for (next = g + 1; next < groups_end; next++)
185         {
186           if (*next == first || *next == *g)
187             ng--;
188           else
189             *++g = *next;
190         }
191     }
192
193   return ng;
194 }
195
196 /* Like mgetgroups, but call xalloc_die on allocation failure.  */
197
198 int
199 xgetgroups (char const *username, gid_t gid, gid_t **groups)
200 {
201   int result = mgetgroups (username, gid, groups);
202   if (result == -1 && errno == ENOMEM)
203     xalloc_die ();
204   return result;
205 }