compat_common.c revision 2830:5228d1267a01
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Common code and structures used by name-service-switch "compat" backends.
26 *
27 * Most of the code in the "compat" backend is a perverted form of code from
28 * the "files" backend;  this file is no exception.
29 */
30
31#pragma ident	"%Z%%M%	%I%	%E% SMI"
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <ctype.h>
37#include <bsm/libbsm.h>
38#include <user_attr.h>
39#include <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
50extern int yp_get_default_domain(char **domain);
51
52/* from libc */
53extern int str2passwd(const char *instr, int lenstr, void *ent,
54		char *buffer, int buflen);
55extern int str2spwd(const char *instr, int lenstr, void *ent,
56		char *buffer, int buflen);
57extern int str2group(const char *instr, int lenstr, void *ent,
58		char *buffer, int buflen);
59
60/* from libnsl */
61extern 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 */
70static int
71str2auuser_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
89static int
90str2userattr_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
114struct 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
123static void
124strset_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
137static boolean_t
138strset_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
157static boolean_t
158strset_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
181extern 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 */
186static DEFINE_NSS_DB_ROOT(netgr_db_root);
187
188static boolean_t
189netgr_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
199static void
200netgr_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
227static boolean_t
228netgr_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
259static void
260netgr_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
277static nss_status_t
278do_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*/
379nss_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	/* ===> ??  netgroup stuff? */
405	return (NSS_SUCCESS);
406}
407
408/*ARGSUSED*/
409nss_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*/
437nss_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);
449	}
450	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
451}
452
453static int
454read_line(f, buffer, buflen)
455	FILE			*f;
456	char			*buffer;
457	int			buflen;
458{
459	/*CONSTCOND*/
460	while (1) {
461		int	linelen;
462
463		if (fgets(buffer, buflen, f) == 0) {
464			/* End of file */
465			return (-1);
466		}
467		linelen = strlen(buffer);
468		/* linelen >= 1 (since fgets didn't return 0) */
469
470		if (buffer[linelen - 1] == '\n') {
471			/*
472			 * ===> The code below that calls read_line() doesn't
473			 *	play by the rules;  it assumes in places that
474			 *	the line is null-terminated.  For now we'll
475			 *	humour it.
476			 */
477			buffer[--linelen] = '\0';
478			return (linelen);
479		}
480		if (feof(f)) {
481			/* Line is last line in file, and has no newline */
482			return (linelen);
483		}
484		/* Line too long for buffer;  toss it and loop for next line */
485		/* ===== should syslog() in cases where previous code did */
486		while (fgets(buffer, buflen, f) != 0 &&
487		    buffer[strlen(buffer) - 1] != '\n') {
488			;
489		}
490	}
491	/*NOTREACHED*/
492}
493
494static int
495is_nss_lookup_by_name(int attrdb, nss_dbop_t op)
496{
497	int result = 0;
498
499	if ((attrdb != 0) &&
500	    ((op == NSS_DBOP_AUDITUSER_BYNAME) ||
501	    (op == NSS_DBOP_USERATTR_BYNAME))) {
502		result = 1;
503	} else if ((attrdb == 0) &&
504	    ((op == NSS_DBOP_GROUP_BYNAME) ||
505	    (op == NSS_DBOP_PASSWD_BYNAME) ||
506	    (op == NSS_DBOP_SHADOW_BYNAME))) {
507		result = 1;
508	}
509
510	return (result);
511}
512
513/*ARGSUSED*/
514nss_status_t
515_attrdb_compat_XY_all(be, argp, netdb, check, op_num)
516    compat_backend_ptr_t be;
517    nss_XbyY_args_t *argp;
518    int netdb;
519    compat_XY_check_func check;
520    nss_dbop_t op_num;
521{
522	int		parsestat;
523	int		(*func)();
524	const char	*filter = argp->key.name;
525	nss_status_t	res;
526	union {
527		au_user_str_t	au;
528		userstr_t	user;
529	} workarea;
530
531#ifdef	DEBUG
532	(void) fprintf(stdout, "\n[compat_common.c: _attrdb_compat_XY_all]\n");
533#endif	/* DEBUG */
534
535	if (be->buf == 0 &&
536	    (be->buf = malloc(be->minbuf)) == 0) {
537		return (NSS_UNAVAIL);
538	}
539	if (check != NULL)
540		if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS)
541			return (res);
542
543	res = NSS_NOTFOUND;
544
545	/*
546	 * assume a NULL buf.result pointer is an indication
547	 * that the lookup result should be returned in /etc
548	 * file format
549	 */
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 = &workarea;
558
559		if (strcmp(be->filename, USERATTR_FILENAME) == 0)
560			func = str2userattr_s;
561		else
562			func = str2auuser_s;
563	} else
564			func = argp->str2ent;
565
566	/*CONSTCOND*/
567	while (1) {
568		int	linelen;
569		char	*instr	= be->buf;
570
571		if ((linelen = read_line(be->f, instr, be->minbuf)) < 0) {
572			/* End of file */
573			argp->returnval = 0;
574			argp->erange    = 0;
575			break;
576		}
577		if (filter != 0 && strstr(instr, filter) == 0) {
578			/*
579			 * Optimization:  if the entry doesn't contain the
580			 * filter string then it can't be the entry we want,
581			 * so don't bother looking more closely at it.
582			 */
583			continue;
584		}
585		if (netdb) {
586			char	*first;
587			char	*last;
588
589			if ((last = strchr(instr, '#')) == 0) {
590				last = instr + linelen;
591			}
592			*last-- = '\0';		/* Nuke '\n' or #comment */
593
594			/*
595			 * Skip leading whitespace.  Normally there isn't
596			 * any, so it's not worth calling strspn().
597			 */
598			for (first = instr;  isspace(*first);  first++) {
599				;
600			}
601			if (*first == '\0') {
602				continue;
603			}
604			/*
605			 * Found something non-blank on the line.  Skip back
606			 * over any trailing whitespace;  since we know
607			 * there's non-whitespace earlier in the line,
608			 * checking for termination is easy.
609			 */
610			while (isspace(*last)) {
611				--last;
612			}
613			linelen = last - first + 1;
614			if (first != instr) {
615				instr = first;
616			}
617		}
618		argp->returnval = 0;
619		parsestat = (*func)(instr, linelen, argp->buf.result,
620					argp->buf.buffer, argp->buf.buflen);
621		if (parsestat == NSS_STR_PARSE_SUCCESS) {
622				argp->returnval = argp->buf.result;
623			if (check == 0 || (*check)(argp)) {
624				int	len;
625
626				if (be->return_string_data != 1) {
627					res = NSS_SUCCESS;
628					break;
629				}
630
631				/* copy string data to result buffer */
632				argp->buf.result = NULL;
633				argp->returnval = argp->buf.buffer;
634				if ((len = strlcpy(argp->buf.buffer, instr,
635					argp->buf.buflen)) >=
636					argp->buf.buflen) {
637					argp->returnval = NULL;
638					res = NSS_NOTFOUND;
639					argp->erange = 1;
640					break;
641				}
642
643				argp->returnlen = len;
644				res = NSS_SUCCESS;
645				break;
646			}
647		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
648			res = NSS_NOTFOUND;
649			argp->erange = 1;
650			break;
651		}
652	}
653	/*
654	 * stayopen is set to 0 by default in order to close the opened
655	 * file.  Some applications may break if it is set to 1.
656	 */
657	if (check != 0 && !argp->stayopen) {
658		(void) _nss_compat_endent(be, 0);
659	}
660
661	if (res != NSS_SUCCESS) {
662		/*
663		 * tell the nss_search() and nss_getent() below
664		 * if the result should be returned in the /etc
665		 * file format
666		 */
667		if (be->return_string_data == 1)
668			argp->buf.result = NULL;
669
670		if ((op_num == NSS_DBOP_USERATTR_BYNAME) ||
671		    (op_num == NSS_DBOP_AUDITUSER_BYNAME)) {
672			res = nss_search(be->db_rootp,
673			    be->db_initf,
674			    op_num,
675			    argp);
676		} else {
677			res = nss_getent(be->db_rootp,
678			    be->db_initf, &be->db_context, argp);
679		}
680		if (res != NSS_SUCCESS) {
681			argp->returnval	= 0;
682			argp->erange	= 0;
683		}
684	}
685
686	return (res);
687}
688
689nss_status_t
690_nss_compat_XY_all(be, args, check, op_num)
691	compat_backend_ptr_t	be;
692	nss_XbyY_args_t		*args;
693	compat_XY_check_func	check;
694	nss_dbop_t		op_num;
695{
696	nss_status_t		res;
697	int			parsestat;
698	union {
699		struct passwd		pwd;
700		struct spwd		shdw;
701		struct group		grp;
702	} workarea;
703	int			(*str2ent_save)();
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 = &workarea;
731
732		str2ent_save = args->str2ent;
733		if (strcmp(be->filename, PASSWD) == 0)
734			args->str2ent = str2passwd;
735		else if (strcmp(be->filename, SHADOW) == 0)
736			args->str2ent = str2spwd;
737		else
738			args->str2ent = str2group;
739	}
740
741	/*CONSTCOND*/
742	while (1) {
743		int		linelen;
744		char		*instr	= be->buf;
745		char		*colon;
746
747		linelen = read_line(be->f, instr, be->minbuf);
748		if (linelen < 0) {
749			/* End of file */
750			args->returnval = 0;
751			args->erange    = 0;
752			break;
753		}
754
755		args->returnval = 0;	/* reset for both types of entries */
756
757		if (instr[0] != '+' && instr[0] != '-') {
758			/* Simple, wholesome, God-fearing entry */
759			parsestat = (*args->str2ent)(instr, linelen,
760						    args->buf.result,
761						    args->buf.buffer,
762						    args->buf.buflen);
763			if (parsestat == NSS_STR_PARSE_SUCCESS) {
764				args->returnval = args->buf.result;
765				if ((*check)(args) != 0) {
766					int len;
767					if (be->return_string_data != 1) {
768						res = NSS_SUCCESS;
769						break;
770					}
771
772					/*
773					 * copy string data to
774					 * result buffer
775					 */
776					args->buf.result = NULL;
777					args->str2ent = str2ent_save;
778					if ((len = strlcpy(args->buf.buffer,
779						instr, args->buf.buflen)) >=
780							args->buf.buflen)
781						parsestat =
782							NSS_STR_PARSE_ERANGE;
783					else {
784						args->returnval =
785							args->buf.buffer;
786						args->returnlen = len;
787						res = NSS_SUCCESS;
788						break;
789					}
790				} else
791					continue;
792			}
793
794/* ===> Check the Dani logic here... */
795
796			if (parsestat == NSS_STR_PARSE_ERANGE) {
797				args->erange = 1;
798				res = NSS_NOTFOUND;
799				break;
800				/* should we just skip this one long line ? */
801			} /* else if (parsestat == NSS_STR_PARSE_PARSE) */
802				/* don't care ! */
803
804/* ==> ?? */		continue;
805		}
806
807
808		/*
809		 * Process "+", "+name", "+@netgroup", "-name" or "-@netgroup"
810		 *
811		 * This code is optimized for lookups by name.
812		 *
813		 * For lookups by identifier search key cannot be matched with
814		 * the name of the "+" or "-" entry. So nss_search() is to be
815		 * called before extracting the name i.e. via (*be->getnamef)().
816		 *
817		 * But for lookups by name, search key is compared with the name
818		 * of the "+" or "-" entry to acquire a match and thus
819		 * unnesessary calls to nss_search() is eliminated. Also for
820		 * matching "-" entries, calls to nss_search() is eliminated.
821		 */
822
823		if ((colon = strchr(instr, ':')) != 0) {
824			*colon = '\0';	/* terminate field to extract name */
825		}
826
827		if (instr[1] == '@') {
828			/*
829			 * Case 1:
830			 * The entry is of the form "+@netgroup" or
831			 * "-@netgroup".  If we're performing a lookup by name,
832			 * we can simply extract the name from the search key
833			 * (i.e. args->key.name).  If not, then we must call
834			 * nss_search() before extracting the name via the
835			 * get_XXname() function. i.e. (*be->getnamef)(args).
836			 */
837			if (is_nss_lookup_by_name(0, op_num) != 0) {
838				/* compare then search */
839				if (!be->permit_netgroups ||
840				    !netgr_in(be, instr + 2, args->key.name))
841					continue;
842				if (instr[0] == '+') {
843					/* need to search for "+" entry */
844					(void) nss_search(be->db_rootp,
845						be->db_initf, op_num, args);
846					if (args->returnval == 0)
847						continue;
848				}
849			} else {
850				/* search then compare */
851				(void) nss_search(be->db_rootp,
852					be->db_initf, op_num, args);
853				if (args->returnval == 0)
854					continue;
855				if (!be->permit_netgroups ||
856				    !netgr_in(be, instr + 2,
857				    (*be->getnamef)(args)))
858					continue;
859			}
860		} else if (instr[1] == '\0') {
861			/*
862			 * Case 2:
863			 * The entry is of the form "+" or "-".  The former
864			 * allows all entries from name services.  The latter
865			 * is illegal and ought to be ignored.
866			 */
867			if (instr[0] == '-')
868				continue;
869			/* need to search for "+" entry */
870			(void) nss_search(be->db_rootp, be->db_initf,
871				op_num, args);
872			if (args->returnval == 0)
873				continue;
874		} else {
875			/*
876			 * Case 3:
877			 * The entry is of the form "+name" or "-name".
878			 * If we're performing a lookup by name, we can simply
879			 * extract the name from the search key
880			 * (i.e. args->key.name).  If not, then we must call
881			 * nss_search() before extracting the name via the
882			 * get_XXname() function. i.e. (*be->getnamef)(args).
883			 */
884			if (is_nss_lookup_by_name(0, op_num) != 0) {
885				/* compare then search */
886				if (strcmp(instr + 1, args->key.name) != 0)
887					continue;
888				if (instr[0] == '+') {
889					/* need to search for "+" entry */
890					(void) nss_search(be->db_rootp,
891						be->db_initf, op_num, args);
892					if (args->returnval == 0)
893						continue;
894				}
895			} else {
896				/* search then compare */
897				(void) nss_search(be->db_rootp,
898					be->db_initf, op_num, args);
899				if (args->returnval == 0)
900					continue;
901				if (strcmp(instr + 1, (*be->getnamef)(args))
902				    != 0)
903					continue;
904			}
905		}
906		if (instr[0] == '-') {
907			/* no need to search for "-" entry */
908			args->returnval = 0;
909			args->erange = 0;
910			res = NSS_NOTFOUND;
911		} else {
912			if (colon != 0)
913				*colon = ':';	/* restoration */
914			res = do_merge(be, args, instr, linelen);
915		}
916		break;
917	}
918
919	/*
920	 * stayopen is set to 0 by default in order to close the opened
921	 * file.  Some applications may break if it is set to 1.
922	 */
923	if (!args->stayopen) {
924		(void) _nss_compat_endent(be, 0);
925	}
926
927	if (be->return_string_data == 1) {
928		args->str2ent = str2ent_save;
929	}
930
931	return (res);
932}
933
934nss_status_t
935_nss_compat_getent(be, a)
936	compat_backend_ptr_t	be;
937	void			*a;
938{
939	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
940	nss_status_t		res;
941	char			*colon = 0; /* <=== need comment re lifetime */
942	union {
943		struct passwd		pwd;
944		struct spwd		shdw;
945		struct group		grp;
946	} workarea;
947
948
949	if (be->f == 0) {
950		if ((res = _nss_compat_setent(be, 0)) != NSS_SUCCESS) {
951			return (res);
952		}
953	}
954
955	if (be->buf == 0 &&
956	    (be->buf = malloc(be->minbuf)) == 0) {
957		return (NSS_UNAVAIL); /* really panic, malloc failed */
958	}
959
960	/*
961	 * assume a NULL buf.result pointer is an indication
962	 * that the lookup result should be returned in /etc
963	 * file format
964	 */
965	if (args->buf.result == NULL) {
966		be->return_string_data = 1;
967
968		/*
969		 * the code executed later needs the result struct
970		 * as working area
971		 */
972		args->buf.result = &workarea;
973	}
974
975	/*CONSTCOND*/
976	while (1) {
977		char		*instr	= be->buf;
978		int		linelen;
979		char		*name;	/* === Need more distinctive label */
980		const char	*savename;
981
982		/*
983		 * In the code below...
984		 *    break	means "I found one, I think" (i.e. goto the
985		 *		code after the end of the switch statement),
986		 *    continue	means "Next candidate"
987		 *		(i.e. loop around to the switch statement),
988		 *    return	means "I'm quite sure" (either Yes or No).
989		 */
990		switch (be->state) {
991
992		    case GETENT_DONE:
993			args->returnval	= 0;
994			args->erange	= 0;
995			return (NSS_NOTFOUND);
996
997		    case GETENT_ATTRDB:
998			args->key.name = NULL;
999			res = _attrdb_compat_XY_all(be,
1000			    args, 1, (compat_XY_check_func)NULL, 0);
1001			return (res);
1002
1003		    case GETENT_FILE:
1004			linelen = read_line(be->f, instr, be->minbuf);
1005			if (linelen < 0) {
1006				/* End of file */
1007				be->state = GETENT_DONE;
1008				continue;
1009			}
1010			if ((colon = strchr(instr, ':')) != 0) {
1011				*colon = '\0';
1012			}
1013			if (instr[0] == '-') {
1014				if (instr[1] != '@') {
1015					(void) strset_add(&be->minuses,
1016						instr + 1);
1017				} else if (be->permit_netgroups) {
1018					netgr_set(be, instr + 2);
1019					while (netgr_next_u(be, &name)) {
1020						(void) strset_add(&be->minuses,
1021							name);
1022					}
1023					netgr_end(be);
1024				} /* Else (silently) ignore the entry */
1025				continue;
1026			} else if (instr[0] != '+') {
1027				int	parsestat;
1028				/*
1029				 * Normal entry, no +/- nonsense
1030				 */
1031				if (colon != 0) {
1032					*colon = ':';
1033				}
1034				args->returnval = 0;
1035				parsestat = (*args->str2ent)(instr, linelen,
1036							args->buf.result,
1037							args->buf.buffer,
1038							args->buf.buflen);
1039				if (parsestat == NSS_STR_PARSE_SUCCESS) {
1040					int	len;
1041
1042					if (be->return_string_data != 1) {
1043						args->returnval =
1044							args->buf.result;
1045						return (NSS_SUCCESS);
1046					}
1047
1048					/*
1049					 * copy string data to
1050					 * result buffer
1051					 */
1052					args->buf.result = NULL;
1053					args->returnval =
1054						args->buf.buffer;
1055					if ((len = strlcpy(args->buf.buffer,
1056						instr, args->buf.buflen)) >=
1057						args->buf.buflen)
1058						parsestat =
1059							NSS_STR_PARSE_ERANGE;
1060					else {
1061						args->returnlen = len;
1062						return (NSS_SUCCESS);
1063					}
1064				}
1065				/* ==> ?? Treat ERANGE differently ?? */
1066				if (parsestat == NSS_STR_PARSE_ERANGE) {
1067					args->returnval = 0;
1068					args->erange = 1;
1069					return (NSS_NOTFOUND);
1070				}
1071				/* Skip the offending entry, get next */
1072				continue;
1073			} else if (instr[1] == '\0') {
1074				/* Plain "+" */
1075				nss_setent(be->db_rootp, be->db_initf,
1076					&be->db_context);
1077				be->state = GETENT_ALL;
1078				be->linelen = linelen;
1079				continue;
1080			} else if (instr[1] == '@') {
1081				/* "+@netgroup" */
1082				netgr_set(be, instr + 2);
1083				be->state = GETENT_NETGROUP;
1084				be->linelen = linelen;
1085				continue;
1086			} else {
1087				/* "+name" */
1088				name = instr + 1;
1089				break;
1090			}
1091			/* NOTREACHED */
1092
1093		    case GETENT_ALL:
1094			linelen = be->linelen;
1095			args->returnval = 0;
1096			(void) nss_getent(be->db_rootp, be->db_initf,
1097				&be->db_context, args);
1098			if (args->returnval == 0) {
1099				/* ==> ?? Treat ERANGE differently ?? */
1100				nss_endent(be->db_rootp, be->db_initf,
1101					&be->db_context);
1102				be->state = GETENT_FILE;
1103				continue;
1104			}
1105			if (strset_in(&be->minuses, (*be->getnamef)(args))) {
1106				continue;
1107			}
1108			name = 0; /* tell code below we've done the lookup */
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			(void) nss_search(be->db_rootp, be->db_initf,
1141				NSS_DBOP_next_iter, args);
1142			args->key.name = savename;  /* In case anyone cares */
1143		}
1144		/*
1145		 * Found one via "+", "+name" or "@netgroup".
1146		 * Override some fields if the /etc file says to do so.
1147		 */
1148		if (args->returnval == 0) {
1149			/* ==> ?? Should treat erange differently? */
1150			continue;
1151		}
1152		/* 'colon' was set umpteen iterations ago in GETENT_FILE */
1153		if (colon != 0) {
1154			*colon = ':';
1155			colon = 0;
1156		}
1157		return (do_merge(be, args, instr, linelen));
1158	}
1159	/*NOTREACHED*/
1160}
1161
1162/* We don't use this directly;  we just copy the bits when we want to	 */
1163/* initialize the variable (in the compat_backend struct) that we do use */
1164static DEFINE_NSS_GETENT(context_initval);
1165
1166nss_backend_t *
1167_nss_compat_constr(ops, n_ops, filename, min_bufsize, rootp, initf, netgroups,
1168		getname_func, merge_func)
1169	compat_backend_op_t	ops[];
1170	int			n_ops;
1171	const char		*filename;
1172	int			min_bufsize;
1173	nss_db_root_t		*rootp;
1174	nss_db_initf_t		initf;
1175	int			netgroups;
1176	compat_get_name		getname_func;
1177	compat_merge_func	merge_func;
1178{
1179	compat_backend_ptr_t	be;
1180
1181	if ((be = (compat_backend_ptr_t)malloc(sizeof (*be))) == 0) {
1182		return (0);
1183	}
1184	be->ops		= ops;
1185	be->n_ops	= n_ops;
1186	be->filename	= filename;
1187	be->f		= 0;
1188	be->minbuf	= min_bufsize;
1189	be->buf		= 0;
1190
1191	be->db_rootp	= rootp;
1192	be->db_initf	= initf;
1193	be->db_context	= context_initval;
1194
1195	be->getnamef	= getname_func;
1196	be->mergef	= merge_func;
1197
1198	if ((strcmp(be->filename, USERATTR_FILENAME) == 0) ||
1199	    (strcmp(be->filename, AUDITUSER_FILENAME) == 0))
1200		be->state = GETENT_ATTRDB;
1201	else
1202		be->state = GETENT_FILE;    /* i.e. do Automatic setent(); */
1203
1204	be->minuses	= 0;
1205
1206	be->permit_netgroups = netgroups;
1207	be->yp_domain	= 0;
1208	be->getnetgrent_backend	= 0;
1209	be->netgr_buffer = 0;
1210	be->return_string_data = 0;
1211
1212	return ((nss_backend_t *)be);
1213}
1214