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