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