getgrent.c revision 1.46 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