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