1 /* mgetgroups.c -- return a list of the groups a user or current process is in 2 3 Copyright (C) 2007-2021 Free Software Foundation, Inc. 4 5 This file is free software: you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1 of the 8 License, or (at your option) any later version. 9 10 This file 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 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser 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__) || defined __clang__ 39 # pragma GCC diagnostic ignored "-Wpointer-sign" 40 #endif 41 42 static gid_t * 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 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 free (g); 106 return -1; 107 } 108 g = h; 109 110 if (0 <= ng) 111 { 112 *groups = g; 113 /* On success some systems just return 0 from getgrouplist, 114 so return max_n_groups rather than ng. */ 115 return max_n_groups; 116 } 117 } 118 } 119 /* else no username, so fall through and use getgroups. */ 120 #endif 121 122 max_n_groups = (username 123 ? getugroups (0, NULL, username, gid) 124 : getgroups (0, NULL)); 125 126 /* If we failed to count groups because there is no supplemental 127 group support, then return an array containing just GID. 128 Otherwise, we fail for the same reason. */ 129 if (max_n_groups < 0) 130 { 131 if (errno == ENOSYS && (g = realloc_groupbuf (NULL, 1))) 132 { 133 *groups = g; 134 *g = gid; 135 return gid != (gid_t) -1; 136 } 137 return -1; 138 } 139 140 if (max_n_groups == 0 || (!username && gid != (gid_t) -1)) 141 max_n_groups++; 142 g = realloc_groupbuf (NULL, max_n_groups); 143 if (g == NULL) 144 return -1; 145 146 ng = (username 147 ? getugroups (max_n_groups, g, username, gid) 148 : getgroups (max_n_groups - (gid != (gid_t) -1), 149 g + (gid != (gid_t) -1))); 150 151 if (ng < 0) 152 { 153 /* Failure is unexpected, but handle it anyway. */ 154 free (g); 155 return -1; 156 } 157 158 if (!username && gid != (gid_t) -1) 159 { 160 *g = gid; 161 ng++; 162 } 163 *groups = g; 164 165 /* Reduce the number of duplicates. On some systems, getgroups 166 returns the effective gid twice: once as the first element, and 167 once in its position within the supplementary groups. On other 168 systems, getgroups does not return the effective gid at all, 169 which is why we provide a GID argument. Meanwhile, the GID 170 argument, if provided, is typically any member of the 171 supplementary groups, and not necessarily the effective gid. So, 172 the most likely duplicates are the first element with an 173 arbitrary other element, or pair-wise duplication between the 174 first and second elements returned by getgroups. It is possible 175 that this O(n) pass will not remove all duplicates, but it is not 176 worth the effort to slow down to an O(n log n) algorithm that 177 sorts the array in place, nor the extra memory needed for 178 duplicate removal via an O(n) hash-table. Hence, this function 179 is only documented as guaranteeing no pair-wise duplicates, 180 rather than returning the minimal set. */ 181 if (1 < ng) 182 { 183 gid_t first = *g; 184 gid_t *next; 185 gid_t *groups_end = g + ng; 186 187 for (next = g + 1; next < groups_end; next++) 188 { 189 if (*next == first || *next == *g) 190 ng--; 191 else 192 *++g = *next; 193 } 194 } 195 196 return ng; 197 }