xref: /netbsd-src/lib/libc/gen/getgroupmembership.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: getgroupmembership.c,v 1.3 2007/02/03 16:17:15 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2004-2005 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the NetBSD
21  *	Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #if defined(LIBC_SCCS) && !defined(lint)
41 __RCSID("$NetBSD: getgroupmembership.c,v 1.3 2007/02/03 16:17:15 christos Exp $");
42 #endif /* LIBC_SCCS and not lint */
43 
44 /*
45  * calculate group access list
46  */
47 
48 #include "namespace.h"
49 #include "reentrant.h"
50 
51 #include <sys/param.h>
52 
53 #include <assert.h>
54 #include <errno.h>
55 #include <grp.h>
56 #include <limits.h>
57 #include <nsswitch.h>
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63 
64 #ifdef HESIOD
65 #include <hesiod.h>
66 #endif
67 
68 #include "gr_private.h"
69 
70 #ifdef __weak_alias
71 __weak_alias(getgroupmembership,_getgroupmembership)
72 #endif
73 
74 /*
75  * __gr_addgid
76  *	Add gid to the groups array (of maxgrp size) at the position
77  *	indicated by *groupc, unless it already exists or *groupc is
78  *	past &groups[maxgrp].
79  *	Returns 1 upon success (including duplicate suppression), 0 otherwise.
80  */
81 static int
82 __gr_addgid(gid_t gid, gid_t *groups, int maxgrp, int *groupc)
83 {
84 	int	ret, dupc;
85 
86 	_DIAGASSERT(groupc != NULL);
87 	_DIAGASSERT(groups != NULL);
88 
89 						/* skip duplicates */
90 	for (dupc = 0; dupc < MIN(maxgrp, *groupc); dupc++) {
91 		if (groups[dupc] == gid)
92 			return 1;
93 	}
94 
95 	ret = 1;
96 	if (*groupc < maxgrp)			/* add this gid */
97 		groups[*groupc] = gid;
98 	else
99 		ret = 0;
100 	(*groupc)++;
101 	return ret;
102 }
103 
104 
105 /*ARGSUSED*/
106 static int
107 _files_getgroupmembership(void *retval, void *cb_data, va_list ap)
108 {
109 	int		*result	= va_arg(ap, int *);
110 	const char 	*uname	= va_arg(ap, const char *);
111 	gid_t		 agroup	= va_arg(ap, gid_t);
112 	gid_t		*groups	= va_arg(ap, gid_t *);
113 	int		 maxgrp	= va_arg(ap, int);
114 	int		*groupc	= va_arg(ap, int *);
115 
116 	struct __grstate_files	state;
117 	struct group		grp;
118 	char			grpbuf[_GETGR_R_SIZE_MAX];
119 	int			rv, i;
120 
121 	_DIAGASSERT(result != NULL);
122 	_DIAGASSERT(uname != NULL);
123 	/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
124 	_DIAGASSERT(groupc != NULL);
125 
126 						/* install primary group */
127 	(void) __gr_addgid(agroup, groups, maxgrp, groupc);
128 
129 	memset(&state, 0, sizeof(state));
130 	while (__grscan_files(&rv, &grp, grpbuf, sizeof(grpbuf), &state,
131 				0, NULL, 0) == NS_SUCCESS) {
132 						/* scan members */
133 		for (i = 0; grp.gr_mem[i]; i++) {
134 			if (strcmp(grp.gr_mem[i], uname) != 0)
135 				continue;
136 			if (! __gr_addgid(grp.gr_gid, groups, maxgrp, groupc))
137 				*result = -1;
138 			break;
139 		}
140 	}
141 	__grend_files(&state);
142 	return NS_NOTFOUND;
143 }
144 
145 
146 #ifdef HESIOD
147 
148 /*ARGSUSED*/
149 static int
150 _dns_getgroupmembership(void *retval, void *cb_data, va_list ap)
151 {
152 	int		*result	= va_arg(ap, int *);
153 	const char 	*uname	= va_arg(ap, const char *);
154 	gid_t		 agroup	= va_arg(ap, gid_t);
155 	gid_t		*groups	= va_arg(ap, gid_t *);
156 	int		 maxgrp	= va_arg(ap, int);
157 	int		*groupc	= va_arg(ap, int *);
158 
159 	struct __grstate_dns	state;
160 	struct group		grp;
161 	char			grpbuf[_GETGR_R_SIZE_MAX];
162 	unsigned long		id;
163 	void			*context;
164 	char			**hp, *cp, *ep;
165 	int			rv, i;
166 
167 	_DIAGASSERT(result != NULL);
168 	_DIAGASSERT(uname != NULL);
169 	/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
170 	_DIAGASSERT(groupc != NULL);
171 
172 						/* install primary group */
173 	(void) __gr_addgid(agroup, groups, maxgrp, groupc);
174 
175 	hp = NULL;
176 	rv = NS_NOTFOUND;
177 
178 	if (hesiod_init(&context) == -1)		/* setup hesiod */
179 		return NS_UNAVAIL;
180 
181 	hp = hesiod_resolve(context, uname, "grplist");	/* find grplist */
182 	if (hp == NULL) {
183 		if (errno != ENOENT) {			/* wasn't "not found"*/
184 			rv = NS_UNAVAIL;
185 			goto dnsgroupmembers_out;
186 		}
187 			/* grplist not found, fallback to _dns_grscan */
188 		memset(&state, 0, sizeof(state));
189 		while (__grscan_dns(&rv, &grp, grpbuf, sizeof(grpbuf), &state,
190 					0, NULL, 0) == NS_SUCCESS) {
191 							/* scan members */
192 			for (i = 0; grp.gr_mem[i]; i++) {
193 				if (strcmp(grp.gr_mem[i], uname) != 0)
194 					continue;
195 				if (! __gr_addgid(grp.gr_gid, groups, maxgrp,
196 				    groupc))
197 					*result = -1;
198 				break;
199 			}
200 		}
201 		__grend_dns(&state);
202 		rv = NS_NOTFOUND;
203 		goto dnsgroupmembers_out;
204 	}
205 
206 	if ((ep = strchr(hp[0], '\n')) != NULL)
207 		*ep = '\0';				/* clear trailing \n */
208 
209 	for (cp = hp[0]; *cp != '\0'; ) {		/* parse grplist */
210 		if ((cp = strchr(cp, ':')) == NULL)	/* skip grpname */
211 			break;
212 		cp++;
213 		id = strtoul(cp, &ep, 10);		/* parse gid */
214 		if (id > GID_MAX || (*ep != ':' && *ep != '\0')) {
215 			rv = NS_UNAVAIL;
216 			goto dnsgroupmembers_out;
217 		}
218 		cp = ep;
219 		if (*cp == ':')
220 			cp++;
221 
222 							/* add gid */
223 		if (! __gr_addgid((gid_t)id, groups, maxgrp, groupc))
224 			*result = -1;
225 	}
226 
227 	rv = NS_NOTFOUND;
228 
229  dnsgroupmembers_out:
230 	if (hp)
231 		hesiod_free_list(context, hp);
232 	hesiod_end(context);
233 	return rv;
234 }
235 
236 #endif /* HESIOD */
237 
238 
239 #ifdef YP
240 
241 /*ARGSUSED*/
242 static int
243 _nis_getgroupmembership(void *retval, void *cb_data, va_list ap)
244 {
245 	int		*result	= va_arg(ap, int *);
246 	const char 	*uname	= va_arg(ap, const char *);
247 	gid_t		 agroup	= va_arg(ap, gid_t);
248 	gid_t		*groups	= va_arg(ap, gid_t *);
249 	int		 maxgrp	= va_arg(ap, int);
250 	int		*groupc	= va_arg(ap, int *);
251 
252 	struct __grstate_nis	state;
253 	struct group		grp;
254 	char			grpbuf[_GETGR_R_SIZE_MAX];
255 	int			rv, i;
256 
257 	_DIAGASSERT(result != NULL);
258 	_DIAGASSERT(uname != NULL);
259 	/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
260 	_DIAGASSERT(groupc != NULL);
261 
262 						/* install primary group */
263 	(void) __gr_addgid(agroup, groups, maxgrp, groupc);
264 
265 	memset(&state, 0, sizeof(state));
266 	while (__grscan_nis(&rv, &grp, grpbuf, sizeof(grpbuf), &state,
267 				0, NULL, 0) == NS_SUCCESS) {
268 						/* scan members */
269 		for (i = 0; grp.gr_mem[i]; i++) {
270 			if (strcmp(grp.gr_mem[i], uname) != 0)
271 				continue;
272 			if (! __gr_addgid(grp.gr_gid, groups, maxgrp, groupc))
273 				*result = -1;
274 			break;
275 		}
276 	}
277 	__grend_nis(&state);
278 
279 	return NS_NOTFOUND;
280 }
281 
282 #endif /* YP */
283 
284 
285 #ifdef _GROUP_COMPAT
286 
287 struct __compatggm {
288 	const char	*uname;		/* user to search for */
289 	gid_t		*groups;
290 	gid_t		 agroup;
291 	int		 maxgrp;
292 	int		*groupc;
293 };
294 
295 static int
296 _compat_ggm_search(void *cookie, struct group **groupres)
297 {
298 	struct __compatggm	*cp;
299 	int			rerror, crv;
300 
301 	static const ns_dtab dtab[] = {
302 		NS_FILES_CB(__grbad_compat, "files")
303 		NS_DNS_CB(_dns_getgroupmembership, NULL)
304 		NS_NIS_CB(_nis_getgroupmembership, NULL)
305 		NS_COMPAT_CB(__grbad_compat, "compat")
306 		NS_NULL_CB
307 	};
308 
309 	*groupres = NULL;	/* we don't care about this */
310 	cp = (struct __compatggm *)cookie;
311 
312 	crv = nsdispatch(NULL, dtab,
313 	    NSDB_GROUP_COMPAT, "getgroupmembership",
314 	    __nsdefaultnis,
315 	    &rerror, cp->uname, cp->agroup, cp->groups, cp->maxgrp, cp->groupc);
316 
317 	if (crv == NS_SUCCESS)
318 		crv = NS_NOTFOUND;	/* indicate "no more +: entries" */
319 
320 	return crv;
321 }
322 
323 /* ARGSUSED */
324 static int
325 _compat_getgroupmembership(void *retval, void *cb_data, va_list ap)
326 {
327 	int		*result	= va_arg(ap, int *);
328 	const char 	*uname	= va_arg(ap, const char *);
329 	gid_t		 agroup	= va_arg(ap, gid_t);
330 	gid_t		*groups	= va_arg(ap, gid_t *);
331 	int		 maxgrp	= va_arg(ap, int);
332 	int		*groupc	= va_arg(ap, int *);
333 
334 	struct __grstate_compat	state;
335 	struct __compatggm	ggmstate;
336 	struct group		grp;
337 	char			grpbuf[_GETGR_R_SIZE_MAX];
338 	int			rv, i;
339 
340 	_DIAGASSERT(result != NULL);
341 	_DIAGASSERT(uname != NULL);
342 	/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
343 	_DIAGASSERT(groupc != NULL);
344 
345 						/* install primary group */
346 	(void) __gr_addgid(agroup, groups, maxgrp, groupc);
347 
348 	memset(&state, 0, sizeof(state));
349 	memset(&ggmstate, 0, sizeof(ggmstate));
350 	ggmstate.uname = uname;
351 	ggmstate.groups = groups;
352 	ggmstate.agroup = agroup;
353 	ggmstate.maxgrp = maxgrp;
354 	ggmstate.groupc = groupc;
355 
356 	while (__grscan_compat(&rv, &grp, grpbuf, sizeof(grpbuf), &state,
357 				0, NULL, 0, _compat_ggm_search, &ggmstate)
358 		== NS_SUCCESS) {
359 						/* scan members */
360 		for (i = 0; grp.gr_mem[i]; i++) {
361 			if (strcmp(grp.gr_mem[i], uname) != 0)
362 				continue;
363 			if (! __gr_addgid(grp.gr_gid, groups, maxgrp, groupc))
364 				*result = -1;
365 			break;
366 		}
367 	}
368 
369 	__grend_compat(&state);
370 	return NS_NOTFOUND;
371 }
372 
373 #endif	/* _GROUP_COMPAT */
374 
375 
376 int
377 getgroupmembership(const char *uname, gid_t agroup,
378     gid_t *groups, int maxgrp, int *groupc)
379 {
380 	int	rerror;
381 
382 	static const ns_dtab dtab[] = {
383 		NS_FILES_CB(_files_getgroupmembership, NULL)
384 		NS_DNS_CB(_dns_getgroupmembership, NULL)
385 		NS_NIS_CB(_nis_getgroupmembership, NULL)
386 		NS_COMPAT_CB(_compat_getgroupmembership, NULL)
387 		NS_NULL_CB
388 	};
389 
390 	_DIAGASSERT(uname != NULL);
391 	/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
392 	_DIAGASSERT(groupc != NULL);
393 
394 	*groupc = 0;
395 
396 	mutex_lock(&__grmutex);
397 			/*
398 			 * Call each backend.
399 			 * For compatibility with getgrent(3) semantics,
400 			 * a backend should return NS_NOTFOUND even upon
401 			 * completion, to allow result merging to occur.
402 			 */
403 	(void) nsdispatch(NULL, dtab, NSDB_GROUP, "getgroupmembership",
404 	    __nsdefaultcompat,
405 	    &rerror, uname, agroup, groups, maxgrp, groupc);
406 	mutex_unlock(&__grmutex);
407 
408 	if (*groupc > maxgrp)			/* too many groups found */
409 		return -1;
410 	else
411 		return 0;
412 }
413