xref: /netbsd-src/lib/libc/gen/getgrent.c (revision 001c68bd94f75ce9270b69227c4199fbf34ee396)
1 /*	$NetBSD: getgrent.c,v 1.46 2003/02/17 00:11:54 simonb Exp $	*/
2 
3 /*
4  * Copyright (c) 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  * Portions Copyright (c) 1994, Jason Downs. All Rights Reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <sys/cdefs.h>
38 #if defined(LIBC_SCCS) && !defined(lint)
39 #if 0
40 static char sccsid[] = "@(#)getgrent.c	8.2 (Berkeley) 3/21/94";
41 #else
42 __RCSID("$NetBSD: getgrent.c,v 1.46 2003/02/17 00:11:54 simonb Exp $");
43 #endif
44 #endif /* LIBC_SCCS and not lint */
45 
46 #include "namespace.h"
47 
48 #include <sys/types.h>
49 
50 #include <assert.h>
51 #include <errno.h>
52 #include <grp.h>
53 #include <limits.h>
54 #include <nsswitch.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <syslog.h>
59 
60 #include <stdarg.h>
61 
62 #ifdef HESIOD
63 #include <hesiod.h>
64 #endif
65 #ifdef YP
66 #include <rpc/rpc.h>
67 #include <rpcsvc/yp_prot.h>
68 #include <rpcsvc/ypclnt.h>
69 #endif
70 
71 #if defined(YP) || defined(HESIOD)
72 #define _GROUP_COMPAT
73 #endif
74 
75 struct group *_getgrent_user(const char *);
76 
77 #ifdef __weak_alias
78 __weak_alias(endgrent,_endgrent)
79 __weak_alias(getgrent,_getgrent)
80 __weak_alias(getgrgid,_getgrgid)
81 __weak_alias(getgrnam,_getgrnam)
82 __weak_alias(setgrent,_setgrent)
83 __weak_alias(setgroupent,_setgroupent)
84 #endif
85 
86 static FILE		*_gr_fp;
87 static struct group	_gr_group;
88 static int		_gr_stayopen;
89 static int		_gr_filesdone;
90 
91 static void grcleanup(void);
92 static int grscan(int, gid_t, const char *, const char *);
93 static int grstart(void);
94 static int grmatchline(int, gid_t, const char *, const char *);
95 
96 #define	MAXGRP		200
97 #define	MAXLINELENGTH	1024
98 
99 static __aconst char	*members[MAXGRP];
100 static char		line[MAXLINELENGTH];
101 
102 #ifdef YP
103 static char	*__ypcurrent, *__ypdomain;
104 static int	 __ypcurrentlen;
105 static int	 _gr_ypdone;
106 #endif
107 
108 #ifdef HESIOD
109 static int		 _gr_hesnum;
110 static struct group	*_gr_hesgrplist = NULL;
111 static int		 _gr_hesgrplistnum;
112 static int		 _gr_hesgrplistmax;
113 #endif
114 
115 #ifdef _GROUP_COMPAT
116 enum _grmode { GRMODE_NONE, GRMODE_FULL, GRMODE_NAME };
117 static enum _grmode	 __grmode;
118 #endif
119 
120 struct group *
121 getgrent(void)
122 {
123 
124 	if ((!_gr_fp && !grstart()) || !grscan(0, 0, NULL, NULL))
125  		return (NULL);
126 	return &_gr_group;
127 }
128 
129 /*
130  * _getgrent_user() is designed only to be called by getgrouplist(3) and
131  * hence makes no guarantees about filling the entire structure that it
132  * returns.  It may only fill in the group name and gid fields.
133  */
134 
135 struct group *
136 _getgrent_user(const char *user)
137 {
138 
139 	if ((!_gr_fp && !grstart()) || !grscan(0, 0, NULL, user))
140  		return (NULL);
141 	return &_gr_group;
142 }
143 
144 struct group *
145 getgrnam(const char *name)
146 {
147 	int rval;
148 
149 	_DIAGASSERT(name != NULL);
150 
151 	if (!grstart())
152 		return NULL;
153 	rval = grscan(1, 0, name, NULL);
154 	if (!_gr_stayopen)
155 		endgrent();
156 	return (rval) ? &_gr_group : NULL;
157 }
158 
159 struct group *
160 getgrgid(gid_t gid)
161 {
162 	int rval;
163 
164 	if (!grstart())
165 		return NULL;
166 	rval = grscan(1, gid, NULL, NULL);
167 	if (!_gr_stayopen)
168 		endgrent();
169 	return (rval) ? &_gr_group : NULL;
170 }
171 
172 void
173 grcleanup(void)
174 {
175 
176 	_gr_filesdone = 0;
177 #ifdef YP
178 	if (__ypcurrent)
179 		free(__ypcurrent);
180 	__ypcurrent = NULL;
181 	_gr_ypdone = 0;
182 #endif
183 #ifdef HESIOD
184 	_gr_hesnum = 0;
185 	if (!_gr_hesgrplist)
186 		free(_gr_hesgrplist);
187 	_gr_hesgrplist = NULL;
188 	_gr_hesgrplistnum = -1;
189 	_gr_hesgrplistmax = 0;
190 #endif
191 #ifdef _GROUP_COMPAT
192 	__grmode = GRMODE_NONE;
193 #endif
194 }
195 
196 static int
197 grstart(void)
198 {
199 
200 	grcleanup();
201 	if (_gr_fp) {
202 		rewind(_gr_fp);
203 		return 1;
204 	}
205 	return (_gr_fp = fopen(_PATH_GROUP, "r")) ? 1 : 0;
206 }
207 
208 void
209 setgrent(void)
210 {
211 
212 	(void) setgroupent(0);
213 }
214 
215 int
216 setgroupent(int stayopen)
217 {
218 
219 	if (!grstart())
220 		return 0;
221 	_gr_stayopen = stayopen;
222 	return 1;
223 }
224 
225 void
226 endgrent(void)
227 {
228 
229 	grcleanup();
230 	if (_gr_fp) {
231 		(void)fclose(_gr_fp);
232 		_gr_fp = NULL;
233 	}
234 }
235 
236 
237 static int _local_grscan(void *, void *, va_list);
238 
239 /*ARGSUSED*/
240 static int
241 _local_grscan(void *rv, void *cb_data, va_list ap)
242 {
243 	int		 search = va_arg(ap, int);
244 	gid_t		 gid = va_arg(ap, gid_t);
245 	const char	*name = va_arg(ap, const char *);
246 	const char	*user = va_arg(ap, const char *);
247 
248 	if (_gr_filesdone)
249 		return NS_NOTFOUND;
250 	for (;;) {
251 		if (!fgets(line, sizeof(line), _gr_fp)) {
252 			if (!search)
253 				_gr_filesdone = 1;
254 			return NS_NOTFOUND;
255 		}
256 		/* skip lines that are too big */
257 		if (!strchr(line, '\n')) {
258 			int ch;
259 
260 			while ((ch = getc(_gr_fp)) != '\n' && ch != EOF)
261 				;
262 			continue;
263 		}
264 		if (grmatchline(search, gid, name, user))
265 			return NS_SUCCESS;
266 	}
267 	/* NOTREACHED */
268 }
269 
270 #ifdef HESIOD
271 static int _dns_grscan(void *, void *, va_list);
272 static int _dns_grplist(const char *);
273 
274 /*ARGSUSED*/
275 static int
276 _dns_grscan(void *rv, void *cb_data, va_list ap)
277 {
278 	int		 search = va_arg(ap, int);
279 	gid_t		 gid = va_arg(ap, gid_t);
280 	const char	*name = va_arg(ap, const char *);
281 	const char	*user = va_arg(ap, const char *);
282 
283 	char		**hp;
284 	void		 *context;
285 	int		  r;
286 
287 	r = NS_UNAVAIL;
288 	if (!search && user && _gr_hesgrplistmax != -1) {
289 		r = _dns_grplist(user);
290 		/* if we did not find user.grplist, just iterate */
291 		if (!_gr_hesgrplist) {
292 			_gr_hesgrplistmax = -1;
293 			if (r != NS_NOTFOUND)
294 				return r;
295 		} else
296 			return r;
297 	}
298 	if (!search && _gr_hesnum == -1)
299 		return NS_NOTFOUND;
300 	if (hesiod_init(&context) == -1)
301 		return (r);
302 
303 	for (;;) {
304 		if (search) {
305 			if (name)
306 				strlcpy(line, name, sizeof(line));
307 			else
308 				snprintf(line, sizeof(line), "%u",
309 				    (unsigned int)gid);
310 		} else {
311 			snprintf(line, sizeof(line), "group-%u", _gr_hesnum);
312 			_gr_hesnum++;
313 		}
314 
315 		hp = NULL;
316 		if (search && !name) {
317 			hp = hesiod_resolve(context, line, "gid");
318 			if (hp == NULL && errno != ENOENT)
319 				break;
320 		}
321 		if (hp == NULL)
322 			hp = hesiod_resolve(context, line, "group");
323 		if (hp == NULL) {
324 			if (errno == ENOENT) {
325 				if (!search)
326 					_gr_hesnum = -1;
327 				r = NS_NOTFOUND;
328 			}
329 			break;
330 		}
331 
332 						/* only check first elem */
333 		strlcpy(line, hp[0], sizeof(line));
334 		hesiod_free_list(context, hp);
335 		if (grmatchline(search, gid, name, user)) {
336 			r = NS_SUCCESS;
337 			break;
338 		} else if (search) {
339 			r = NS_NOTFOUND;
340 			break;
341 		}
342 	}
343 	hesiod_end(context);
344 	return (r);
345 }
346 
347 static int
348 _dns_grplist(const char *user)
349 {
350 	void	 *context;
351 	int	  r;
352 	char	**hp;
353 	char	 *cp;
354 
355 	r = NS_UNAVAIL;
356 	if (!_gr_hesgrplist) {
357 		if (hesiod_init(&context) == -1)
358 			return r;
359 
360 		_gr_hesgrplistnum = -1;
361 		hp = hesiod_resolve(context, user, "grplist");
362 		if (!hp) {
363 			if (errno == ENOENT)
364 				r = NS_NOTFOUND;
365 			hesiod_end(context);
366 			return r;
367 		}
368 
369 		strlcpy(line, hp[0], sizeof(line));
370 		hesiod_free_list(context, hp);
371 
372 		_gr_hesgrplistmax = 0;
373 		for (cp=line; *cp; cp++)
374 			if (*cp == ':')
375 				_gr_hesgrplistmax++;
376 		_gr_hesgrplistmax /= 2;
377 		_gr_hesgrplistmax++;
378 
379 		_gr_hesgrplist = malloc(_gr_hesgrplistmax *
380 		    sizeof(*_gr_hesgrplist));
381 		if (!_gr_hesgrplist) {
382 			hesiod_end(context);
383 			return NS_UNAVAIL;
384 		}
385 
386 		cp = line;
387 		_gr_hesgrplistmax = 0;
388 		for (;;) {
389 			char	*name;
390 			char	*num;
391 			gid_t	 gid;
392 			char	*ep;
393 
394 			/* XXXrcd: error handling */
395 			if (!(name = strsep(&cp, ":")))
396 				break;
397 			if (!(num = strsep(&cp, ":")))
398 				break;
399 			gid = (gid_t) strtoul(num, &ep, 10);
400 			if (gid > GID_MAX || *ep != '\0')
401 				break;
402 
403 			_gr_hesgrplist[_gr_hesgrplistmax].gr_name = name;
404 			_gr_hesgrplist[_gr_hesgrplistmax].gr_gid  = gid;
405 			_gr_hesgrplistmax++;
406 		}
407 
408 		hesiod_end(context);
409 	}
410 
411 	/* we assume that _gr_hesgrplist is now defined */
412 	if (++_gr_hesgrplistnum >= _gr_hesgrplistmax)
413 		return NS_NOTFOUND;
414 
415 	/*
416 	 * Now we copy the relevant information into _gr_group, so that
417 	 * it can be returned.  Note that we only fill in the bare necessities
418 	 * as this will be used exclusively by getgrouplist(3) and we do
419 	 * not want to have to look up all of the information.
420 	 */
421 	_gr_group.gr_name   = _gr_hesgrplist[_gr_hesgrplistnum].gr_name;
422 	_gr_group.gr_passwd = NULL;
423 	_gr_group.gr_gid    = _gr_hesgrplist[_gr_hesgrplistnum].gr_gid;
424 	_gr_group.gr_mem    = NULL;
425 
426 	return NS_SUCCESS;
427 }
428 #endif	/* HESIOD */
429 
430 #ifdef YP
431 static int _nis_grscan(void *, void *, va_list);
432 
433 /*ARGSUSED*/
434 static int
435 _nis_grscan(void *rv, void *cb_data, va_list ap)
436 {
437 	int		 search = va_arg(ap, int);
438 	gid_t		 gid = va_arg(ap, gid_t);
439 	const char	*name = va_arg(ap, const char *);
440 	const char	*user = va_arg(ap, const char *);
441 
442 	char	*key, *data;
443 	int	 keylen, datalen;
444 	int	 r;
445 
446 	if(__ypdomain == NULL) {
447 		switch (yp_get_default_domain(&__ypdomain)) {
448 		case 0:
449 			break;
450 		case YPERR_RESRC:
451 			return NS_TRYAGAIN;
452 		default:
453 			return NS_UNAVAIL;
454 		}
455 	}
456 
457 	if (search) {			/* specific group or gid */
458 		if (name)
459 			strlcpy(line, name, sizeof(line));
460 		else
461 			snprintf(line, sizeof(line), "%u", (unsigned int)gid);
462 		data = NULL;
463 		r = yp_match(__ypdomain,
464 				(name) ? "group.byname" : "group.bygid",
465 				line, (int)strlen(line), &data, &datalen);
466 		switch (r) {
467 		case 0:
468 			break;
469 		case YPERR_KEY:
470 			if (data)
471 				free(data);
472 			return NS_NOTFOUND;
473 		default:
474 			if (data)
475 				free(data);
476 			return NS_UNAVAIL;
477 		}
478 		data[datalen] = '\0';			/* clear trailing \n */
479 		strlcpy(line, data, sizeof(line));
480 		free(data);
481 		if (grmatchline(search, gid, name, user))
482 			return NS_SUCCESS;
483 		else
484 			return NS_NOTFOUND;
485 	}
486 
487 						/* ! search */
488 	if (_gr_ypdone)
489 		return NS_NOTFOUND;
490 	for (;;) {
491 		data = NULL;
492 		if(__ypcurrent) {
493 			key = NULL;
494 			r = yp_next(__ypdomain, "group.byname",
495 				__ypcurrent, __ypcurrentlen,
496 				&key, &keylen, &data, &datalen);
497 			free(__ypcurrent);
498 			switch (r) {
499 			case 0:
500 				break;
501 			case YPERR_NOMORE:
502 				__ypcurrent = NULL;
503 				if (key)
504 					free(key);
505 				if (data)
506 					free(data);
507 				_gr_ypdone = 1;
508 				return NS_NOTFOUND;
509 			default:
510 				if (key)
511 					free(key);
512 				if (data)
513 					free(data);
514 				return NS_UNAVAIL;
515 			}
516 			__ypcurrent = key;
517 			__ypcurrentlen = keylen;
518 		} else {
519 			if (yp_first(__ypdomain, "group.byname",
520 					&__ypcurrent, &__ypcurrentlen,
521 					&data, &datalen)) {
522 				if (data)
523 					free(data);
524 				return NS_UNAVAIL;
525 			}
526 		}
527 		data[datalen] = '\0';			/* clear trailing \n */
528 		strlcpy(line, data, sizeof(line));
529 		free(data);
530 		if (grmatchline(search, gid, name, user))
531 			return NS_SUCCESS;
532 	}
533 	/* NOTREACHED */
534 }
535 #endif	/* YP */
536 
537 #ifdef _GROUP_COMPAT
538 /*
539  * log an error if "files" or "compat" is specified in group_compat database
540  */
541 static int _bad_grscan(void *, void *, va_list);
542 
543 /*ARGSUSED*/
544 static int
545 _bad_grscan(void *rv, void *cb_data, va_list ap)
546 {
547 	static int warned;
548 
549 	_DIAGASSERT(cb_data != NULL);
550 
551 	if (!warned) {
552 		syslog(LOG_ERR,
553 			"nsswitch.conf group_compat database can't use '%s'",
554 			(char *)cb_data);
555 	}
556 	warned = 1;
557 	return NS_UNAVAIL;
558 }
559 
560 /*
561  * when a name lookup in compat mode is required, look it up in group_compat
562  * nsswitch database. only Hesiod and NIS is supported - it doesn't make
563  * sense to lookup compat names from 'files' or 'compat'
564  */
565 
566 static int __grscancompat(int, gid_t, const char *, const char *);
567 
568 static int
569 __grscancompat(int search, gid_t gid, const char *name, const char *user)
570 {
571 	static const ns_dtab dtab[] = {
572 		NS_FILES_CB(_bad_grscan, "files")
573 		NS_DNS_CB(_dns_grscan, NULL)
574 		NS_NIS_CB(_nis_grscan, NULL)
575 		NS_COMPAT_CB(_bad_grscan, "compat")
576 		{ 0 }
577 	};
578 	static const ns_src defaultnis[] = {
579 		{ NSSRC_NIS, 	NS_SUCCESS },
580 		{ 0 }
581 	};
582 
583 	_DIAGASSERT(name != NULL);
584 
585 	return (nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "grscancompat",
586 	    defaultnis, search, gid, name, user));
587 }
588 #endif	/* GROUP_COMPAT */
589 
590 
591 static int _compat_grscan(void *, void *, va_list);
592 
593 /*ARGSUSED*/
594 static int
595 _compat_grscan(void *rv, void *cb_data, va_list ap)
596 {
597 	int		 search = va_arg(ap, int);
598 	gid_t		 gid = va_arg(ap, gid_t);
599 	const char	*name = va_arg(ap, const char *);
600 	const char	*user = va_arg(ap, const char *);
601 
602 #ifdef _GROUP_COMPAT
603 	static char	*grname = NULL;
604 #endif
605 
606 	for (;;) {
607 #ifdef _GROUP_COMPAT
608 		if(__grmode != GRMODE_NONE) {
609 			int	 r;
610 
611 			switch(__grmode) {
612 			case GRMODE_FULL:
613 				r = __grscancompat(search, gid, name, user);
614 				if (r == NS_SUCCESS)
615 					return r;
616 				__grmode = GRMODE_NONE;
617 				break;
618 			case GRMODE_NAME:
619 				if(grname == (char *)NULL) {
620 					__grmode = GRMODE_NONE;
621 					break;
622 				}
623 				r = __grscancompat(1, 0, grname, user);
624 				free(grname);
625 				grname = (char *)NULL;
626 				if (r != NS_SUCCESS)
627 					break;
628 				if (!search)
629 					return NS_SUCCESS;
630 				if (name) {
631 					if (! strcmp(_gr_group.gr_name, name))
632 						return NS_SUCCESS;
633 				} else {
634 					if (_gr_group.gr_gid == gid)
635 						return NS_SUCCESS;
636 				}
637 				break;
638 			case GRMODE_NONE:
639 				abort();
640 			}
641 			continue;
642 		}
643 #endif	/* _GROUP_COMPAT */
644 
645 		if (!fgets(line, sizeof(line), _gr_fp))
646 			return NS_NOTFOUND;
647 		/* skip lines that are too big */
648 		if (!strchr(line, '\n')) {
649 			int ch;
650 
651 			while ((ch = getc(_gr_fp)) != '\n' && ch != EOF)
652 				;
653 			continue;
654 		}
655 
656 #ifdef _GROUP_COMPAT
657 		if (line[0] == '+') {
658 			char	*tptr, *bp;
659 
660 			switch(line[1]) {
661 			case ':':
662 			case '\0':
663 			case '\n':
664 				__grmode = GRMODE_FULL;
665 				break;
666 			default:
667 				__grmode = GRMODE_NAME;
668 				bp = line;
669 				tptr = strsep(&bp, ":\n");
670 				grname = strdup(tptr + 1);
671 				break;
672 			}
673 			continue;
674 		}
675 #endif	/* _GROUP_COMPAT */
676 		if (grmatchline(search, gid, name, user))
677 			return NS_SUCCESS;
678 	}
679 	/* NOTREACHED */
680 }
681 
682 static int
683 grscan(int search, gid_t gid, const char *name, const char *user)
684 {
685 	int		r;
686 	static const ns_dtab dtab[] = {
687 		NS_FILES_CB(_local_grscan, NULL)
688 		NS_DNS_CB(_dns_grscan, NULL)
689 		NS_NIS_CB(_nis_grscan, NULL)
690 		NS_COMPAT_CB(_compat_grscan, NULL)
691 		{ 0 }
692 	};
693 	static const ns_src compatsrc[] = {
694 		{ NSSRC_COMPAT, NS_SUCCESS },
695 		{ 0 }
696 	};
697 
698 	/* name may be NULL if search is nonzero */
699 
700 	r = nsdispatch(NULL, dtab, NSDB_GROUP, "grscan", compatsrc,
701 	    search, gid, name, user);
702 	return (r == NS_SUCCESS) ? 1 : 0;
703 }
704 
705 static int
706 grmatchline(int search, gid_t gid, const char *name, const char *user)
707 {
708 	unsigned long	id;
709 	__aconst char	**m;
710 	char		*cp, *bp, *ep;
711 
712 	/* name may be NULL if search is nonzero */
713 
714 	if (line[0] == '+')
715 		return 0;	/* sanity check to prevent recursion */
716 	bp = line;
717 	_gr_group.gr_name = strsep(&bp, ":\n");
718 	if (search && name && strcmp(_gr_group.gr_name, name))
719 		return 0;
720 	_gr_group.gr_passwd = strsep(&bp, ":\n");
721 	if (!(cp = strsep(&bp, ":\n")))
722 		return 0;
723 	id = strtoul(cp, &ep, 10);
724 	if (id > GID_MAX || *ep != '\0')
725 		return 0;
726 	_gr_group.gr_gid = (gid_t)id;
727 	if (search && name == NULL && _gr_group.gr_gid != gid)
728 		return 0;
729 	cp = NULL;
730 	if (bp == NULL)
731 		return 0;
732 	for (_gr_group.gr_mem = m = members;; bp++) {
733 		if (m == &members[MAXGRP - 1])
734 			break;
735 		if (*bp == ',') {
736 			if (cp) {
737 				*bp = '\0';
738 				*m++ = cp;
739 				cp = NULL;
740 			}
741 		} else if (*bp == '\0' || *bp == '\n' || *bp == ' ') {
742 			if (cp) {
743 				*bp = '\0';
744 				*m++ = cp;
745 			}
746 			break;
747 		} else if (cp == NULL)
748 			cp = bp;
749 	}
750 	*m = NULL;
751 	if (user) {
752 		for (m = members; *m; m++)
753 			if (!strcmp(user, *m))
754 				return 1;
755 		return 0;
756 	}
757 	return 1;
758 }
759