1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Common code and structures used by name-service-switch "compat" backends.
26  *
27  * Most of the code in the "compat" backend is a perverted form of code from
28  * the "files" backend;  this file is no exception.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <ctype.h>
37 #include <bsm/libbsm.h>
38 #include <user_attr.h>
39 #include "compat_common.h"
40 
41 /*
42  * This should be in a header.
43  */
44 
45 extern int yp_get_default_domain(char **domain);
46 
47 /*
48  * Routines to manage list of "-" users for get{pw, sp, gr}ent().  Current
49  *   implementation is completely moronic; we use a linked list.  But then
50  *   that's what it's always done in 4.x...
51  */
52 
53 struct setofstrings {
54 	char			*name;
55 	struct setofstrings	*next;
56 	/*
57 	 * === Should get smart and malloc the string and pointer as one
58 	 *	object rather than two.
59 	 */
60 };
61 typedef struct setofstrings	*strset_t;
62 
63 static void
64 strset_free(ssp)
65 	strset_t	*ssp;
66 {
67 	strset_t	cur, nxt;
68 
69 	for (cur = *ssp;  cur != 0;  cur = nxt) {
70 		nxt = cur->next;
71 		free(cur->name);
72 		free(cur);
73 	}
74 	*ssp = 0;
75 }
76 
77 static boolean_t
78 strset_add(ssp, nam)
79 	strset_t	*ssp;
80 	const char	*nam;
81 {
82 	strset_t	new;
83 
84 	if (0 == (new = (strset_t)malloc(sizeof (*new)))) {
85 		return (B_FALSE);
86 	}
87 	if (0 == (new->name = malloc(strlen(nam) + 1))) {
88 		free(new);
89 		return (B_FALSE);
90 	}
91 	strcpy(new->name, nam);
92 	new->next = *ssp;
93 	*ssp = new;
94 	return (B_TRUE);
95 }
96 
97 static boolean_t
98 strset_in(ssp, nam)
99 	const strset_t	*ssp;
100 	const char	*nam;
101 {
102 	strset_t	cur;
103 
104 	for (cur = *ssp;  cur != 0;  cur = cur->next) {
105 		if (strcmp(cur->name, nam) == 0) {
106 			return (B_TRUE);
107 		}
108 	}
109 	return (B_FALSE);
110 }
111 
112 
113 struct compat_backend {
114 	compat_backend_op_t	*ops;
115 	int			n_ops;
116 	const char		*filename;
117 	FILE			*f;
118 	int			minbuf;
119 	char			*buf;
120 	int			linelen;	/* <== Explain use, lifetime */
121 
122 	nss_db_initf_t		db_initf;
123 	nss_db_root_t		*db_rootp;	/* Shared between instances */
124 	nss_getent_t		db_context;	/* Per-instance enumeration */
125 
126 	compat_get_name		getnamef;
127 	compat_merge_func	mergef;
128 
129 	/* We wouldn't need all this hokey state stuff if we */
130 	/*   used another thread to implement a coroutine... */
131 	enum {
132 		GETENT_FILE,
133 		GETENT_NETGROUP,
134 		GETENT_ATTRDB,
135 		GETENT_ALL,
136 		GETENT_DONE
137 	}			state;
138 	strset_t		minuses;
139 
140 	int			permit_netgroups;
141 	const char		*yp_domain;
142 	nss_backend_t		*getnetgrent_backend;
143 	char			*netgr_buffer;
144 };
145 
146 
147 /*
148  * Lookup and enumeration routines for +@group and -@group.
149  *
150  * This code knows a lot more about lib/libc/port/gen/getnetgrent.c than
151  *   is really healthy.  The set/get/end routines below duplicate code
152  *   from that file, but keep the state information per-backend-instance
153  *   instead of just per-process.
154  */
155 
156 extern void _nss_initf_netgroup(nss_db_params_t *);
157 /*
158  * Should really share the db_root in getnetgrent.c in order to get the
159  *   resource-management quotas right, but this will have to do.
160  */
161 static DEFINE_NSS_DB_ROOT(netgr_db_root);
162 
163 static boolean_t
164 netgr_in(compat_backend_ptr_t be, const char *group, const char *user)
165 {
166 	if (be->yp_domain == 0) {
167 		if (yp_get_default_domain((char **)&be->yp_domain) != 0) {
168 			return (B_FALSE);
169 		}
170 	}
171 	return (innetgr(group, 0, user, be->yp_domain));
172 }
173 
174 static boolean_t
175 netgr_all_in(compat_backend_ptr_t be, const char *group)
176 {
177 	/*
178 	 * 4.x does this;  ours not to reason why...
179 	 */
180 	return (netgr_in(be, group, "*"));
181 }
182 
183 static void
184 netgr_set(be, netgroup)
185 	compat_backend_ptr_t	be;
186 	const char		*netgroup;
187 {
188 	/*
189 	 * ===> Need comment to explain that this first "if" is optimizing
190 	 *	for the same-netgroup-as-last-time case
191 	 */
192 	if (be->getnetgrent_backend != 0 &&
193 	    NSS_INVOKE_DBOP(be->getnetgrent_backend,
194 			    NSS_DBOP_SETENT,
195 			    (void *) netgroup) != NSS_SUCCESS) {
196 		NSS_INVOKE_DBOP(be->getnetgrent_backend, NSS_DBOP_DESTRUCTOR,
197 				0);
198 		be->getnetgrent_backend = 0;
199 	}
200 	if (be->getnetgrent_backend == 0) {
201 		struct nss_setnetgrent_args	args;
202 
203 		args.netgroup	= netgroup;
204 		args.iterator	= 0;
205 		nss_search(&netgr_db_root, _nss_initf_netgroup,
206 			NSS_DBOP_NETGROUP_SET, &args);
207 		be->getnetgrent_backend = args.iterator;
208 	}
209 }
210 
211 static boolean_t
212 netgr_next_u(be, up)
213 	compat_backend_ptr_t	be;
214 	char			**up;
215 {
216 	if (be->netgr_buffer == 0 &&
217 	    (be->netgr_buffer = malloc(NSS_BUFLEN_NETGROUP)) == 0) {
218 		/* Out of memory */
219 		return (B_FALSE);
220 	}
221 
222 	do {
223 		struct nss_getnetgrent_args	args;
224 
225 		args.buffer	= be->netgr_buffer;
226 		args.buflen	= NSS_BUFLEN_NETGROUP;
227 		args.status	= NSS_NETGR_NO;
228 
229 		if (be->getnetgrent_backend != 0) {
230 			NSS_INVOKE_DBOP(be->getnetgrent_backend,
231 					NSS_DBOP_GETENT, &args);
232 		}
233 
234 		if (args.status == NSS_NETGR_FOUND) {
235 			*up	  = args.retp[NSS_NETGR_USER];
236 		} else {
237 			return (B_FALSE);
238 		}
239 	} while (*up == 0);
240 	return (B_TRUE);
241 }
242 
243 static void
244 netgr_end(be)
245 	compat_backend_ptr_t	be;
246 {
247 	if (be->getnetgrent_backend != 0) {
248 		NSS_INVOKE_DBOP(be->getnetgrent_backend,
249 				NSS_DBOP_DESTRUCTOR, 0);
250 		be->getnetgrent_backend = 0;
251 	}
252 	if (be->netgr_buffer != 0) {
253 		free(be->netgr_buffer);
254 		be->netgr_buffer = 0;
255 	}
256 }
257 
258 
259 #define	MAXFIELDS 9	/* Sufficient for passwd (7), shadow (9), group (4) */
260 
261 static nss_status_t
262 do_merge(be, args, instr, linelen)
263 	compat_backend_ptr_t	be;
264 	nss_XbyY_args_t		*args;
265 	const char		*instr;
266 	int			linelen;
267 {
268 	char			*fields[MAXFIELDS];
269 	int			i;
270 	int			overrides;
271 	const char		*p;
272 	const char		*end = instr + linelen;
273 	nss_status_t		res;
274 
275 	/*
276 	 * Potential optimization:  only perform the field-splitting nonsense
277 	 *   once per input line (at present, "+" and "+@netgroup" entries
278 	 *   will cause us to do this multiple times in getent() requests).
279 	 */
280 
281 	for (i = 0;  i < MAXFIELDS;  i++) {
282 		fields[i] = 0;
283 	}
284 	for (p = instr, overrides = 0, i = 0; /* no test */; i++) {
285 		const char	*q = memchr(p, ':', end - p);
286 		const char	*r = (q == 0) ? end : q;
287 		ssize_t		len = r - p;
288 
289 		if (len > 0) {
290 			char	*s = malloc(len + 1);
291 			if (s == 0) {
292 				overrides = -1;	/* Indicates "you lose" */
293 				break;
294 			}
295 			memcpy(s, p, len);
296 			s[len] = '\0';
297 			fields[i] = s;
298 			overrides++;
299 		}
300 		if (q == 0) {
301 			/* End of line */
302 			break;
303 		} else {
304 			/* Skip the colon at (*q) */
305 			p = q + 1;
306 		}
307 	}
308 	if (overrides == 1) {
309 		/* No real overrides, return (*args) intact */
310 		res = NSS_SUCCESS;
311 	} else if (overrides > 1) {
312 		/*
313 		 * The zero'th field is always nonempty (+/-...), but at least
314 		 *   one other field was also nonempty, i.e. wants to override
315 		 */
316 		switch ((*be->mergef)(be, args, (const char **)fields)) {
317 		    case NSS_STR_PARSE_SUCCESS:
318 			args->returnval	= args->buf.result;
319 			args->erange	= 0;
320 			res = NSS_SUCCESS;
321 			break;
322 		    case NSS_STR_PARSE_ERANGE:
323 			args->returnval	= 0;
324 			args->erange	= 1;
325 			res = NSS_NOTFOUND;
326 			break;
327 		    case NSS_STR_PARSE_PARSE:
328 			args->returnval	= 0;
329 			args->erange	= 0;
330 /* ===> Very likely the wrong thing to do... */
331 			res = NSS_NOTFOUND;
332 			break;
333 		}
334 	} else {
335 		args->returnval	= 0;
336 		args->erange	= 0;
337 		res = NSS_UNAVAIL;	/* ==> Right? */
338 	}
339 
340 	for (i = 0;  i < MAXFIELDS;  i++) {
341 		if (fields[i] != 0) {
342 			free(fields[i]);
343 		}
344 	}
345 
346 	return (res);
347 }
348 
349 /*ARGSUSED*/
350 nss_status_t
351 _nss_compat_setent(be, dummy)
352 	compat_backend_ptr_t	be;
353 	void			*dummy;
354 {
355 	if (be->f == 0) {
356 		if (be->filename == 0) {
357 			/* Backend isn't initialized properly? */
358 			return (NSS_UNAVAIL);
359 		}
360 		if ((be->f = fopen(be->filename, "rF")) == 0) {
361 			return (NSS_UNAVAIL);
362 		}
363 	} else {
364 		rewind(be->f);
365 	}
366 	strset_free(&be->minuses);
367 	/* ===> ??? nss_endent(be->db_rootp, be->db_initf, &be->db_context); */
368 
369 	if ((strcmp(be->filename, USERATTR_FILENAME) == 0) ||
370 	    (strcmp(be->filename, AUDITUSER_FILENAME) == 0))
371 		be->state = GETENT_ATTRDB;
372 	else
373 		be->state = GETENT_FILE;
374 
375 	/* ===> ??  netgroup stuff? */
376 	return (NSS_SUCCESS);
377 }
378 
379 /*ARGSUSED*/
380 nss_status_t
381 _nss_compat_endent(be, dummy)
382 	compat_backend_ptr_t	be;
383 	void			*dummy;
384 {
385 	if (be->f != 0) {
386 		fclose(be->f);
387 		be->f = 0;
388 	}
389 	if (be->buf != 0) {
390 		free(be->buf);
391 		be->buf = 0;
392 	}
393 	nss_endent(be->db_rootp, be->db_initf, &be->db_context);
394 
395 	be->state = GETENT_FILE; /* Probably superfluous but comforting */
396 	strset_free(&be->minuses);
397 	netgr_end(be);
398 
399 	/*
400 	 * Question: from the point of view of resource-freeing vs. time to
401 	 *   start up again, how much should we do in endent() and how much
402 	 *   in the destructor?
403 	 */
404 	return (NSS_SUCCESS);
405 }
406 
407 /*ARGSUSED*/
408 nss_status_t
409 _nss_compat_destr(be, dummy)
410 	compat_backend_ptr_t	be;
411 	void			*dummy;
412 {
413 	if (be != 0) {
414 		if (be->f != 0) {
415 			_nss_compat_endent(be, 0);
416 		}
417 		nss_delete(be->db_rootp);
418 		nss_delete(&netgr_db_root);
419 		free(be);
420 	}
421 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
422 }
423 
424 static int
425 read_line(f, buffer, buflen)
426 	FILE			*f;
427 	char			*buffer;
428 	int			buflen;
429 {
430 	/*CONSTCOND*/
431 	while (1) {
432 		int	linelen;
433 
434 		if (fgets(buffer, buflen, f) == 0) {
435 			/* End of file */
436 			return (-1);
437 		}
438 		linelen = strlen(buffer);
439 		/* linelen >= 1 (since fgets didn't return 0) */
440 
441 		if (buffer[linelen - 1] == '\n') {
442 			/*
443 			 * ===> The code below that calls read_line() doesn't
444 			 *	play by the rules;  it assumes in places that
445 			 *	the line is null-terminated.  For now we'll
446 			 *	humour it.
447 			 */
448 			buffer[--linelen] = '\0';
449 			return (linelen);
450 		}
451 		if (feof(f)) {
452 			/* Line is last line in file, and has no newline */
453 			return (linelen);
454 		}
455 		/* Line too long for buffer;  toss it and loop for next line */
456 		/* ===== should syslog() in cases where previous code did */
457 		while (fgets(buffer, buflen, f) != 0 &&
458 		    buffer[strlen(buffer) - 1] != '\n') {
459 			;
460 		}
461 	}
462 }
463 
464 static int
465 is_nss_lookup_by_name(int attrdb, nss_dbop_t op)
466 {
467 	int result = 0;
468 
469 	if ((attrdb != 0) &&
470 	    ((op == NSS_DBOP_AUDITUSER_BYNAME) ||
471 	    (op == NSS_DBOP_USERATTR_BYNAME))) {
472 		result = 1;
473 	} else if ((attrdb == 0) &&
474 	    ((op == NSS_DBOP_GROUP_BYNAME) ||
475 	    (op == NSS_DBOP_PASSWD_BYNAME) ||
476 	    (op == NSS_DBOP_SHADOW_BYNAME))) {
477 		result = 1;
478 	}
479 
480 	return (result);
481 }
482 
483 /*ARGSUSED*/
484 nss_status_t
485 _attrdb_compat_XY_all(be, argp, netdb, check, op_num)
486     compat_backend_ptr_t be;
487     nss_XbyY_args_t *argp;
488     int netdb;
489     compat_XY_check_func check;
490     nss_dbop_t op_num;
491 {
492 	int		parsestat;
493 	int		(*func)();
494 	const char	*filter = argp->key.name;
495 	nss_status_t	res;
496 
497 #ifdef	DEBUG
498 	(void) fprintf(stdout, "\n[compat_common.c: _attrdb_compat_XY_all]\n");
499 #endif	/* DEBUG */
500 
501 	if (be->buf == 0 &&
502 	    (be->buf = malloc(be->minbuf)) == 0) {
503 		return (NSS_UNAVAIL);
504 	}
505 	if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
506 		return (res);
507 	}
508 	res = NSS_NOTFOUND;
509 
510 	/*CONSTCOND*/
511 	while (1) {
512 		int	linelen;
513 		char	*instr	= be->buf;
514 
515 		if ((linelen = read_line(be->f, instr, be->minbuf)) < 0) {
516 			/* End of file */
517 			argp->returnval = 0;
518 			argp->erange    = 0;
519 			break;
520 		}
521 		if (filter != 0 && strstr(instr, filter) == 0) {
522 			/*
523 			 * Optimization:  if the entry doesn't contain the
524 			 * filter string then it can't be the entry we want,
525 			 * so don't bother looking more closely at it.
526 			 */
527 			continue;
528 		}
529 		if (netdb) {
530 			char	*first;
531 			char	*last;
532 
533 			if ((last = strchr(instr, '#')) == 0) {
534 				last = instr + linelen;
535 			}
536 			*last-- = '\0';		/* Nuke '\n' or #comment */
537 
538 			/*
539 			 * Skip leading whitespace.  Normally there isn't
540 			 * any, so it's not worth calling strspn().
541 			 */
542 			for (first = instr;  isspace(*first);  first++) {
543 				;
544 			}
545 			if (*first == '\0') {
546 				continue;
547 			}
548 			/*
549 			 * Found something non-blank on the line.  Skip back
550 			 * over any trailing whitespace;  since we know
551 			 * there's non-whitespace earlier in the line,
552 			 * checking for termination is easy.
553 			 */
554 			while (isspace(*last)) {
555 				--last;
556 			}
557 			linelen = last - first + 1;
558 			if (first != instr) {
559 				instr = first;
560 			}
561 		}
562 		argp->returnval = 0;
563 		func = argp->str2ent;
564 		parsestat = (*func)(instr, linelen, argp->buf.result,
565 					argp->buf.buffer, argp->buf.buflen);
566 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
567 			argp->returnval = argp->buf.result;
568 			if (check == 0 || (*check)(argp)) {
569 				res = NSS_SUCCESS;
570 				break;
571 			}
572 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
573 			argp->erange = 1;
574 			break;
575 		}
576 	}
577 	/*
578 	 * stayopen is set to 0 by default in order to close the opened
579 	 * file.  Some applications may break if it is set to 1.
580 	 */
581 	if (check != 0 && !argp->stayopen) {
582 		(void) _nss_compat_endent(be, 0);
583 	}
584 
585 	if (res != NSS_SUCCESS) {
586 		if ((op_num == NSS_DBOP_USERATTR_BYNAME) ||
587 		    (op_num == NSS_DBOP_AUDITUSER_BYNAME)) {
588 			res = nss_search(be->db_rootp,
589 			    be->db_initf,
590 			    op_num,
591 			    argp);
592 		} else {
593 			res = nss_getent(be->db_rootp,
594 			    be->db_initf, &be->db_context, argp);
595 		}
596 		if (res != NSS_SUCCESS) {
597 			argp->returnval	= 0;
598 			argp->erange	= 0;
599 		}
600 	}
601 
602 	return (res);
603 }
604 
605 nss_status_t
606 _nss_compat_XY_all(be, args, check, op_num)
607 	compat_backend_ptr_t	be;
608 	nss_XbyY_args_t		*args;
609 	compat_XY_check_func	check;
610 	nss_dbop_t		op_num;
611 {
612 	nss_status_t		res;
613 	int			parsestat;
614 
615 	if (be->buf == 0 &&
616 	    (be->buf = malloc(be->minbuf)) == 0) {
617 		return (NSS_UNAVAIL); /* really panic, malloc failed */
618 	}
619 
620 	if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
621 		return (res);
622 	}
623 
624 	res = NSS_NOTFOUND;
625 
626 	/*CONSTCOND*/
627 	while (1) {
628 		int		linelen;
629 		char		*instr	= be->buf;
630 		char		*colon;
631 
632 		linelen = read_line(be->f, instr, be->minbuf);
633 		if (linelen < 0) {
634 			/* End of file */
635 			args->returnval = 0;
636 			args->erange    = 0;
637 			break;
638 		}
639 
640 		args->returnval = 0;	/* reset for both types of entries */
641 
642 		if (instr[0] != '+' && instr[0] != '-') {
643 			/* Simple, wholesome, God-fearing entry */
644 			parsestat = (*args->str2ent)(instr, linelen,
645 						    args->buf.result,
646 						    args->buf.buffer,
647 						    args->buf.buflen);
648 			if (parsestat == NSS_STR_PARSE_SUCCESS) {
649 				args->returnval = args->buf.result;
650 				if ((*check)(args) != 0) {
651 					res = NSS_SUCCESS;
652 					break;
653 				}
654 
655 /* ===> Check the Dani logic here... */
656 
657 			} else if (parsestat == NSS_STR_PARSE_ERANGE) {
658 				args->erange = 1;
659 				res = NSS_NOTFOUND;
660 				break;
661 				/* should we just skip this one long line ? */
662 			} /* else if (parsestat == NSS_STR_PARSE_PARSE) */
663 				/* don't care ! */
664 
665 /* ==> ?? */		continue;
666 		}
667 
668 		/*
669 		 * Process "+", "+name", "+@netgroup", "-name" or "-@netgroup"
670 		 *
671 		 * This code is optimized for lookups by name.
672 		 *
673 		 * For lookups by identifier search key cannot be matched with
674 		 * the name of the "+" or "-" entry. So nss_search() is to be
675 		 * called before extracting the name i.e. via (*be->getnamef)().
676 		 *
677 		 * But for lookups by name, search key is compared with the name
678 		 * of the "+" or "-" entry to acquire a match and thus
679 		 * unnesessary calls to nss_search() is eliminated. Also for
680 		 * matching "-" entries, calls to nss_search() is eliminated.
681 		 */
682 
683 		if ((colon = strchr(instr, ':')) != 0) {
684 			*colon = '\0';	/* terminate field to extract name */
685 		}
686 
687 		if (instr[1] == '@') {
688 			/*
689 			 * Case 1:
690 			 * The entry is of the form "+@netgroup" or
691 			 * "-@netgroup".  If we're performing a lookup by name,
692 			 * we can simply extract the name from the search key
693 			 * (i.e. args->key.name).  If not, then we must call
694 			 * nss_search() before extracting the name via the
695 			 * get_XXname() function. i.e. (*be->getnamef)(args).
696 			 */
697 			if (is_nss_lookup_by_name(0, op_num) != 0) {
698 				/* compare then search */
699 				if (!be->permit_netgroups ||
700 				    !netgr_in(be, instr + 2, args->key.name))
701 					continue;
702 				if (instr[0] == '+') {
703 					/* need to search for "+" entry */
704 					nss_search(be->db_rootp, be->db_initf,
705 					    op_num, args);
706 					if (args->returnval == 0)
707 						continue;
708 				}
709 			} else {
710 				/* search then compare */
711 				nss_search(be->db_rootp, be->db_initf, op_num,
712 				    args);
713 				if (args->returnval == 0)
714 					continue;
715 				if (!be->permit_netgroups ||
716 				    !netgr_in(be, instr + 2,
717 				    (*be->getnamef)(args)))
718 					continue;
719 			}
720 		} else if (instr[1] == '\0') {
721 			/*
722 			 * Case 2:
723 			 * The entry is of the form "+" or "-".  The former
724 			 * allows all entries from name services.  The latter
725 			 * is illegal and ought to be ignored.
726 			 */
727 			if (instr[0] == '-')
728 				continue;
729 			/* need to search for "+" entry */
730 			nss_search(be->db_rootp, be->db_initf, op_num, args);
731 			if (args->returnval == 0)
732 				continue;
733 		} else {
734 			/*
735 			 * Case 3:
736 			 * The entry is of the form "+name" or "-name".
737 			 * If we're performing a lookup by name, we can simply
738 			 * extract the name from the search key
739 			 * (i.e. args->key.name).  If not, then we must call
740 			 * nss_search() before extracting the name via the
741 			 * get_XXname() function. i.e. (*be->getnamef)(args).
742 			 */
743 			if (is_nss_lookup_by_name(0, op_num) != 0) {
744 				/* compare then search */
745 				if (strcmp(instr + 1, args->key.name) != 0)
746 					continue;
747 				if (instr[0] == '+') {
748 					/* need to search for "+" entry */
749 					nss_search(be->db_rootp, be->db_initf,
750 					    op_num, args);
751 					if (args->returnval == 0)
752 						continue;
753 				}
754 			} else {
755 				/* search then compare */
756 				nss_search(be->db_rootp, be->db_initf, op_num,
757 				    args);
758 				if (args->returnval == 0)
759 					continue;
760 				if (strcmp(instr + 1, (*be->getnamef)(args))
761 				    != 0)
762 					continue;
763 			}
764 		}
765 		if (instr[0] == '-') {
766 			/* no need to search for "-" entry */
767 			args->returnval = 0;
768 			args->erange = 0;
769 			res = NSS_NOTFOUND;
770 		} else {
771 			if (colon != 0)
772 				*colon = ':';	/* restoration */
773 			res = do_merge(be, args, instr, linelen);
774 		}
775 		break;
776 	}
777 
778 	/*
779 	 * stayopen is set to 0 by default in order to close the opened
780 	 * file.  Some applications may break if it is set to 1.
781 	 */
782 	if (!args->stayopen) {
783 		(void) _nss_compat_endent(be, 0);
784 	}
785 
786 	return (res);
787 }
788 
789 nss_status_t
790 _nss_compat_getent(be, a)
791 	compat_backend_ptr_t	be;
792 	void			*a;
793 {
794 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
795 	nss_status_t		res;
796 	char			*colon = 0; /* <=== need comment re lifetime */
797 
798 	if (be->f == 0) {
799 		if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
800 			return (res);
801 		}
802 	}
803 
804 	if (be->buf == 0 &&
805 	    (be->buf = malloc(be->minbuf)) == 0) {
806 		return (NSS_UNAVAIL); /* really panic, malloc failed */
807 	}
808 
809 	/*CONSTCOND*/
810 	while (1) {
811 		char		*instr	= be->buf;
812 		int		linelen;
813 		char		*name;	/* === Need more distinctive label */
814 		const char	*savename;
815 
816 		/*
817 		 * In the code below...
818 		 *    break	means "I found one, I think" (i.e. goto the
819 		 *		code after the end of the switch statement),
820 		 *    continue	means "Next candidate"
821 		 *		(i.e. loop around to the switch statement),
822 		 *    return	means "I'm quite sure" (either Yes or No).
823 		 */
824 		switch (be->state) {
825 
826 		    case GETENT_DONE:
827 			args->returnval	= 0;
828 			args->erange	= 0;
829 			return (NSS_NOTFOUND);
830 
831 		    case GETENT_ATTRDB:
832 			res = _attrdb_compat_XY_all(be,
833 			    args, 1, (compat_XY_check_func)NULL, 0);
834 			return (res);
835 
836 		    case GETENT_FILE:
837 			linelen = read_line(be->f, instr, be->minbuf);
838 			if (linelen < 0) {
839 				/* End of file */
840 				be->state = GETENT_DONE;
841 				continue;
842 			}
843 			if ((colon = strchr(instr, ':')) != 0) {
844 				*colon = '\0';
845 			}
846 			if (instr[0] == '-') {
847 				if (instr[1] != '@') {
848 					strset_add(&be->minuses, instr + 1);
849 				} else if (be->permit_netgroups) {
850 					netgr_set(be, instr + 2);
851 					while (netgr_next_u(be, &name)) {
852 						strset_add(&be->minuses,
853 							name);
854 					}
855 					netgr_end(be);
856 				} /* Else (silently) ignore the entry */
857 				continue;
858 			} else if (instr[0] != '+') {
859 				int	parsestat;
860 				/*
861 				 * Normal entry, no +/- nonsense
862 				 */
863 				if (colon != 0) {
864 					*colon = ':';
865 				}
866 				args->returnval = 0;
867 				parsestat = (*args->str2ent)(instr, linelen,
868 							args->buf.result,
869 							args->buf.buffer,
870 							args->buf.buflen);
871 				if (parsestat == NSS_STR_PARSE_SUCCESS) {
872 					args->returnval = args->buf.result;
873 					return (NSS_SUCCESS);
874 				}
875 				/* ==> ?? Treat ERANGE differently ?? */
876 				if (parsestat == NSS_STR_PARSE_ERANGE) {
877 					args->returnval = 0;
878 					args->erange = 1;
879 					return (NSS_NOTFOUND);
880 				}
881 				/* Skip the offending entry, get next */
882 				continue;
883 			} else if (instr[1] == '\0') {
884 				/* Plain "+" */
885 				nss_setent(be->db_rootp, be->db_initf,
886 					&be->db_context);
887 				be->state = GETENT_ALL;
888 				be->linelen = linelen;
889 				continue;
890 			} else if (instr[1] == '@') {
891 				/* "+@netgroup" */
892 				netgr_set(be, instr + 2);
893 				be->state = GETENT_NETGROUP;
894 				be->linelen = linelen;
895 				continue;
896 			} else {
897 				/* "+name" */
898 				name = instr + 1;
899 				break;
900 			}
901 			/* NOTREACHED */
902 
903 		    case GETENT_ALL:
904 			linelen = be->linelen;
905 			args->returnval = 0;
906 			nss_getent(be->db_rootp, be->db_initf,
907 				&be->db_context, args);
908 			if (args->returnval == 0) {
909 				/* ==> ?? Treat ERANGE differently ?? */
910 				nss_endent(be->db_rootp, be->db_initf,
911 					&be->db_context);
912 				be->state = GETENT_FILE;
913 				continue;
914 			}
915 			if (strset_in(&be->minuses, (*be->getnamef)(args))) {
916 				continue;
917 			}
918 			name = 0; /* tell code below we've done the lookup */
919 			break;
920 
921 		    case GETENT_NETGROUP:
922 			linelen = be->linelen;
923 			if (!netgr_next_u(be, &name)) {
924 				netgr_end(be);
925 				be->state = GETENT_FILE;
926 				continue;
927 			}
928 			/* pass "name" variable to code below... */
929 			break;
930 		}
931 
932 		if (name != 0) {
933 			if (strset_in(&be->minuses, name)) {
934 				continue;
935 			}
936 			/*
937 			 * Do a getXXXnam(name).  If we were being pure,
938 			 *   we'd introduce yet another function-pointer
939 			 *   that the database-specific code had to supply
940 			 *   to us.  Instead we'll be grotty and hard-code
941 			 *   the knowledge that
942 			 *	(a) The username is always passwd in key.name,
943 			 *	(b) NSS_DBOP_PASSWD_BYNAME ==
944 			 *		NSS_DBOP_SHADOW_BYNAME ==
945 			 *		NSS_DBOP_next_iter.
946 			 */
947 			savename = args->key.name;
948 			args->key.name	= name;
949 			args->returnval	= 0;
950 			nss_search(be->db_rootp, be->db_initf,
951 				NSS_DBOP_next_iter, args);
952 			args->key.name = savename;  /* In case anyone cares */
953 		}
954 		/*
955 		 * Found one via "+", "+name" or "@netgroup".
956 		 * Override some fields if the /etc file says to do so.
957 		 */
958 		if (args->returnval == 0) {
959 			/* ==> ?? Should treat erange differently? */
960 			continue;
961 		}
962 		/* 'colon' was set umpteen iterations ago in GETENT_FILE */
963 		if (colon != 0) {
964 			*colon = ':';
965 			colon = 0;
966 		}
967 		return (do_merge(be, args, instr, linelen));
968 	}
969 }
970 
971 /* We don't use this directly;  we just copy the bits when we want to	 */
972 /* initialize the variable (in the compat_backend struct) that we do use */
973 static DEFINE_NSS_GETENT(context_initval);
974 
975 nss_backend_t *
976 _nss_compat_constr(ops, n_ops, filename, min_bufsize, rootp, initf, netgroups,
977 		getname_func, merge_func)
978 	compat_backend_op_t	ops[];
979 	int			n_ops;
980 	const char		*filename;
981 	int			min_bufsize;
982 	nss_db_root_t		*rootp;
983 	nss_db_initf_t		initf;
984 	int			netgroups;
985 	compat_get_name		getname_func;
986 	compat_merge_func	merge_func;
987 {
988 	compat_backend_ptr_t	be;
989 
990 	if ((be = (compat_backend_ptr_t)malloc(sizeof (*be))) == 0) {
991 		return (0);
992 	}
993 	be->ops		= ops;
994 	be->n_ops	= n_ops;
995 	be->filename	= filename;
996 	be->f		= 0;
997 	be->minbuf	= min_bufsize;
998 	be->buf		= 0;
999 
1000 	be->db_rootp	= rootp;
1001 	be->db_initf	= initf;
1002 	be->db_context	= context_initval;
1003 
1004 	be->getnamef	= getname_func;
1005 	be->mergef	= merge_func;
1006 
1007 	if ((strcmp(be->filename, USERATTR_FILENAME) == 0) ||
1008 	    (strcmp(be->filename, AUDITUSER_FILENAME) == 0))
1009 		be->state = GETENT_ATTRDB;
1010 	else
1011 		be->state = GETENT_FILE;    /* i.e. do Automatic setent(); */
1012 
1013 	be->minuses	= 0;
1014 
1015 	be->permit_netgroups = netgroups;
1016 	be->yp_domain	= 0;
1017 	be->getnetgrent_backend	= 0;
1018 	be->netgr_buffer = 0;
1019 
1020 	return ((nss_backend_t *)be);
1021 }
1022