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