xref: /illumos-gate/usr/src/lib/nsswitch/compat/common/compat_common.c (revision 4e93fb0f6383eaac21897dcdae56b87118131e4d)
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 2007 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 <pwd.h>
40 #include <shadow.h>
41 #include <grp.h>
42 #include <unistd.h>	/* for GF_PATH */
43 #include <dlfcn.h>
44 #include "compat_common.h"
45 
46 /*
47  * This should be in a header.
48  */
49 
50 extern int yp_get_default_domain(char **domain);
51 
52 /* from libc */
53 extern int str2passwd(const char *instr, int lenstr, void *ent,
54 		char *buffer, int buflen);
55 extern int str2spwd(const char *instr, int lenstr, void *ent,
56 		char *buffer, int buflen);
57 extern int str2group(const char *instr, int lenstr, void *ent,
58 		char *buffer, int buflen);
59 
60 /* from libnsl */
61 extern char *_strtok_escape(char *, char *, char **);
62 
63 /*
64  * str2auuser_s and str2userattr_s are very simple version
65  * of the str2auuser() and str2userattr() that can be found in
66  * libnsl. They only copy the user name into the userstr_t
67  * or au_user_str_t structure (so check on user name can be
68  * performed).
69  */
70 static int
71 str2auuser_s(
72 	const char	*instr,
73 	int		lenstr,
74 	void		*ent,
75 	char		*buffer,
76 	int		buflen)
77 {
78 	char		*last = NULL;
79 	char		*sep = KV_TOKEN_DELIMIT;
80 	au_user_str_t	*au_user = (au_user_str_t *)ent;
81 
82 	if (lenstr >= buflen)
83 		return (NSS_STR_PARSE_ERANGE);
84 	(void) strncpy(buffer, instr, buflen);
85 	au_user->au_name = _strtok_escape(buffer, sep, &last);
86 	return (0);
87 }
88 
89 static int
90 str2userattr_s(
91 	const char	*instr,
92 	int		lenstr,
93 	void		*ent,
94 	char		*buffer,
95 	int		buflen)
96 {
97 	char		*last = NULL;
98 	char		*sep = KV_TOKEN_DELIMIT;
99 	userstr_t	*user = (userstr_t *)ent;
100 
101 	if (lenstr >= buflen)
102 		return (NSS_STR_PARSE_ERANGE);
103 	(void) strncpy(buffer, instr, buflen);
104 	user->name = _strtok_escape(buffer, sep, &last);
105 	return (0);
106 }
107 
108 /*
109  * Routines to manage list of "-" users for get{pw, sp, gr}ent().  Current
110  *   implementation is completely moronic; we use a linked list.  But then
111  *   that's what it's always done in 4.x...
112  */
113 
114 struct setofstrings {
115 	char			*name;
116 	struct setofstrings	*next;
117 	/*
118 	 * === Should get smart and malloc the string and pointer as one
119 	 *	object rather than two.
120 	 */
121 };
122 
123 static void
124 strset_free(ssp)
125 	strset_t	*ssp;
126 {
127 	strset_t	cur, nxt;
128 
129 	for (cur = *ssp;  cur != 0;  cur = nxt) {
130 		nxt = cur->next;
131 		free(cur->name);
132 		free(cur);
133 	}
134 	*ssp = 0;
135 }
136 
137 static boolean_t
138 strset_add(ssp, nam)
139 	strset_t	*ssp;
140 	const char	*nam;
141 {
142 	strset_t	new;
143 
144 	if (0 == (new = (strset_t)malloc(sizeof (*new)))) {
145 		return (B_FALSE);
146 	}
147 	if (0 == (new->name = malloc(strlen(nam) + 1))) {
148 		free(new);
149 		return (B_FALSE);
150 	}
151 	(void) strcpy(new->name, nam);
152 	new->next = *ssp;
153 	*ssp = new;
154 	return (B_TRUE);
155 }
156 
157 static boolean_t
158 strset_in(ssp, nam)
159 	const strset_t	*ssp;
160 	const char	*nam;
161 {
162 	strset_t	cur;
163 
164 	for (cur = *ssp;  cur != 0;  cur = cur->next) {
165 		if (strcmp(cur->name, nam) == 0) {
166 			return (B_TRUE);
167 		}
168 	}
169 	return (B_FALSE);
170 }
171 
172 /*
173  * Lookup and enumeration routines for +@group and -@group.
174  *
175  * This code knows a lot more about lib/libc/port/gen/getnetgrent.c than
176  *   is really healthy.  The set/get/end routines below duplicate code
177  *   from that file, but keep the state information per-backend-instance
178  *   instead of just per-process.
179  */
180 
181 extern void _nss_initf_netgroup(nss_db_params_t *);
182 /*
183  * Should really share the db_root in getnetgrent.c in order to get the
184  *   resource-management quotas right, but this will have to do.
185  */
186 static DEFINE_NSS_DB_ROOT(netgr_db_root);
187 
188 static boolean_t
189 netgr_in(compat_backend_ptr_t be, const char *group, const char *user)
190 {
191 	if (be->yp_domain == 0) {
192 		if (yp_get_default_domain((char **)&be->yp_domain) != 0) {
193 			return (B_FALSE);
194 		}
195 	}
196 	return (innetgr(group, 0, user, be->yp_domain));
197 }
198 
199 static void
200 netgr_set(be, netgroup)
201 	compat_backend_ptr_t	be;
202 	const char		*netgroup;
203 {
204 	/*
205 	 * ===> Need comment to explain that this first "if" is optimizing
206 	 *	for the same-netgroup-as-last-time case
207 	 */
208 	if (be->getnetgrent_backend != 0 &&
209 	    NSS_INVOKE_DBOP(be->getnetgrent_backend,
210 			    NSS_DBOP_SETENT,
211 			    (void *) netgroup) != NSS_SUCCESS) {
212 		NSS_INVOKE_DBOP(be->getnetgrent_backend, NSS_DBOP_DESTRUCTOR,
213 				0);
214 		be->getnetgrent_backend = 0;
215 	}
216 	if (be->getnetgrent_backend == 0) {
217 		struct nss_setnetgrent_args	args;
218 
219 		args.netgroup	= netgroup;
220 		args.iterator	= 0;
221 		(void) nss_search(&netgr_db_root, _nss_initf_netgroup,
222 			NSS_DBOP_NETGROUP_SET, &args);
223 		be->getnetgrent_backend = args.iterator;
224 	}
225 }
226 
227 static boolean_t
228 netgr_next_u(be, up)
229 	compat_backend_ptr_t	be;
230 	char			**up;
231 {
232 	if (be->netgr_buffer == 0 &&
233 	    (be->netgr_buffer = malloc(NSS_BUFLEN_NETGROUP)) == 0) {
234 		/* Out of memory */
235 		return (B_FALSE);
236 	}
237 
238 	do {
239 		struct nss_getnetgrent_args	args;
240 
241 		args.buffer	= be->netgr_buffer;
242 		args.buflen	= NSS_BUFLEN_NETGROUP;
243 		args.status	= NSS_NETGR_NO;
244 
245 		if (be->getnetgrent_backend != 0) {
246 			NSS_INVOKE_DBOP(be->getnetgrent_backend,
247 					NSS_DBOP_GETENT, &args);
248 		}
249 
250 		if (args.status == NSS_NETGR_FOUND) {
251 			*up	  = args.retp[NSS_NETGR_USER];
252 		} else {
253 			return (B_FALSE);
254 		}
255 	} while (*up == 0);
256 	return (B_TRUE);
257 }
258 
259 static void
260 netgr_end(be)
261 	compat_backend_ptr_t	be;
262 {
263 	if (be->getnetgrent_backend != 0) {
264 		NSS_INVOKE_DBOP(be->getnetgrent_backend,
265 				NSS_DBOP_DESTRUCTOR, 0);
266 		be->getnetgrent_backend = 0;
267 	}
268 	if (be->netgr_buffer != 0) {
269 		free(be->netgr_buffer);
270 		be->netgr_buffer = 0;
271 	}
272 }
273 
274 
275 #define	MAXFIELDS 9	/* Sufficient for passwd (7), shadow (9), group (4) */
276 
277 static nss_status_t
278 do_merge(be, args, instr, linelen)
279 	compat_backend_ptr_t	be;
280 	nss_XbyY_args_t		*args;
281 	const char		*instr;
282 	int			linelen;
283 {
284 	char			*fields[MAXFIELDS];
285 	int			i;
286 	int			overrides;
287 	const char		*p;
288 	const char		*end = instr + linelen;
289 	nss_status_t		res = NSS_NOTFOUND;
290 
291 	/*
292 	 * Potential optimization:  only perform the field-splitting nonsense
293 	 *   once per input line (at present, "+" and "+@netgroup" entries
294 	 *   will cause us to do this multiple times in getent() requests).
295 	 */
296 
297 	for (i = 0;  i < MAXFIELDS;  i++) {
298 		fields[i] = 0;
299 	}
300 	for (p = instr, overrides = 0, i = 0; /* no test */; i++) {
301 		const char	*q = memchr(p, ':', end - p);
302 		const char	*r = (q == 0) ? end : q;
303 		ssize_t		len = r - p;
304 
305 		if (len > 0) {
306 			char	*s = malloc(len + 1);
307 			if (s == 0) {
308 				overrides = -1;	/* Indicates "you lose" */
309 				break;
310 			}
311 			(void) memcpy(s, p, len);
312 			s[len] = '\0';
313 			fields[i] = s;
314 			overrides++;
315 		}
316 		if (q == 0) {
317 			/* End of line */
318 			break;
319 		} else {
320 			/* Skip the colon at (*q) */
321 			p = q + 1;
322 		}
323 	}
324 	if (overrides == 1) {
325 		/*
326 		 * return result here if /etc file format is requested
327 		 */
328 		if (be->return_string_data != 1) {
329 			/* No real overrides, return (*args) intact */
330 			res = NSS_SUCCESS;
331 		} else {
332 			free(fields[0]);
333 			fields[0] = NULL;
334 		}
335 	}
336 
337 	if (overrides > 1 || be->return_string_data == 1) {
338 		/*
339 		 * The zero'th field is always nonempty (+/-...), but at least
340 		 *   one other field was also nonempty, i.e. wants to override
341 		 */
342 		switch ((*be->mergef)(be, args, (const char **)fields)) {
343 		    case NSS_STR_PARSE_SUCCESS:
344 			if (be->return_string_data != 1)
345 				args->returnval	= args->buf.result;
346 			else
347 				args->returnval	= args->buf.buffer;
348 			args->erange	= 0;
349 			res = NSS_SUCCESS;
350 			break;
351 		    case NSS_STR_PARSE_ERANGE:
352 			args->returnval	= 0;
353 			args->erange	= 1;
354 			res = NSS_NOTFOUND;
355 			break;
356 		    case NSS_STR_PARSE_PARSE:
357 			args->returnval	= 0;
358 			args->erange	= 0;
359 /* ===> Very likely the wrong thing to do... */
360 			res = NSS_NOTFOUND;
361 			break;
362 		}
363 	} else if (res != NSS_SUCCESS) {
364 		args->returnval	= 0;
365 		args->erange	= 0;
366 		res = NSS_UNAVAIL;	/* ==> Right? */
367 	}
368 
369 	for (i = 0;  i < MAXFIELDS;  i++) {
370 		if (fields[i] != 0) {
371 			free(fields[i]);
372 		}
373 	}
374 
375 	return (res);
376 }
377 
378 /*ARGSUSED*/
379 nss_status_t
380 _nss_compat_setent(be, dummy)
381 	compat_backend_ptr_t	be;
382 	void			*dummy;
383 {
384 	if (be->f == 0) {
385 		if (be->filename == 0) {
386 			/* Backend isn't initialized properly? */
387 			return (NSS_UNAVAIL);
388 		}
389 		if ((be->f = fopen(be->filename, "rF")) == 0) {
390 			return (NSS_UNAVAIL);
391 		}
392 	} else {
393 		rewind(be->f);
394 	}
395 	strset_free(&be->minuses);
396 	/* ===> ??? nss_endent(be->db_rootp, be->db_initf, &be->db_context); */
397 
398 	if ((strcmp(be->filename, USERATTR_FILENAME) == 0) ||
399 	    (strcmp(be->filename, AUDITUSER_FILENAME) == 0))
400 		be->state = GETENT_ATTRDB;
401 	else
402 		be->state = GETENT_FILE;
403 
404 	be->return_string_data = 0;
405 
406 	/* ===> ??  netgroup stuff? */
407 	return (NSS_SUCCESS);
408 }
409 
410 /*ARGSUSED*/
411 nss_status_t
412 _nss_compat_endent(be, dummy)
413 	compat_backend_ptr_t	be;
414 	void			*dummy;
415 {
416 	if (be->f != 0) {
417 		(void) fclose(be->f);
418 		be->f = 0;
419 	}
420 	if (be->buf != 0) {
421 		free(be->buf);
422 		be->buf = 0;
423 	}
424 	nss_endent(be->db_rootp, be->db_initf, &be->db_context);
425 
426 	be->state = GETENT_FILE; /* Probably superfluous but comforting */
427 	strset_free(&be->minuses);
428 	netgr_end(be);
429 
430 	/*
431 	 * Question: from the point of view of resource-freeing vs. time to
432 	 *   start up again, how much should we do in endent() and how much
433 	 *   in the destructor?
434 	 */
435 	return (NSS_SUCCESS);
436 }
437 
438 /*ARGSUSED*/
439 nss_status_t
440 _nss_compat_destr(be, dummy)
441 	compat_backend_ptr_t	be;
442 	void			*dummy;
443 {
444 	if (be != 0) {
445 		if (be->f != 0) {
446 			(void) _nss_compat_endent(be, 0);
447 		}
448 		nss_delete(be->db_rootp);
449 		nss_delete(&netgr_db_root);
450 		free(be->workarea);
451 		free(be);
452 	}
453 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
454 }
455 
456 static int
457 read_line(f, buffer, buflen)
458 	FILE			*f;
459 	char			*buffer;
460 	int			buflen;
461 {
462 	/*CONSTCOND*/
463 	while (1) {
464 		int	linelen;
465 
466 		if (fgets(buffer, buflen, f) == 0) {
467 			/* End of file */
468 			return (-1);
469 		}
470 		linelen = strlen(buffer);
471 		/* linelen >= 1 (since fgets didn't return 0) */
472 
473 		if (buffer[linelen - 1] == '\n') {
474 			/*
475 			 * ===> The code below that calls read_line() doesn't
476 			 *	play by the rules;  it assumes in places that
477 			 *	the line is null-terminated.  For now we'll
478 			 *	humour it.
479 			 */
480 			buffer[--linelen] = '\0';
481 			return (linelen);
482 		}
483 		if (feof(f)) {
484 			/* Line is last line in file, and has no newline */
485 			return (linelen);
486 		}
487 		/* Line too long for buffer;  toss it and loop for next line */
488 		/* ===== should syslog() in cases where previous code did */
489 		while (fgets(buffer, buflen, f) != 0 &&
490 		    buffer[strlen(buffer) - 1] != '\n') {
491 			;
492 		}
493 	}
494 	/*NOTREACHED*/
495 }
496 
497 static int
498 is_nss_lookup_by_name(int attrdb, nss_dbop_t op)
499 {
500 	int result = 0;
501 
502 	if ((attrdb != 0) &&
503 	    ((op == NSS_DBOP_AUDITUSER_BYNAME) ||
504 	    (op == NSS_DBOP_USERATTR_BYNAME))) {
505 		result = 1;
506 	} else if ((attrdb == 0) &&
507 	    ((op == NSS_DBOP_GROUP_BYNAME) ||
508 	    (op == NSS_DBOP_PASSWD_BYNAME) ||
509 	    (op == NSS_DBOP_SHADOW_BYNAME))) {
510 		result = 1;
511 	}
512 
513 	return (result);
514 }
515 
516 /*ARGSUSED*/
517 nss_status_t
518 _attrdb_compat_XY_all(be, argp, netdb, check, op_num)
519     compat_backend_ptr_t be;
520     nss_XbyY_args_t *argp;
521     int netdb;
522     compat_XY_check_func check;
523     nss_dbop_t op_num;
524 {
525 	int		parsestat;
526 	int		(*func)();
527 	const char	*filter = argp->key.name;
528 	nss_status_t	res;
529 
530 #ifdef	DEBUG
531 	(void) fprintf(stdout, "\n[compat_common.c: _attrdb_compat_XY_all]\n");
532 #endif	/* DEBUG */
533 
534 	if (be->buf == 0 &&
535 	    (be->buf = malloc(be->minbuf)) == 0) {
536 		return (NSS_UNAVAIL);
537 	}
538 	if (check != NULL)
539 		if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS)
540 			return (res);
541 
542 	res = NSS_NOTFOUND;
543 
544 	/*
545 	 * assume a NULL buf.result pointer is an indication
546 	 * that the lookup result should be returned in /etc
547 	 * file format (if called from _nss_compat_getent(),
548 	 * be->return_string_data and argp->buf.result
549 	 * would be set already if argp->buf.result is NULL)
550 	 */
551 	if (check != NULL) {
552 		if (argp->buf.result == NULL) {
553 			be->return_string_data = 1;
554 
555 			/*
556 			 * the code executed later needs the result struct
557 			 * as working area
558 			 */
559 			argp->buf.result = be->workarea;
560 		} else
561 			be->return_string_data = 0;
562 	}
563 
564 	/*
565 	 * use an alternate str2ent function if necessary
566 	 */
567 	if (be->return_string_data == 1)
568 		func = be->str2ent_alt;
569 	else
570 		func = argp->str2ent;
571 
572 	/*CONSTCOND*/
573 	while (1) {
574 		int	linelen;
575 		char	*instr	= be->buf;
576 
577 		if ((linelen = read_line(be->f, instr, be->minbuf)) < 0) {
578 			/* End of file */
579 			argp->returnval = 0;
580 			argp->erange    = 0;
581 			break;
582 		}
583 		if (filter != 0 && strstr(instr, filter) == 0) {
584 			/*
585 			 * Optimization:  if the entry doesn't contain the
586 			 * filter string then it can't be the entry we want,
587 			 * so don't bother looking more closely at it.
588 			 */
589 			continue;
590 		}
591 		if (netdb) {
592 			char	*first;
593 			char	*last;
594 
595 			if ((last = strchr(instr, '#')) == 0) {
596 				last = instr + linelen;
597 			}
598 			*last-- = '\0';		/* Nuke '\n' or #comment */
599 
600 			/*
601 			 * Skip leading whitespace.  Normally there isn't
602 			 * any, so it's not worth calling strspn().
603 			 */
604 			for (first = instr;  isspace(*first);  first++) {
605 				;
606 			}
607 			if (*first == '\0') {
608 				continue;
609 			}
610 			/*
611 			 * Found something non-blank on the line.  Skip back
612 			 * over any trailing whitespace;  since we know
613 			 * there's non-whitespace earlier in the line,
614 			 * checking for termination is easy.
615 			 */
616 			while (isspace(*last)) {
617 				--last;
618 			}
619 			linelen = last - first + 1;
620 			if (first != instr) {
621 				instr = first;
622 			}
623 		}
624 		argp->returnval = 0;
625 		parsestat = (*func)(instr, linelen, argp->buf.result,
626 					argp->buf.buffer, argp->buf.buflen);
627 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
628 				argp->returnval = argp->buf.result;
629 			if (check == 0 || (*check)(argp)) {
630 				int	len;
631 
632 				if (be->return_string_data != 1) {
633 					res = NSS_SUCCESS;
634 					break;
635 				}
636 
637 				/* copy string data to result buffer */
638 				argp->buf.result = NULL;
639 				argp->returnval = argp->buf.buffer;
640 				if ((len = strlcpy(argp->buf.buffer, instr,
641 					argp->buf.buflen)) >=
642 					argp->buf.buflen) {
643 					argp->returnval = NULL;
644 					res = NSS_NOTFOUND;
645 					argp->erange = 1;
646 					break;
647 				}
648 
649 				argp->returnlen = len;
650 				res = NSS_SUCCESS;
651 				break;
652 			}
653 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
654 			res = NSS_NOTFOUND;
655 			argp->erange = 1;
656 			break;
657 		}
658 	}
659 	/*
660 	 * stayopen is set to 0 by default in order to close the opened
661 	 * file.  Some applications may break if it is set to 1.
662 	 */
663 	if (check != 0 && !argp->stayopen) {
664 		(void) _nss_compat_endent(be, 0);
665 	}
666 
667 	if (res != NSS_SUCCESS) {
668 		/*
669 		 * tell the nss_search() and nss_getent() below
670 		 * if the result should be returned in the /etc
671 		 * file format
672 		 */
673 		if (be->return_string_data == 1)
674 			argp->buf.result = NULL;
675 
676 		if ((op_num == NSS_DBOP_USERATTR_BYNAME) ||
677 		    (op_num == NSS_DBOP_AUDITUSER_BYNAME)) {
678 			res = nss_search(be->db_rootp,
679 			    be->db_initf,
680 			    op_num,
681 			    argp);
682 		} else {
683 			res = nss_getent(be->db_rootp,
684 			    be->db_initf, &be->db_context, argp);
685 		}
686 		if (res != NSS_SUCCESS) {
687 			argp->returnval	= 0;
688 			argp->erange	= 0;
689 		}
690 	}
691 
692 	return (res);
693 }
694 
695 nss_status_t
696 _nss_compat_XY_all(be, args, check, op_num)
697 	compat_backend_ptr_t	be;
698 	nss_XbyY_args_t		*args;
699 	compat_XY_check_func	check;
700 	nss_dbop_t		op_num;
701 {
702 	nss_status_t		res;
703 	int			parsestat;
704 
705 
706 	if (be->buf == 0 &&
707 	    (be->buf = malloc(be->minbuf)) == 0) {
708 		return (NSS_UNAVAIL); /* really panic, malloc failed */
709 	}
710 
711 	if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
712 		return (res);
713 	}
714 
715 	res = NSS_NOTFOUND;
716 
717 	/*
718 	 * assume a NULL buf.result pointer is an indication
719 	 * that the lookup result should be returned in /etc
720 	 * file format
721 	 */
722 	if (args->buf.result == NULL) {
723 
724 		be->return_string_data = 1;
725 
726 		/*
727 		 * the code executed later needs the result struct
728 		 * as working area
729 		 */
730 		args->buf.result = be->workarea;
731 
732 		be->str2ent_save = args->str2ent;
733 		args->str2ent = be->str2ent_alt;
734 	} else
735 		be->return_string_data = 0;
736 
737 	/*CONSTCOND*/
738 	while (1) {
739 		int		linelen;
740 		char		*instr	= be->buf;
741 		char		*colon;
742 
743 		linelen = read_line(be->f, instr, be->minbuf);
744 		if (linelen < 0) {
745 			/* End of file */
746 			args->returnval = 0;
747 			args->erange    = 0;
748 			break;
749 		}
750 
751 		args->returnval = 0;	/* reset for both types of entries */
752 
753 		if (instr[0] != '+' && instr[0] != '-') {
754 			/* Simple, wholesome, God-fearing entry */
755 			parsestat = (*args->str2ent)(instr, linelen,
756 						    args->buf.result,
757 						    args->buf.buffer,
758 						    args->buf.buflen);
759 			if (parsestat == NSS_STR_PARSE_SUCCESS) {
760 				args->returnval = args->buf.result;
761 				if ((*check)(args) != 0) {
762 					int len;
763 					if (be->return_string_data != 1) {
764 						res = NSS_SUCCESS;
765 						break;
766 					}
767 
768 					/*
769 					 * copy string data to
770 					 * result buffer
771 					 */
772 					args->buf.result = NULL;
773 					args->str2ent = be->str2ent_save;
774 					if ((len = strlcpy(args->buf.buffer,
775 						instr, args->buf.buflen)) >=
776 							args->buf.buflen)
777 						parsestat =
778 							NSS_STR_PARSE_ERANGE;
779 					else {
780 						args->returnval =
781 							args->buf.buffer;
782 						args->returnlen = len;
783 						res = NSS_SUCCESS;
784 						break;
785 					}
786 				} else
787 					continue;
788 			}
789 
790 /* ===> Check the Dani logic here... */
791 
792 			if (parsestat == NSS_STR_PARSE_ERANGE) {
793 				args->erange = 1;
794 				res = NSS_NOTFOUND;
795 				break;
796 				/* should we just skip this one long line ? */
797 			} /* else if (parsestat == NSS_STR_PARSE_PARSE) */
798 				/* don't care ! */
799 
800 /* ==> ?? */		continue;
801 		}
802 
803 
804 		/*
805 		 * Process "+", "+name", "+@netgroup", "-name" or "-@netgroup"
806 		 *
807 		 * This code is optimized for lookups by name.
808 		 *
809 		 * For lookups by identifier search key cannot be matched with
810 		 * the name of the "+" or "-" entry. So nss_search() is to be
811 		 * called before extracting the name i.e. via (*be->getnamef)().
812 		 *
813 		 * But for lookups by name, search key is compared with the name
814 		 * of the "+" or "-" entry to acquire a match and thus
815 		 * unnesessary calls to nss_search() is eliminated. Also for
816 		 * matching "-" entries, calls to nss_search() is eliminated.
817 		 */
818 
819 		if ((colon = strchr(instr, ':')) != 0) {
820 			*colon = '\0';	/* terminate field to extract name */
821 		}
822 
823 		if (instr[1] == '@') {
824 			/*
825 			 * Case 1:
826 			 * The entry is of the form "+@netgroup" or
827 			 * "-@netgroup".  If we're performing a lookup by name,
828 			 * we can simply extract the name from the search key
829 			 * (i.e. args->key.name).  If not, then we must call
830 			 * nss_search() before extracting the name via the
831 			 * get_XXname() function. i.e. (*be->getnamef)(args).
832 			 */
833 			if (is_nss_lookup_by_name(0, op_num) != 0) {
834 				/* compare then search */
835 				if (!be->permit_netgroups ||
836 				    !netgr_in(be, instr + 2, args->key.name))
837 					continue;
838 				if (instr[0] == '+') {
839 					/* need to search for "+" entry */
840 					(void) nss_search(be->db_rootp,
841 						be->db_initf, op_num, args);
842 					if (args->returnval == 0)
843 						continue;
844 				}
845 			} else {
846 				/* search then compare */
847 				(void) nss_search(be->db_rootp,
848 					be->db_initf, op_num, args);
849 				if (args->returnval == 0)
850 					continue;
851 				if (!be->permit_netgroups ||
852 				    !netgr_in(be, instr + 2,
853 				    (*be->getnamef)(args)))
854 					continue;
855 			}
856 		} else if (instr[1] == '\0') {
857 			/*
858 			 * Case 2:
859 			 * The entry is of the form "+" or "-".  The former
860 			 * allows all entries from name services.  The latter
861 			 * is illegal and ought to be ignored.
862 			 */
863 			if (instr[0] == '-')
864 				continue;
865 			/* need to search for "+" entry */
866 			(void) nss_search(be->db_rootp, be->db_initf,
867 				op_num, args);
868 			if (args->returnval == 0)
869 				continue;
870 		} else {
871 			/*
872 			 * Case 3:
873 			 * The entry is of the form "+name" or "-name".
874 			 * If we're performing a lookup by name, we can simply
875 			 * extract the name from the search key
876 			 * (i.e. args->key.name).  If not, then we must call
877 			 * nss_search() before extracting the name via the
878 			 * get_XXname() function. i.e. (*be->getnamef)(args).
879 			 */
880 			if (is_nss_lookup_by_name(0, op_num) != 0) {
881 				/* compare then search */
882 				if (strcmp(instr + 1, args->key.name) != 0)
883 					continue;
884 				if (instr[0] == '+') {
885 					/* need to search for "+" entry */
886 					(void) nss_search(be->db_rootp,
887 						be->db_initf, op_num, args);
888 					if (args->returnval == 0)
889 						continue;
890 				}
891 			} else {
892 				/* search then compare */
893 				(void) nss_search(be->db_rootp,
894 					be->db_initf, op_num, args);
895 				if (args->returnval == 0)
896 					continue;
897 				if (strcmp(instr + 1, (*be->getnamef)(args))
898 				    != 0)
899 					continue;
900 			}
901 		}
902 		if (instr[0] == '-') {
903 			/* no need to search for "-" entry */
904 			args->returnval = 0;
905 			args->erange = 0;
906 			res = NSS_NOTFOUND;
907 		} else {
908 			if (colon != 0)
909 				*colon = ':';	/* restoration */
910 			res = do_merge(be, args, instr, linelen);
911 		}
912 		break;
913 	}
914 
915 	/*
916 	 * stayopen is set to 0 by default in order to close the opened
917 	 * file.  Some applications may break if it is set to 1.
918 	 */
919 	if (!args->stayopen) {
920 		(void) _nss_compat_endent(be, 0);
921 	}
922 
923 	if (be->return_string_data == 1) {
924 		args->str2ent = be->str2ent_save;
925 	}
926 
927 	return (res);
928 }
929 
930 nss_status_t
931 _nss_compat_getent(be, a)
932 	compat_backend_ptr_t	be;
933 	void			*a;
934 {
935 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
936 	nss_status_t		res;
937 	char			*colon = 0; /* <=== need comment re lifetime */
938 
939 	if (be->f == 0) {
940 		if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
941 			return (res);
942 		}
943 	}
944 
945 	if (be->buf == 0 &&
946 	    (be->buf = malloc(be->minbuf)) == 0) {
947 		return (NSS_UNAVAIL); /* really panic, malloc failed */
948 	}
949 
950 	/*
951 	 * assume a NULL buf.result pointer is an indication
952 	 * that the lookup result should be returned in /etc
953 	 * file format
954 	 */
955 	if (args->buf.result == NULL) {
956 		be->return_string_data = 1;
957 
958 		/*
959 		 * the code executed later needs the result struct
960 		 * as working area
961 		 */
962 		args->buf.result = be->workarea;
963 	} else
964 		be->return_string_data = 0;
965 
966 	/*CONSTCOND*/
967 	while (1) {
968 		char		*instr	= be->buf;
969 		int		linelen;
970 		char		*name;	/* === Need more distinctive label */
971 		const char	*savename;
972 
973 		/*
974 		 * In the code below...
975 		 *    break	means "I found one, I think" (i.e. goto the
976 		 *		code after the end of the switch statement),
977 		 *    continue	means "Next candidate"
978 		 *		(i.e. loop around to the switch statement),
979 		 *    return	means "I'm quite sure" (either Yes or No).
980 		 */
981 		switch (be->state) {
982 
983 		    case GETENT_DONE:
984 			args->returnval	= 0;
985 			args->erange	= 0;
986 			return (NSS_NOTFOUND);
987 
988 		    case GETENT_ATTRDB:
989 			args->key.name = NULL;
990 			res = _attrdb_compat_XY_all(be,
991 			    args, 1, (compat_XY_check_func)NULL, 0);
992 			return (res);
993 
994 		    case GETENT_FILE:
995 			linelen = read_line(be->f, instr, be->minbuf);
996 			if (linelen < 0) {
997 				/* End of file */
998 				be->state = GETENT_DONE;
999 				continue;
1000 			}
1001 			if ((colon = strchr(instr, ':')) != 0) {
1002 				*colon = '\0';
1003 			}
1004 			if (instr[0] == '-') {
1005 				if (instr[1] != '@') {
1006 					(void) strset_add(&be->minuses,
1007 						instr + 1);
1008 				} else if (be->permit_netgroups) {
1009 					netgr_set(be, instr + 2);
1010 					while (netgr_next_u(be, &name)) {
1011 						(void) strset_add(&be->minuses,
1012 							name);
1013 					}
1014 					netgr_end(be);
1015 				} /* Else (silently) ignore the entry */
1016 				continue;
1017 			} else if (instr[0] != '+') {
1018 				int	parsestat;
1019 				/*
1020 				 * Normal entry, no +/- nonsense
1021 				 */
1022 				if (colon != 0) {
1023 					*colon = ':';
1024 				}
1025 				args->returnval = 0;
1026 				parsestat = (*args->str2ent)(instr, linelen,
1027 							args->buf.result,
1028 							args->buf.buffer,
1029 							args->buf.buflen);
1030 				if (parsestat == NSS_STR_PARSE_SUCCESS) {
1031 					int	len;
1032 
1033 					if (be->return_string_data != 1) {
1034 						args->returnval =
1035 							args->buf.result;
1036 						return (NSS_SUCCESS);
1037 					}
1038 
1039 					/*
1040 					 * copy string data to
1041 					 * result buffer
1042 					 */
1043 					args->buf.result = NULL;
1044 					args->returnval =
1045 						args->buf.buffer;
1046 					if ((len = strlcpy(args->buf.buffer,
1047 						instr, args->buf.buflen)) >=
1048 						args->buf.buflen)
1049 						parsestat =
1050 							NSS_STR_PARSE_ERANGE;
1051 					else {
1052 						args->returnlen = len;
1053 						return (NSS_SUCCESS);
1054 					}
1055 				}
1056 				/* ==> ?? Treat ERANGE differently ?? */
1057 				if (parsestat == NSS_STR_PARSE_ERANGE) {
1058 					args->returnval = 0;
1059 					args->erange = 1;
1060 					return (NSS_NOTFOUND);
1061 				}
1062 				/* Skip the offending entry, get next */
1063 				continue;
1064 			} else if (instr[1] == '\0') {
1065 				/* Plain "+" */
1066 				nss_setent(be->db_rootp, be->db_initf,
1067 					&be->db_context);
1068 				be->state = GETENT_ALL;
1069 				be->linelen = linelen;
1070 
1071 				continue;
1072 			} else if (instr[1] == '@') {
1073 				/* "+@netgroup" */
1074 				netgr_set(be, instr + 2);
1075 				be->state = GETENT_NETGROUP;
1076 				be->linelen = linelen;
1077 				continue;
1078 			} else {
1079 				/* "+name" */
1080 				name = instr + 1;
1081 				break;
1082 			}
1083 			/* NOTREACHED */
1084 
1085 		    case GETENT_ALL:
1086 			linelen = be->linelen;
1087 			args->returnval = 0;
1088 			if (be->return_string_data == 1) {
1089 				be->str2ent_save = args->str2ent;
1090 				args->str2ent = be->str2ent_alt;
1091 			}
1092 
1093 			(void) nss_getent(be->db_rootp, be->db_initf,
1094 				&be->db_context, args);
1095 			if (args->returnval == 0) {
1096 				/* ==> ?? Treat ERANGE differently ?? */
1097 				nss_endent(be->db_rootp, be->db_initf,
1098 					&be->db_context);
1099 				be->state = GETENT_FILE;
1100 				if (be->return_string_data == 1)
1101 					args->str2ent = be->str2ent_save;
1102 				continue;
1103 			}
1104 			if (strset_in(&be->minuses, (*be->getnamef)(args)))
1105 				continue;
1106 			name = 0; /* tell code below we've done the lookup */
1107 			if (be->return_string_data == 1)
1108 				args->str2ent = be->str2ent_save;
1109 			break;
1110 
1111 		    case GETENT_NETGROUP:
1112 			linelen = be->linelen;
1113 			if (!netgr_next_u(be, &name)) {
1114 				netgr_end(be);
1115 				be->state = GETENT_FILE;
1116 				continue;
1117 			}
1118 			/* pass "name" variable to code below... */
1119 			break;
1120 		}
1121 
1122 		if (name != 0) {
1123 			if (strset_in(&be->minuses, name)) {
1124 				continue;
1125 			}
1126 			/*
1127 			 * Do a getXXXnam(name).  If we were being pure,
1128 			 *   we'd introduce yet another function-pointer
1129 			 *   that the database-specific code had to supply
1130 			 *   to us.  Instead we'll be grotty and hard-code
1131 			 *   the knowledge that
1132 			 *	(a) The username is always passwd in key.name,
1133 			 *	(b) NSS_DBOP_PASSWD_BYNAME ==
1134 			 *		NSS_DBOP_SHADOW_BYNAME ==
1135 			 *		NSS_DBOP_next_iter.
1136 			 */
1137 			savename = args->key.name;
1138 			args->key.name	= name;
1139 			args->returnval	= 0;
1140 			if (be->return_string_data == 1) {
1141 				be->str2ent_save = args->str2ent;
1142 				args->str2ent = be->str2ent_alt;
1143 			}
1144 
1145 			(void) nss_search(be->db_rootp, be->db_initf,
1146 				NSS_DBOP_next_iter, args);
1147 
1148 			if (be->return_string_data == 1)
1149 				args->str2ent = be->str2ent_save;
1150 			args->key.name = savename;  /* In case anyone cares */
1151 		}
1152 		/*
1153 		 * Found one via "+", "+name" or "@netgroup".
1154 		 * Override some fields if the /etc file says to do so.
1155 		 */
1156 		if (args->returnval == 0) {
1157 			/* ==> ?? Should treat erange differently? */
1158 			continue;
1159 		}
1160 		/* 'colon' was set umpteen iterations ago in GETENT_FILE */
1161 		if (colon != 0) {
1162 			*colon = ':';
1163 			colon = 0;
1164 		}
1165 		return (do_merge(be, args, instr, linelen));
1166 	}
1167 	/*NOTREACHED*/
1168 }
1169 
1170 /* We don't use this directly;  we just copy the bits when we want to	 */
1171 /* initialize the variable (in the compat_backend struct) that we do use */
1172 static DEFINE_NSS_GETENT(context_initval);
1173 
1174 nss_backend_t *
1175 _nss_compat_constr(ops, n_ops, filename, min_bufsize, rootp, initf, netgroups,
1176 		getname_func, merge_func)
1177 	compat_backend_op_t	ops[];
1178 	int			n_ops;
1179 	const char		*filename;
1180 	int			min_bufsize;
1181 	nss_db_root_t		*rootp;
1182 	nss_db_initf_t		initf;
1183 	int			netgroups;
1184 	compat_get_name		getname_func;
1185 	compat_merge_func	merge_func;
1186 {
1187 	compat_backend_ptr_t	be;
1188 
1189 	if ((be = (compat_backend_ptr_t)malloc(sizeof (*be))) == 0) {
1190 		return (0);
1191 	}
1192 	be->ops		= ops;
1193 	be->n_ops	= n_ops;
1194 	be->filename	= filename;
1195 	be->f		= 0;
1196 	be->minbuf	= min_bufsize;
1197 	be->buf		= 0;
1198 
1199 	be->db_rootp	= rootp;
1200 	be->db_initf	= initf;
1201 	be->db_context	= context_initval;
1202 
1203 	be->getnamef	= getname_func;
1204 	be->mergef	= merge_func;
1205 
1206 	be->state = GETENT_FILE;    /* i.e. do Automatic setent(); */
1207 	if (strcmp(be->filename, USERATTR_FILENAME) == 0) {
1208 		be->state = GETENT_ATTRDB;
1209 		be->str2ent_alt = str2userattr_s;
1210 		be->workarea = calloc(1, sizeof (userstr_t));
1211 	} else if (strcmp(be->filename, AUDITUSER_FILENAME) == 0) {
1212 		be->state = GETENT_ATTRDB;
1213 		be->str2ent_alt = str2auuser_s;
1214 		be->workarea = calloc(1, sizeof (au_user_str_t));
1215 	} else if (strcmp(be->filename, PASSWD) == 0) {
1216 		be->str2ent_alt = str2passwd;
1217 		be->workarea = calloc(1, sizeof (struct passwd));
1218 	} else if (strcmp(be->filename, SHADOW) == 0) {
1219 		be->str2ent_alt = str2spwd;
1220 		be->workarea = calloc(1, sizeof (struct spwd));
1221 	} else { /* group */
1222 		be->str2ent_alt = str2group;
1223 		be->workarea = calloc(1, sizeof (struct group));
1224 	}
1225 	if (be->workarea == NULL)
1226 		return (NULL);
1227 
1228 	be->minuses	= 0;
1229 
1230 	be->permit_netgroups = netgroups;
1231 	be->yp_domain	= 0;
1232 	be->getnetgrent_backend	= 0;
1233 	be->netgr_buffer = 0;
1234 	be->return_string_data = 0;
1235 
1236 	return ((nss_backend_t *)be);
1237 }
1238