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