xref: /openbsd-src/lib/libc/gen/auth_subr.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: auth_subr.c,v 1.23 2003/06/11 21:03:10 deraadt Exp $	*/
2 
3 /*-
4  * Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by Berkeley Software Design,
18  *	Inc.
19  * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
20  *    or promote products derived from this software without specific prior
21  *    written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  *	BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $
36  */
37 #include <sys/param.h>
38 #include <sys/time.h>
39 #include <sys/resource.h>
40 #include <sys/socket.h>
41 #include <sys/wait.h>
42 
43 #include <ctype.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <paths.h>
48 #include <pwd.h>
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <syslog.h>
54 #include <unistd.h>
55 
56 #include <login_cap.h>
57 
58 #define	MAXSPOOLSIZE	(8*1024)	/* Spool up to 8K of back info */
59 
60 struct rmfiles {
61 	struct rmfiles	*next;
62 	char		*file;
63 };
64 
65 struct authopts {
66 	struct authopts	*next;
67 	char		*opt;
68 };
69 
70 struct authdata {
71 	struct	authdata *next;
72 	void	*ptr;
73 	size_t	len;
74 };
75 
76 struct auth_session_t {
77 	char	*name;			/* name of use being authenticated */
78 	char	*style;			/* style of authentication used */
79 	char	*class;			/* class of user */
80 	char	*service;		/* type of service being performed */
81 	char	*challenge;		/* last challenge issued */
82 	int	flags;			/* see below */
83 	struct	passwd *pwd;		/* password entry for user */
84 	struct	timeval now;		/* time of authentication */
85 
86 	int	state;			/* authenticated state */
87 
88 	struct	rmfiles *rmlist;	/* list of files to remove on failure */
89 	struct	authopts *optlist;	/* list of options to scripts */
90 	struct	authdata *data;		/* additional data to send to scripts */
91 
92 	char	spool[MAXSPOOLSIZE];	/* data returned from login script */
93 	int	index;			/* how much returned thus far */
94 
95 	va_list	ap0;			/* argument list to auth_call */
96 	va_list	ap;			/* additional arguments to auth_call */
97 };
98 
99 /*
100  * Internal flags
101  */
102 #define	AF_INTERACTIVE		0x0001	/* This is an interactive session */
103 
104 /*
105  * We cannot include bsd_auth.h until we define the above structures
106  */
107 #include <bsd_auth.h>
108 
109 /*
110  * Internally used functions
111  */
112 static void _add_rmlist(auth_session_t *, char *);
113 static void _auth_spool(auth_session_t *, int);
114 static char *_auth_next_arg(auth_session_t *);
115 /*
116  * Set up a known environment for all authentication scripts.
117  */
118 static char *auth_environ[] = {
119 	"PATH=" _PATH_DEFPATH,
120 	"SHELL=" _PATH_BSHELL,
121 	NULL,
122 };
123 
124 static char defservice[] = LOGIN_DEFSERVICE;
125 
126 static va_list nilap;
127 
128 /*
129  * Quick one liners that only exist to keep auth_session_t opaque
130  */
131 void	auth_setstate(auth_session_t *as, int s){ as->state = s; }
132 void	auth_set_va_list(auth_session_t *as, va_list ap) { as->ap = ap; }
133 int	auth_getstate(auth_session_t *as)	{ return (as->state); }
134 struct passwd *auth_getpwd(auth_session_t *as)	{ return (as->pwd); }
135 
136 /*
137  * Open a new BSD Authentication session with the default service
138  * (which can be changed later).
139  */
140 auth_session_t *
141 auth_open(void)
142 {
143 	auth_session_t *as;
144 
145 	if ((as = malloc(sizeof(auth_session_t))) != NULL) {
146 		memset(as, 0, sizeof(*as));
147 		as->service = defservice;
148 	}
149 
150 	return (as);
151 }
152 
153 /*
154  * Clean the specified BSD Authentication session.
155  */
156 void
157 auth_clean(auth_session_t *as)
158 {
159 	struct rmfiles *rm;
160 	struct authopts *opt;
161 	struct authdata *data;
162 
163 	as->state = 0;
164 
165 	auth_clrenv(as);
166 
167 	/*
168 	 * Clean out the rmlist and remove specified files
169 	 */
170 	while ((rm = as->rmlist) != NULL) {
171 		as->rmlist = rm->next;
172 		unlink(rm->file);
173 		free(rm);
174 	}
175 
176 	/*
177 	 * Clean out the opt list
178 	 */
179 	while ((opt = as->optlist) != NULL) {
180 		as->optlist = opt->next;
181 		free(opt);
182 	}
183 
184 	/*
185 	 * Clean out data
186 	 */
187 	while ((data = as->data) != NULL) {
188 		if (as->data->len)
189 			memset(as->data->ptr, 0, as->data->len);
190 		as->data = data->next;
191 		free(data);
192 	}
193 
194 	auth_setitem(as, AUTHV_ALL, NULL);
195 
196 	if (as->pwd != NULL) {
197 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
198 		free(as->pwd);
199 		as->pwd = NULL;
200 	}
201 }
202 
203 /*
204  * Close the specified BSD Authentication session.
205  * Return 0 if not authenticated.
206  */
207 int
208 auth_close(auth_session_t *as)
209 {
210 	struct rmfiles *rm;
211 	struct authopts *opt;
212 	struct authdata *data;
213 	int s;
214 
215 	/*
216 	 * Save our return value
217 	 */
218 	s = as->state & AUTH_ALLOW;
219 
220 	if (s == 0)
221 		as->index = 0;
222 
223 	auth_setenv(as);
224 
225 
226 	/*
227 	 * Clean out the rmlist and remove specified files if the
228 	 * authentication failed
229 	 */
230 	while ((rm = as->rmlist) != NULL) {
231 		as->rmlist = rm->next;
232 		if (s == 0)
233 			unlink(rm->file);
234 		free(rm);
235 	}
236 
237 	/*
238 	 * Clean out the opt list
239 	 */
240 	while ((opt = as->optlist) != NULL) {
241 		as->optlist = opt->next;
242 		free(opt);
243 	}
244 
245 	/*
246 	 * Clean out data
247 	 */
248 	while ((data = as->data) != NULL) {
249 		if (as->data->len)
250 			memset(as->data->ptr, 0, as->data->len);
251 		as->data = data->next;
252 		free(data);
253 	}
254 
255 	if (as->pwd != NULL) {
256 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
257 		free(as->pwd);
258 		as->pwd = NULL;
259 	}
260 
261 	/*
262 	 * Clean up random variables
263 	 */
264 	if (as->service && as->service != defservice)
265 		free(as->service);
266 	if (as->challenge)
267 		free(as->challenge);
268 	if (as->class)
269 		free(as->class);
270 	if (as->style)
271 		free(as->style);
272 	if (as->name)
273 		free(as->name);
274 
275 	free(as);
276 	return (s);
277 }
278 
279 /*
280  * Request a challenge for the session.
281  * The name and style must have already been specified
282  */
283 char *
284 auth_challenge(auth_session_t *as)
285 {
286 	char path[MAXPATHLEN];
287 
288 	if (as == NULL || as->style == NULL || as->name == NULL)
289 		return (NULL);
290 
291 	as->state = 0;
292 
293 	if (as->challenge) {
294 		free(as->challenge);
295 		as->challenge = NULL;
296 	}
297 
298 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
299 	auth_call(as, path, as->style, "-s", "challenge", as->name,
300 	    as->class, (char *)NULL);
301 	if (as->state & AUTH_CHALLENGE)
302 		as->challenge = auth_getvalue(as, "challenge");
303 	as->state = 0;
304 	as->index = 0;	/* toss our data */
305 	return (as->challenge);
306 }
307 
308 /*
309  * Set/unset the requested environment variables.
310  * Mark the variables as set so they will not be set a second time.
311  * XXX - should provide a way to detect setenv() failure.
312  */
313 void
314 auth_setenv(auth_session_t *as)
315 {
316 	char *line, *name;
317 
318 	/*
319 	 * Set any environment variables we were asked for
320 	 */
321     	for (line = as->spool; line < as->spool + as->index;) {
322 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
323 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
324 				/* only do it once! */
325 				line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
326 				line += sizeof(BI_SETENV) - 1;
327 				for (name = line; isblank(*name); ++name)
328 					;
329 				for (line = name; *line && !isblank(*line);
330 				    ++line)
331 					;
332 				if (*line)
333 					*line++ = '\0';
334 				for (; isblank(*line); ++line)
335 					;
336 				if (*line != '\0' && setenv(name, line, 1))
337 					_warn("setenv(%s, %s)", name, line);
338 			}
339 		} else
340 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
341 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
342 				/* only do it once! */
343 				line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
344 				line += sizeof(BI_UNSETENV) - 1;
345 				for (name = line; isblank(*name); ++name)
346 					;
347 				for (line = name; *line && !isblank(*line);
348 				    ++line)
349 					;
350 				if (*line)
351 					*line++ = '\0';
352 				unsetenv(name);
353 			}
354 		}
355 		while (*line++)
356 			;
357 	}
358 }
359 
360 /*
361  * Clear out any requested environment variables.
362  */
363 void
364 auth_clrenv(auth_session_t *as)
365 {
366 	char *line;
367 
368     	for (line = as->spool; line < as->spool + as->index;) {
369 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
370 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
371 				line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
372 			}
373 		} else
374 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
375 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
376 				line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
377 			}
378 		}
379 		while (*line++)
380 			;
381 	}
382 }
383 
384 char *
385 auth_getitem(auth_session_t *as, auth_item_t item)
386 {
387 	if (as != NULL) {
388 		switch (item) {
389 		case AUTHV_CHALLENGE:
390 			return (as->challenge);
391 		case AUTHV_CLASS:
392 			return (as->class);
393 		case AUTHV_NAME:
394 			return (as->name);
395 		case AUTHV_SERVICE:
396 			return (as->service ? as->service : defservice);
397 		case AUTHV_STYLE:
398 			return (as->style);
399 		case AUTHV_INTERACTIVE:
400 			return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
401 		default:
402 			break;
403 		}
404 	}
405 	return (NULL);
406 }
407 
408 int
409 auth_setitem(auth_session_t *as, auth_item_t item, char *value)
410 {
411 	if (as == NULL) {
412 		errno = EINVAL;
413 		return (-1);
414 	}
415 
416 	switch (item) {
417 	case AUTHV_ALL:
418 		if (value != NULL) {
419 			errno = EINVAL;
420 			return (-1);
421 		}
422 		auth_setitem(as, AUTHV_CHALLENGE, NULL);
423 		auth_setitem(as, AUTHV_CLASS, NULL);
424 		auth_setitem(as, AUTHV_NAME, NULL);
425 		auth_setitem(as, AUTHV_SERVICE, NULL);
426 		auth_setitem(as, AUTHV_STYLE, NULL);
427 		auth_setitem(as, AUTHV_INTERACTIVE, NULL);
428 		return (0);
429 
430 	case AUTHV_CHALLENGE:
431 		if (value == as->challenge)
432 			return (0);
433 		if (value != NULL && (value = strdup(value)) == NULL)
434 			return (-1);
435 		if (as->challenge)
436 			free(as->challenge);
437 		as->challenge = value;
438 		return (0);
439 
440 	case AUTHV_CLASS:
441 		if (value == as->class)
442 			return (0);
443 		if (value != NULL && (value = strdup(value)) == NULL)
444 			return (-1);
445 		if (as->class)
446 			free(as->class);
447 		as->class = value;
448 		return (0);
449 
450 	case AUTHV_NAME:
451 		if (value == as->name)
452 			return (0);
453 		if (value != NULL && (value = strdup(value)) == NULL)
454 			return (-1);
455 		if (as->name)
456 			free(as->name);
457 		as->name = value;
458 		return (0);
459 
460 	case AUTHV_SERVICE:
461 		if (value == as->service)
462 			return (0);
463 		if (value == NULL || strcmp(value, defservice) == 0)
464 			value = defservice;
465 		else if ((value = strdup(value)) == NULL)
466 			return (-1);
467 		if (as->service && as->service != defservice)
468 			free(as->service);
469 		as->service = value;
470 		return (0);
471 
472 	case AUTHV_STYLE:
473 		if (value == as->style)
474 			return (0);
475 		if (value == NULL || strchr(value, '/') != NULL ||
476 		    (value = strdup(value)) == NULL)
477 			return (-1);
478 		if (as->style)
479 			free(as->style);
480 		as->style = value;
481 		return (0);
482 
483 	case AUTHV_INTERACTIVE:
484 		if (value == NULL)
485 			as->flags &= ~AF_INTERACTIVE;
486 		else
487 			as->flags |= ~AF_INTERACTIVE;
488 		return (0);
489 
490 	default:
491 		errno = EINVAL;
492 		return (-1);
493 	}
494 }
495 
496 int
497 auth_setoption(auth_session_t *as, char *n, char *v)
498 {
499 	struct authopts *opt;
500 	int i = strlen(n) + strlen(v) + 2;
501 
502 	if ((opt = malloc(sizeof(*opt) + i)) == NULL)
503 		return (-1);
504 
505 	opt->opt = (char *)(opt + 1);
506 
507 	snprintf(opt->opt, i, "%s=%s", n, v);
508 	opt->next = as->optlist;
509 	as->optlist = opt;
510 	return(0);
511 }
512 
513 void
514 auth_clroptions(auth_session_t *as)
515 {
516 	struct authopts *opt;
517 
518 	while ((opt = as->optlist) != NULL) {
519 		as->optlist = opt->next;
520 		free(opt);
521 	}
522 }
523 
524 void
525 auth_clroption(auth_session_t *as, char *option)
526 {
527 	struct authopts *opt, *oopt;
528 	int len;
529 
530 	len = strlen(option);
531 
532 	if ((opt = as->optlist) == NULL)
533 		return;
534 
535 	if (strncmp(opt->opt, option, len) == 0 &&
536 	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
537 		as->optlist = opt->next;
538 		free(opt);
539 		return;
540 	}
541 
542 	while ((oopt = opt->next) != NULL) {
543 		if (strncmp(oopt->opt, option, len) == 0 &&
544 		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
545 			opt->next = oopt->next;
546 			free(oopt);
547 			return;
548 		}
549 		opt = oopt;
550 	}
551 }
552 
553 int
554 auth_setdata(auth_session_t *as, void *ptr, size_t len)
555 {
556 	struct authdata *data, *dp;
557 
558 	if (len <= 0)
559 		return (0);
560 
561 	if ((data = malloc(sizeof(*data) + len)) == NULL)
562 		return (-1);
563 
564 	data->next = NULL;
565 	data->len = len;
566 	data->ptr = data + 1;
567 	memcpy(data->ptr, ptr, len);
568 
569 	if (as->data == NULL)
570 		as->data = data;
571 	else {
572 		for (dp = as->data; dp->next != NULL; dp = dp->next)
573 			;
574 		dp->next = data;
575 	}
576 	return (0);
577 }
578 
579 int
580 auth_setpwd(auth_session_t *as, struct passwd *pwd)
581 {
582 	char *instance;
583 
584 	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
585 		return (-1);		/* true failure */
586 
587 	if (pwd == NULL) {
588 		/*
589 		 * If we were not passed in a pwd structure we need to
590 		 * go find one for ourself.  Always look up the username
591 		 * (if it is defined) in the passwd database to see if there
592 		 * is an entry for the user.  If not, either use the current
593 		 * entry or simply return a 1 which implies there is
594 		 * no user by that name here.  This is not a failure, just
595 		 * a point of information.
596 		 */
597 		if (as->name == NULL)
598 			return (0);
599 		if ((pwd = getpwnam(as->name)) == NULL) {
600 			instance = strpbrk(as->name, "./");
601 			if (instance++ == NULL)
602 				return (as->pwd ? 0 : 1);
603 			if (strcmp(instance, "root") == 0)
604 				pwd = getpwnam(instance);
605 			if (pwd == NULL)
606 				return (as->pwd ? 0 : 1);
607 		}
608 	}
609 	if ((pwd = pw_dup(pwd)) == NULL)
610 		return (-1);		/* true failure */
611 	if (as->pwd) {
612 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
613 		free(as->pwd);
614 	}
615 	as->pwd = pwd;
616 	return (0);
617 }
618 
619 char *
620 auth_getvalue(auth_session_t *as, char *what)
621 {
622 	char *line, *v, *value;
623 	int n, len;
624 
625 	len = strlen(what);
626 
627     	for (line = as->spool; line < as->spool + as->index;) {
628 		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
629 			goto next;
630 		line += sizeof(BI_VALUE) - 1;
631 
632 		if (!isblank(*line))
633 			goto next;
634 
635 		while (isblank(*++line))
636 			;
637 
638 		if (strncmp(line, what, len) != 0 ||
639 		    !isblank(line[len]))
640 			goto next;
641 		line += len;
642 		while (isblank(*++line))
643 			;
644 		value = strdup(line);
645 		if (value == NULL)
646 			return (NULL);
647 
648 		/*
649 		 * XXX - There should be a more standardized
650 		 * routine for doing this sort of thing.
651 		 */
652 		for (line = v = value; *line; ++line) {
653 			if (*line == '\\') {
654 				switch (*++line) {
655 				case 'r':
656 					*v++ = '\r';
657 					break;
658 				case 'n':
659 					*v++ = '\n';
660 					break;
661 				case 't':
662 					*v++ = '\t';
663 					break;
664 				case '0': case '1': case '2':
665 				case '3': case '4': case '5':
666 				case '6': case '7':
667 					n = *line - '0';
668 					if (isdigit(line[1])) {
669 						++line;
670 						n <<= 3;
671 						n |= *line-'0';
672 					}
673 					if (isdigit(line[1])) {
674 						++line;
675 						n <<= 3;
676 						n |= *line-'0';
677 					}
678 					break;
679 				default:
680 					*v++ = *line;
681 					break;
682 				}
683 			} else
684 				*v++ = *line;
685 		}
686 		*v = '\0';
687 		return (value);
688 next:
689 		while (*line++)
690 			;
691 	}
692 	return (NULL);
693 }
694 
695 quad_t
696 auth_check_expire(auth_session_t *as)
697 {
698 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
699 		as->state &= ~AUTH_ALLOW;
700 		as->state |= AUTH_EXPIRED;	/* XXX */
701 		return (-1);
702 	}
703 
704 	if (as->pwd == NULL)
705 		return (0);
706 
707 	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
708 		if (as->now.tv_sec == 0)
709 			gettimeofday(&as->now, (struct timezone *)NULL);
710 		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
711 			as->state &= ~AUTH_ALLOW;
712 			as->state |= AUTH_EXPIRED;
713 		}
714 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
715 			return (-1);
716 		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
717 	}
718 	return (0);
719 }
720 
721 quad_t
722 auth_check_change(auth_session_t *as)
723 {
724 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
725 		as->state &= ~AUTH_ALLOW;
726 		as->state |= AUTH_PWEXPIRED;	/* XXX */
727 		return (-1);
728 	}
729 
730 	if (as->pwd == NULL)
731 		return (0);
732 
733 	if (as->pwd && (quad_t)as->pwd->pw_change) {
734 		if (as->now.tv_sec == 0)
735 			gettimeofday(&as->now, (struct timezone *)NULL);
736 		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
737 			as->state &= ~AUTH_ALLOW;
738 			as->state |= AUTH_PWEXPIRED;
739 		}
740 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
741 			return (-1);
742 		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
743 	}
744 	return (0);
745 }
746 
747 /*
748  * The down and dirty call to the login script
749  * okay contains the default return value, typically 0 but
750  * is AUTH_OKAY for approval like scripts.
751  *
752  * Internally additional trailing arguments can be read from as->ap
753  * Options will be placed just after the first argument (not including path).
754  *
755  * Any data will be sent to (and freed by) the script
756  */
757 int
758 auth_call(auth_session_t *as, char *path, ...)
759 {
760 	char *line;
761 	struct authdata *data;
762 	struct authopts *opt;
763 	pid_t pid;
764 	int status;
765 	int okay;
766 	int pfd[2];
767 	int argc;
768 	char *argv[64];		/* 64 args should be more than enough */
769 #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
770 
771 	va_start(as->ap0, path);
772 
773 	argc = 0;
774 	if ((argv[argc] = _auth_next_arg(as)) != NULL)
775 		++argc;
776 
777 	for (opt = as->optlist; opt != NULL; opt = opt->next) {
778 		if (argc < Nargc - 2) {
779 			argv[argc++] = "-v";
780 			argv[argc++] = opt->opt;
781 		} else {
782 			syslog(LOG_ERR, "too many authentication options");
783 			goto fail;
784 		}
785 	}
786 	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
787 		++argc;
788 
789 	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
790 		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
791 			va_end(as->ap0);
792 			memset(&(as->ap0), 0, sizeof(as->ap0));
793 		}
794 		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
795 			va_end(as->ap);
796 			memset(&(as->ap), 0, sizeof(as->ap));
797 		}
798 		syslog(LOG_ERR, "too many arguments");
799 		goto fail;
800 	}
801 
802 	argv[argc] = NULL;
803 
804 	if (secure_path(path) < 0) {
805 		syslog(LOG_ERR, "%s: path not secure", path);
806 		_warnx("invalid script: %s", path);
807 		goto fail;
808 	}
809 
810 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
811 		syslog(LOG_ERR, "unable to create backchannel %m");
812 		_warnx("internal resource failure");
813 		goto fail;
814 	}
815 
816 	switch (pid = fork()) {
817 	case -1:
818 		syslog(LOG_ERR, "%s: %m", path);
819 		_warnx("internal resource failure");
820 		close(pfd[0]);
821 		close(pfd[1]);
822 		goto fail;
823 	case 0:
824 #define	COMM_FD	3
825 		close(pfd[0]);
826 		if (pfd[1] != COMM_FD) {
827 			if (dup2(pfd[1], COMM_FD) < 0)
828 				err(1, "dup of backchannel");
829 			close(pfd[1]);
830 		}
831 
832 		for (status = getdtablesize() - 1; status > COMM_FD; status--)
833 			close(status);
834 
835 		execve(path, argv, auth_environ);
836 		syslog(LOG_ERR, "%s: %m", path);
837 		err(1, "%s", path);
838 	default:
839 		close(pfd[1]);
840 		while ((data = as->data) != NULL) {
841 			as->data = data->next;
842 			if (data->len > 0) {
843 				write(pfd[0], data->ptr, data->len);
844 				memset(data->ptr, 0, data->len);
845 			}
846 			free(data);
847 		}
848 		as->index = 0;
849 		_auth_spool(as, pfd[0]);
850 		close(pfd[0]);
851 		status = 0;
852 		if (waitpid(pid, &status, 0) < 0) {
853 			if (errno != ECHILD) {
854 				syslog(LOG_ERR, "%s: waitpid: %m", path);
855 				_warnx("internal failure");
856 				goto fail;
857 			}
858 		} else if (!WIFEXITED(status))
859 			goto fail;
860 	}
861 
862 	/*
863 	 * Now scan the spooled data
864 	 * It is easier to wait for all the data before starting
865 	 * to scan it.
866 	 */
867     	for (line = as->spool; line < as->spool + as->index;) {
868 		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
869 			line += sizeof(BI_REJECT) - 1;
870 			if (!*line || *line == ' ' || *line == '\t') {
871 				while (*line == ' ' || *line == '\t')
872 					++line;
873 				if (!strcasecmp(line, "silent")) {
874 					as->state = AUTH_SILENT;
875 					break;
876 				}
877 				if (!strcasecmp(line, "challenge")) {
878 					as->state  = AUTH_CHALLENGE;
879 					break;
880 				}
881 				if (!strcasecmp(line, "expired")) {
882 					as->state  = AUTH_EXPIRED;
883 					break;
884 				}
885 				if (!strcasecmp(line, "pwexpired")) {
886 					as->state  = AUTH_PWEXPIRED;
887 					break;
888 				}
889 			}
890 			break;
891 		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
892 			line += sizeof(BI_AUTH) - 1;
893 			if (!*line || *line == ' ' || *line == '\t') {
894 				while (*line == ' ' || *line == '\t')
895 					++line;
896 				if (*line == '\0')
897 					as->state |= AUTH_OKAY;
898 				else if (!strcasecmp(line, "root"))
899 					as->state |= AUTH_ROOTOKAY;
900 				else if (!strcasecmp(line, "secure"))
901 					as->state |= AUTH_SECURE;
902 			}
903 		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
904 			line += sizeof(BI_REMOVE) - 1;
905 			while (*line == ' ' || *line == '\t')
906 				++line;
907 			if (*line)
908 				_add_rmlist(as, line);
909 		}
910 		while (*line++)
911 			;
912 	}
913 
914 	if (WEXITSTATUS(status))
915 		as->state &= ~AUTH_ALLOW;
916 
917 	okay = as->state & AUTH_ALLOW;
918 
919 	if (!okay)
920 		auth_clrenv(as);
921 
922 	if (0) {
923 fail:
924 		auth_clrenv(as);
925 		as->state = 0;
926 		okay = -1;
927 	}
928 
929 	while ((data = as->data) != NULL) {
930 		as->data = data->next;
931 		free(data);
932 	}
933 
934 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
935 		va_end(as->ap0);
936 		memset(&(as->ap0), 0, sizeof(as->ap0));
937 	}
938 
939 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
940 		va_end(as->ap);
941 		memset(&(as->ap), 0, sizeof(as->ap));
942 	}
943 	return (okay);
944 }
945 
946 static void
947 _auth_spool(auth_session_t *as, int fd)
948 {
949 	int r;
950 	char *b;
951 
952 	while (as->index < sizeof(as->spool) - 1) {
953 		r = read(fd, as->spool + as->index,
954 		    sizeof(as->spool) - as->index);
955 		if (r <= 0) {
956 			as->spool[as->index] = '\0';
957 			return;
958 		}
959 		b = as->spool + as->index;
960 		as->index += r;
961 		/*
962 		 * Go ahead and convert newlines into NULs to allow
963 		 * easy scanning of the file.
964 		 */
965 		while (r-- > 0)
966 			if (*b++ == '\n')
967 				b[-1] = '\0';
968 	}
969 
970 	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
971 	errx(1, "System error in authentication program");
972 }
973 
974 static void
975 _add_rmlist(auth_session_t *as, char *file)
976 {
977 	struct rmfiles *rm;
978 	int i = strlen(file) + 1;
979 
980 	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
981 		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
982 		return;
983 	}
984 	rm->file = (char *)(rm + 1);
985 	rm->next = as->rmlist;
986 	strlcpy(rm->file, file, i);
987 	as->rmlist = rm;
988 }
989 
990 static char *
991 _auth_next_arg(auth_session_t *as)
992 {
993 	char *arg;
994 
995 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
996 		if ((arg = va_arg(as->ap0, char *)) != NULL)
997 			return (arg);
998 		va_end(as->ap0);
999 		memset(&(as->ap0), 0, sizeof(as->ap0));
1000 	}
1001 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1002 		if ((arg = va_arg(as->ap, char *)) != NULL)
1003 			return (arg);
1004 		va_end(as->ap);
1005 		memset(&(as->ap), 0, sizeof(as->ap));
1006 	}
1007 	return (NULL);
1008 }
1009