xref: /freebsd/lib/libc/gen/getnetgrent.c (revision 1f4bcc459a76b7aa664f3fd557684cd0ba6da352)
1 /*
2  * Copyright (c) 1992, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Rick Macklem at The University of Guelph.
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  * 4. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #if defined(LIBC_SCCS) && !defined(lint)
34 static char sccsid[] = "@(#)getnetgrent.c	8.2 (Berkeley) 4/27/95";
35 #endif /* LIBC_SCCS and not lint */
36 #include <sys/cdefs.h>
37 __FBSDID("$FreeBSD$");
38 
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #ifdef YP
46 /*
47  * Notes:
48  * We want to be able to use NIS netgroups properly while retaining
49  * the ability to use a local /etc/netgroup file. Unfortunately, you
50  * can't really do both at the same time - at least, not efficiently.
51  * NetBSD deals with this problem by creating a netgroup database
52  * using Berkeley DB (just like the password database) that allows
53  * for lookups using netgroup, netgroup.byuser or netgroup.byhost
54  * searches. This is a neat idea, but I don't have time to implement
55  * something like that now. (I think ultimately it would be nice
56  * if we DB-fied the group and netgroup stuff all in one shot, but
57  * for now I'm satisfied just to have something that works well
58  * without requiring massive code changes.)
59  *
60  * Therefore, to still permit the use of the local file and maintain
61  * optimum NIS performance, we allow for the following conditions:
62  *
63  * - If /etc/netgroup does not exist and NIS is turned on, we use
64  *   NIS netgroups only.
65  *
66  * - If /etc/netgroup exists but is empty, we use NIS netgroups
67  *   only.
68  *
69  * - If /etc/netgroup exists and contains _only_ a '+', we use
70  *   NIS netgroups only.
71  *
72  * - If /etc/netgroup exists, contains locally defined netgroups
73  *   and a '+', we use a mixture of NIS and the local entries.
74  *   This method should return the same NIS data as just using
75  *   NIS alone, but it will be slower if the NIS netgroup database
76  *   is large (innetgr() in particular will suffer since extra
77  *   processing has to be done in order to determine memberships
78  *   using just the raw netgroup data).
79  *
80  * - If /etc/netgroup exists and contains only locally defined
81  *   netgroup entries, we use just those local entries and ignore
82  *   NIS (this is the original, pre-NIS behavior).
83  */
84 
85 #include <rpc/rpc.h>
86 #include <rpcsvc/yp_prot.h>
87 #include <rpcsvc/ypclnt.h>
88 #include <sys/types.h>
89 #include <sys/stat.h>
90 #include <sys/param.h>
91 #include <sys/errno.h>
92 static char *_netgr_yp_domain;
93 int _use_only_yp;
94 static int _netgr_yp_enabled;
95 static int _yp_innetgr;
96 #endif
97 
98 #ifndef _PATH_NETGROUP
99 #define _PATH_NETGROUP "/etc/netgroup"
100 #endif
101 
102 /*
103  * Static Variables and functions used by setnetgrent(), getnetgrent() and
104  * endnetgrent().
105  * There are two linked lists:
106  * - linelist is just used by setnetgrent() to parse the net group file via.
107  *   parse_netgrp()
108  * - netgrp is the list of entries for the current netgroup
109  */
110 struct linelist {
111 	struct linelist	*l_next;	/* Chain ptr. */
112 	int		l_parsed;	/* Flag for cycles */
113 	char		*l_groupname;	/* Name of netgroup */
114 	char		*l_line;	/* Netgroup entrie(s) to be parsed */
115 };
116 
117 struct netgrp {
118 	struct netgrp	*ng_next;	/* Chain ptr */
119 	char		*ng_str[3];	/* Field pointers, see below */
120 };
121 #define NG_HOST		0	/* Host name */
122 #define NG_USER		1	/* User name */
123 #define NG_DOM		2	/* and Domain name */
124 
125 static struct linelist	*linehead = (struct linelist *)0;
126 static struct netgrp	*nextgrp = (struct netgrp *)0;
127 static struct {
128 	struct netgrp	*gr;
129 	char		*grname;
130 } grouphead = {
131 	(struct netgrp *)0,
132 	(char *)0,
133 };
134 static FILE *netf = (FILE *)0;
135 
136 static int parse_netgrp(const char *);
137 static struct linelist *read_for_group(const char *);
138 void setnetgrent(const char *);
139 void endnetgrent(void);
140 int getnetgrent(char **, char **, char **);
141 int innetgr(const char *, const char *, const char *, const char *);
142 
143 #define	LINSIZ	1024	/* Length of netgroup file line */
144 
145 /*
146  * setnetgrent()
147  * Parse the netgroup file looking for the netgroup and build the list
148  * of netgrp structures. Let parse_netgrp() and read_for_group() do
149  * most of the work.
150  */
151 void
152 setnetgrent(const char *group)
153 {
154 #ifdef YP
155 	struct stat _yp_statp;
156 	char _yp_plus;
157 #endif
158 
159 	/* Sanity check */
160 
161 	if (group == NULL || !strlen(group))
162 		return;
163 
164 	if (grouphead.gr == NULL || strcmp(group, grouphead.grname)) {
165 		endnetgrent();
166 #ifdef YP
167 		/* Presumed guilty until proven innocent. */
168 		_use_only_yp = 0;
169 		/*
170 		 * If /etc/netgroup doesn't exist or is empty,
171 		 * use NIS exclusively.
172 		 */
173 		if (((stat(_PATH_NETGROUP, &_yp_statp) < 0) &&
174 		    errno == ENOENT) || _yp_statp.st_size == 0)
175 			_use_only_yp = _netgr_yp_enabled = 1;
176 		if ((netf = fopen(_PATH_NETGROUP,"re")) != NULL ||_use_only_yp){
177 		/*
178 		 * Icky: grab the first character of the netgroup file
179 		 * and turn on NIS if it's a '+'. rewind the stream
180 		 * afterwards so we don't goof up read_for_group() later.
181 		 */
182 			if (netf) {
183 				fscanf(netf, "%c", &_yp_plus);
184 				rewind(netf);
185 				if (_yp_plus == '+')
186 					_use_only_yp = _netgr_yp_enabled = 1;
187 			}
188 		/*
189 		 * If we were called specifically for an innetgr()
190 		 * lookup and we're in NIS-only mode, short-circuit
191 		 * parse_netgroup() and cut directly to the chase.
192 		 */
193 			if (_use_only_yp && _yp_innetgr) {
194 				/* dohw! */
195 				if (netf != NULL)
196 					fclose(netf);
197 				return;
198 			}
199 #else
200 		if ((netf = fopen(_PATH_NETGROUP, "re"))) {
201 #endif
202 			if (parse_netgrp(group))
203 				endnetgrent();
204 			else {
205 				grouphead.grname = strdup(group);
206 			}
207 			if (netf)
208 				fclose(netf);
209 		}
210 	}
211 	nextgrp = grouphead.gr;
212 }
213 
214 /*
215  * Get the next netgroup off the list.
216  */
217 int
218 getnetgrent(char **hostp, char **userp, char **domp)
219 {
220 #ifdef YP
221 	_yp_innetgr = 0;
222 #endif
223 
224 	if (nextgrp) {
225 		*hostp = nextgrp->ng_str[NG_HOST];
226 		*userp = nextgrp->ng_str[NG_USER];
227 		*domp = nextgrp->ng_str[NG_DOM];
228 		nextgrp = nextgrp->ng_next;
229 		return (1);
230 	}
231 	return (0);
232 }
233 
234 /*
235  * endnetgrent() - cleanup
236  */
237 void
238 endnetgrent(void)
239 {
240 	struct linelist *lp, *olp;
241 	struct netgrp *gp, *ogp;
242 
243 	lp = linehead;
244 	while (lp) {
245 		olp = lp;
246 		lp = lp->l_next;
247 		free(olp->l_groupname);
248 		free(olp->l_line);
249 		free(olp);
250 	}
251 	linehead = NULL;
252 	if (grouphead.grname) {
253 		free(grouphead.grname);
254 		grouphead.grname = NULL;
255 	}
256 	gp = grouphead.gr;
257 	while (gp) {
258 		ogp = gp;
259 		gp = gp->ng_next;
260 		free(ogp->ng_str[NG_HOST]);
261 		free(ogp->ng_str[NG_USER]);
262 		free(ogp->ng_str[NG_DOM]);
263 		free(ogp);
264 	}
265 	grouphead.gr = NULL;
266 	nextgrp = NULL;
267 #ifdef YP
268 	_netgr_yp_enabled = 0;
269 #endif
270 }
271 
272 #ifdef YP
273 static int
274 _listmatch(const char *list, const char *group, int len)
275 {
276 	const char *ptr = list;
277 	const char *cptr;
278 	int glen = strlen(group);
279 
280 	/* skip possible leading whitespace */
281 	while (isspace((unsigned char)*ptr))
282 		ptr++;
283 
284 	while (ptr < list + len) {
285 		cptr = ptr;
286 		while(*ptr != ','  && *ptr != '\0' && !isspace((unsigned char)*ptr))
287 			ptr++;
288 		if (strncmp(cptr, group, glen) == 0 && glen == (ptr - cptr))
289 			return (1);
290 		while (*ptr == ','  || isspace((unsigned char)*ptr))
291 			ptr++;
292 	}
293 
294 	return (0);
295 }
296 
297 static int
298 _revnetgr_lookup(char* lookupdom, char* map, const char* str,
299 		 const char* dom, const char* group)
300 {
301 	int y, rv, rot;
302 	char key[MAXHOSTNAMELEN];
303 	char *result;
304 	int resultlen;
305 
306 	for (rot = 0; ; rot++) {
307 		switch (rot) {
308 		case 0:
309 			snprintf(key, MAXHOSTNAMELEN, "%s.%s", str,
310 			    dom ? dom : lookupdom);
311 			break;
312 		case 1:
313 			snprintf(key, MAXHOSTNAMELEN, "%s.*", str);
314 			break;
315 		case 2:
316 			snprintf(key, MAXHOSTNAMELEN, "*.%s",
317 			    dom ? dom : lookupdom);
318 			break;
319 		case 3:
320 			snprintf(key, MAXHOSTNAMELEN, "*.*");
321 			break;
322 		default:
323 			return (0);
324 		}
325 		y = yp_match(lookupdom, map, key, strlen(key), &result,
326 		    &resultlen);
327 		if (y == 0) {
328 			rv = _listmatch(result, group, resultlen);
329 			free(result);
330 			if (rv)
331 				return (1);
332 		} else if (y != YPERR_KEY) {
333 			/*
334 			 * If we get an error other than 'no
335 			 * such key in map' then something is
336 			 * wrong and we should stop the search.
337 			 */
338 			return (-1);
339 		}
340 	}
341 }
342 #endif
343 
344 /*
345  * Search for a match in a netgroup.
346  */
347 int
348 innetgr(const char *group, const char *host, const char *user, const char *dom)
349 {
350 	char *hst, *usr, *dm;
351 	/* Sanity check */
352 
353 	if (group == NULL || !strlen(group))
354 		return (0);
355 
356 #ifdef YP
357 	_yp_innetgr = 1;
358 #endif
359 	setnetgrent(group);
360 #ifdef YP
361 	_yp_innetgr = 0;
362 	/*
363 	 * If we're in NIS-only mode, do the search using
364 	 * NIS 'reverse netgroup' lookups.
365 	 *
366 	 * What happens with 'reverse netgroup' lookups:
367 	 *
368 	 * 1) try 'reverse netgroup' lookup
369 	 *    1.a) if host is specified and user is null:
370 	 *         look in netgroup.byhost
371 	 *         (try host.domain, host.*, *.domain or *.*)
372 	 *         if found, return yes
373 	 *    1.b) if user is specified and host is null:
374 	 *         look in netgroup.byuser
375 	 *         (try host.domain, host.*, *.domain or *.*)
376 	 *         if found, return yes
377 	 *    1.c) if both host and user are specified,
378 	 *         don't do 'reverse netgroup' lookup.  It won't work.
379 	 *    1.d) if neither host ane user are specified (why?!?)
380 	 *         don't do 'reverse netgroup' lookup either.
381 	 * 2) if domain is specified and 'reverse lookup' is done:
382 	 *    'reverse lookup' was authoritative.  bye bye.
383 	 * 3) otherwise, too bad, try it the slow way.
384 	 */
385 	if (_use_only_yp && (host == NULL) != (user == NULL)) {
386 		int ret;
387 		if(yp_get_default_domain(&_netgr_yp_domain))
388 			return (0);
389 		ret = _revnetgr_lookup(_netgr_yp_domain,
390 				      host?"netgroup.byhost":"netgroup.byuser",
391 				      host?host:user, dom, group);
392 		if (ret == 1)
393 			return (1);
394 		else if (ret == 0 && dom != NULL)
395 			return (0);
396 	}
397 
398 	setnetgrent(group);
399 #endif /* YP */
400 
401 	while (getnetgrent(&hst, &usr, &dm))
402 		if ((host == NULL || hst == NULL || !strcmp(host, hst)) &&
403 		    (user == NULL || usr == NULL || !strcmp(user, usr)) &&
404 		    ( dom == NULL ||  dm == NULL || !strcmp(dom, dm))) {
405 			endnetgrent();
406 			return (1);
407 		}
408 	endnetgrent();
409 	return (0);
410 }
411 
412 /*
413  * Parse the netgroup file setting up the linked lists.
414  */
415 static int
416 parse_netgrp(const char *group)
417 {
418 	struct netgrp *grp;
419 	struct linelist *lp = linehead;
420 	char **ng;
421 	char *epos, *gpos, *pos, *spos;
422 	int freepos, len, strpos;
423 #ifdef DEBUG
424 	int fields;
425 #endif
426 
427 	/*
428 	 * First, see if the line has already been read in.
429 	 */
430 	while (lp) {
431 		if (!strcmp(group, lp->l_groupname))
432 			break;
433 		lp = lp->l_next;
434 	}
435 	if (lp == NULL && (lp = read_for_group(group)) == NULL)
436 		return (1);
437 	if (lp->l_parsed) {
438 #ifdef DEBUG
439 		/*
440 		 * This error message is largely superflous since the
441 		 * code handles the error condition sucessfully, and
442 		 * spewing it out from inside libc can actually hose
443 		 * certain programs.
444 		 */
445 		fprintf(stderr, "Cycle in netgroup %s\n", lp->l_groupname);
446 #endif
447 		return (1);
448 	} else
449 		lp->l_parsed = 1;
450 	pos = lp->l_line;
451 	/* Watch for null pointer dereferences, dammit! */
452 	while (pos != NULL && *pos != '\0') {
453 		if (*pos == '(') {
454 			grp = malloc(sizeof(*grp));
455 			if (grp == NULL)
456 				return (1);
457 			ng = grp->ng_str;
458 			bzero(grp, sizeof(*grp));
459 			pos++;
460 			gpos = strsep(&pos, ")");
461 #ifdef DEBUG
462 			fields = 0;
463 #endif
464 			for (strpos = 0; strpos < 3; strpos++) {
465 				if ((spos = strsep(&gpos, ",")) == NULL) {
466 					/*
467 					 * All other systems I've tested
468 					 * return NULL for empty netgroup
469 					 * fields. It's up to user programs
470 					 * to handle the NULLs appropriately.
471 					 */
472 					ng[strpos] = NULL;
473 					continue;
474 				}
475 #ifdef DEBUG
476 				fields++;
477 #endif
478 				while (*spos == ' ' || *spos == '\t')
479 					spos++;
480 				if ((epos = strpbrk(spos, " \t"))) {
481 					*epos = '\0';
482 					len = epos - spos;
483 				} else
484 					len = strlen(spos);
485 				if (len <= 0)
486 					continue;
487 				ng[strpos] = malloc(len + 1);
488 				if (ng[strpos] == NULL) {
489 					for (freepos = 0; freepos < strpos;
490 					    freepos++)
491 						free(ng[freepos]);
492 					free(grp);
493 					return (1);
494 				}
495 				bcopy(spos, ng[strpos], len + 1);
496 			}
497 			grp->ng_next = grouphead.gr;
498 			grouphead.gr = grp;
499 #ifdef DEBUG
500 			/*
501 			 * Note: on other platforms, malformed netgroup
502 			 * entries are not normally flagged. While we
503 			 * can catch bad entries and report them, we should
504 			 * stay silent by default for compatibility's sake.
505 			 */
506 			if (fields < 3) {
507 				fprintf(stderr,
508 				"Bad entry (%s%s%s%s%s) in netgroup \"%s\"\n",
509 				    ng[NG_HOST] == NULL ? "" : ng[NG_HOST],
510 				    ng[NG_USER] == NULL ? "" : ",",
511 				    ng[NG_USER] == NULL ? "" : ng[NG_USER],
512 				    ng[NG_DOM] == NULL ? "" : ",",
513 				    ng[NG_DOM] == NULL ? "" : ng[NG_DOM],
514 				    lp->l_groupname);
515 			}
516 #endif
517 		} else {
518 			spos = strsep(&pos, ", \t");
519 			if (parse_netgrp(spos))
520 				continue;
521 		}
522 		if (pos == NULL)
523 			break;
524 		while (*pos == ' ' || *pos == ',' || *pos == '\t')
525 			pos++;
526 	}
527 	return (0);
528 }
529 
530 /*
531  * Read the netgroup file and save lines until the line for the netgroup
532  * is found. Return 1 if eof is encountered.
533  */
534 static struct linelist *
535 read_for_group(const char *group)
536 {
537 	char *linep, *olinep, *pos, *spos;
538 	int len, olen;
539 	int cont;
540 	struct linelist *lp;
541 	char line[LINSIZ + 2];
542 #ifdef YP
543 	char *result;
544 	int resultlen;
545 	linep = NULL;
546 
547 	while (_netgr_yp_enabled || fgets(line, LINSIZ, netf) != NULL) {
548 		if (_netgr_yp_enabled) {
549 			if(!_netgr_yp_domain)
550 				if(yp_get_default_domain(&_netgr_yp_domain))
551 					continue;
552 			if (yp_match(_netgr_yp_domain, "netgroup", group,
553 			    strlen(group), &result, &resultlen)) {
554 				free(result);
555 				if (_use_only_yp)
556 					return ((struct linelist *)0);
557 				else {
558 					_netgr_yp_enabled = 0;
559 					continue;
560 				}
561 			}
562 			snprintf(line, LINSIZ, "%s %s", group, result);
563 			free(result);
564 		}
565 #else
566 	linep = NULL;
567 	while (fgets(line, LINSIZ, netf) != NULL) {
568 #endif
569 		pos = (char *)&line;
570 #ifdef YP
571 		if (*pos == '+') {
572 			_netgr_yp_enabled = 1;
573 			continue;
574 		}
575 #endif
576 		if (*pos == '#')
577 			continue;
578 		while (*pos == ' ' || *pos == '\t')
579 			pos++;
580 		spos = pos;
581 		while (*pos != ' ' && *pos != '\t' && *pos != '\n' &&
582 			*pos != '\0')
583 			pos++;
584 		len = pos - spos;
585 		while (*pos == ' ' || *pos == '\t')
586 			pos++;
587 		if (*pos != '\n' && *pos != '\0') {
588 			lp = (struct linelist *)malloc(sizeof (*lp));
589 			if (lp == NULL)
590 				return (NULL);
591 			lp->l_parsed = 0;
592 			lp->l_groupname = (char *)malloc(len + 1);
593 			if (lp->l_groupname == NULL) {
594 				free(lp);
595 				return (NULL);
596 			}
597 			bcopy(spos, lp->l_groupname, len);
598 			*(lp->l_groupname + len) = '\0';
599 			len = strlen(pos);
600 			olen = 0;
601 
602 			/*
603 			 * Loop around handling line continuations.
604 			 */
605 			do {
606 				if (*(pos + len - 1) == '\n')
607 					len--;
608 				if (*(pos + len - 1) == '\\') {
609 					len--;
610 					cont = 1;
611 				} else
612 					cont = 0;
613 				if (len > 0) {
614 					linep = malloc(olen + len + 1);
615 					if (linep == NULL) {
616 						free(lp->l_groupname);
617 						free(lp);
618 						return (NULL);
619 					}
620 					if (olen > 0) {
621 						bcopy(olinep, linep, olen);
622 						free(olinep);
623 					}
624 					bcopy(pos, linep + olen, len);
625 					olen += len;
626 					*(linep + olen) = '\0';
627 					olinep = linep;
628 				}
629 				if (cont) {
630 					if (fgets(line, LINSIZ, netf)) {
631 						pos = line;
632 						len = strlen(pos);
633 					} else
634 						cont = 0;
635 				}
636 			} while (cont);
637 			lp->l_line = linep;
638 			lp->l_next = linehead;
639 			linehead = lp;
640 
641 			/*
642 			 * If this is the one we wanted, we are done.
643 			 */
644 			if (!strcmp(lp->l_groupname, group))
645 				return (lp);
646 		}
647 	}
648 #ifdef YP
649 	/*
650 	 * Yucky. The recursive nature of this whole mess might require
651 	 * us to make more than one pass through the netgroup file.
652 	 * This might be best left outside the #ifdef YP, but YP is
653 	 * defined by default anyway, so I'll leave it like this
654 	 * until I know better.
655 	 */
656 	rewind(netf);
657 #endif
658 	return (NULL);
659 }
660