xref: /openbsd-src/lib/libc/gen/auth_subr.c (revision 33b792a3c1c87b47219fdf9a73548c4003214de3)
1 /*	$OpenBSD: auth_subr.c,v 1.8 2002/02/05 07:51:52 mpech 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()
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 challange 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 	as->state = 0;
289 
290 	if (as == NULL || as->style == NULL || as->name == NULL)
291 		return (NULL);
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, 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 != NULL && (value = strdup(value)) == NULL)
432 			return (-1);
433 		if (as->challenge)
434 			free(as->challenge);
435 		as->challenge = value;
436 		return (0);
437 
438 	case AUTHV_CLASS:
439 		if (value != NULL && (value = strdup(value)) == NULL)
440 			return (-1);
441 
442 		if (as->class)
443 			free(as->class);
444 
445 		as->class = value;
446 		return (0);
447 
448 	case AUTHV_NAME:
449 		if (value != NULL && (value = strdup(value)) == NULL)
450 			return (-1);
451 
452 		if (as->name)
453 			free(as->name);
454 
455 		as->name = value;
456 		return (0);
457 
458 	case AUTHV_SERVICE:
459 		if (value == NULL || strcmp(value, defservice) == 0)
460 			value = defservice;
461 		else if ((value = strdup(value)) == NULL)
462 			return (-1);
463 
464 		if (as->service && as->service != defservice)
465 			free(as->service);
466 
467 		as->service = value;
468 		return (0);
469 
470 	case AUTHV_STYLE:
471 		if (value == NULL || strchr(value, '/') != NULL ||
472 		    (value = strdup(value)) == NULL)
473 			return (-1);
474 
475 		if (as->style)
476 			free(as->style);
477 
478 		as->style = value;
479 		return (0);
480 
481 	case AUTHV_INTERACTIVE:
482 		if (value == NULL)
483 			as->flags &= ~AF_INTERACTIVE;
484 		else
485 			as->flags |= ~AF_INTERACTIVE;
486 		return (0);
487 
488 	default:
489 		errno = EINVAL;
490 		return (-1);
491 	}
492 }
493 
494 int
495 auth_setoption(auth_session_t *as, char *n, char *v)
496 {
497 	struct authopts *opt;
498 	int i = strlen(n) + strlen(v) + 2;
499 
500 	if ((opt = malloc(sizeof(*opt) + i)) == NULL)
501 		return (-1);
502 
503 	opt->opt = (char *)(opt + 1);
504 
505 	sprintf(opt->opt, "%s=%s", n, v);
506 	opt->next = as->optlist;
507 	as->optlist = opt;
508 	return(0);
509 }
510 
511 void
512 auth_clroptions(auth_session_t *as)
513 {
514 	struct authopts *opt;
515 
516 	while ((opt = as->optlist) != NULL) {
517 		as->optlist = opt->next;
518 		free(opt);
519 	}
520 }
521 
522 void
523 auth_clroption(auth_session_t *as, char *option)
524 {
525 	struct authopts *opt, *oopt;
526 	int len;
527 
528 	len = strlen(option);
529 
530 	if ((opt = as->optlist) == NULL)
531 		return;
532 
533 	if (strncmp(opt->opt, option, len) == 0 &&
534 	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
535 		as->optlist = opt->next;
536 		free(opt);
537 		return;
538 	}
539 
540 	while ((oopt = opt->next) != NULL) {
541 		if (strncmp(oopt->opt, option, len) == 0 &&
542 		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
543 			opt->next = oopt->next;
544 			free(oopt);
545 			return;
546 		}
547 		opt = oopt;
548 	}
549 }
550 
551 int
552 auth_setdata(auth_session_t *as, void *ptr, size_t len)
553 {
554 	struct authdata *data, *dp;
555 
556 	if (len <= 0)
557 		return (0);
558 
559 	if ((data = malloc(sizeof(*data) + len)) == NULL)
560 		return (-1);
561 
562 	data->next = NULL;
563 	data->len = len;
564 	data->ptr = data + 1;
565 	memcpy(data->ptr, ptr, len);
566 
567 	if (as->data == NULL)
568 		as->data = data;
569 	else {
570 		for (dp = as->data; dp->next != NULL; dp = dp->next)
571 			;
572 		dp->next = data;
573 	}
574 	return (0);
575 }
576 
577 int
578 auth_setpwd(auth_session_t *as, struct passwd *pwd)
579 {
580 	char *instance;
581 
582 	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
583 		return (-1);		/* true failure */
584 
585 	if (pwd == NULL) {
586 		/*
587 		 * If we were not passed in a pwd structure we need to
588 		 * go find one for ourself.  Always look up the username
589 		 * (if it is defined) in the passwd database to see if there
590 		 * is an entry for the user.  If not, either use the current
591 		 * entry or simply return a 1 which implies there is
592 		 * no user by that name here.  This is not a failure, just
593 		 * a point of information.
594 		 */
595 		if (as->name == NULL)
596 			return (0);
597 		if ((pwd = getpwnam(as->name)) == NULL) {
598 			instance = strpbrk(as->name, "./");
599 			if (instance++ == NULL)
600 				return (as->pwd ? 0 : 1);
601 			if (strcmp(instance, "root") == 0)
602 				pwd = getpwnam(instance);
603 			if (pwd == NULL)
604 				return (as->pwd ? 0 : 1);
605 		}
606 	}
607 	if ((pwd = pw_dup(pwd)) == NULL)
608 		return (-1);		/* true failure */
609 	if (as->pwd) {
610 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
611 		free(as->pwd);
612 	}
613 	as->pwd = pwd;
614 	return (0);
615 }
616 
617 char *
618 auth_getvalue(auth_session_t *as, char *what)
619 {
620 	char *line, *v, *value;
621 	int n, len;
622 
623 	len = strlen(what);
624 
625     	for (line = as->spool; line < as->spool + as->index;) {
626 		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
627 			goto next;
628 		line += sizeof(BI_VALUE) - 1;
629 
630 		if (!isblank(*line))
631 			goto next;
632 
633 		while (isblank(*++line))
634 			;
635 
636 		if (strncmp(line, what, len) != 0 ||
637 		    !isblank(line[len]))
638 			goto next;
639 		line += len;
640 		while (isblank(*++line))
641 			;
642 		value = strdup(line);
643 		if (value == NULL)
644 			return (NULL);
645 
646 		/*
647 		 * XXX - There should be a more standardized
648 		 * routine for doing this sort of thing.
649 		 */
650 		for (line = v = value; *line; ++line) {
651 			if (*line == '\\') {
652 				switch (*++line) {
653 				case 'r':
654 					*v++ = '\r';
655 					break;
656 				case 'n':
657 					*v++ = '\n';
658 					break;
659 				case 't':
660 					*v++ = '\t';
661 					break;
662 				case '0': case '1': case '2':
663 				case '3': case '4': case '5':
664 				case '6': case '7':
665 					n = *line - '0';
666 					if (isdigit(line[1])) {
667 						++line;
668 						n <<= 3;
669 						n |= *line-'0';
670 					}
671 					if (isdigit(line[1])) {
672 						++line;
673 						n <<= 3;
674 						n |= *line-'0';
675 					}
676 					break;
677 				default:
678 					*v++ = *line;
679 					break;
680 				}
681 			} else
682 				*v++ = *line;
683 		}
684 		*v = '\0';
685 		return (value);
686 next:
687 		while (*line++)
688 			;
689 	}
690 	return (NULL);
691 }
692 
693 quad_t
694 auth_check_expire(auth_session_t *as)
695 {
696 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
697 		as->state &= ~AUTH_ALLOW;
698 		as->state |= AUTH_EXPIRED;	/* XXX */
699 		return (-1);
700 	}
701 
702 	if (as->pwd == NULL)
703 		return (0);
704 
705 	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
706 		if (as->now.tv_sec == 0)
707 			gettimeofday(&as->now, (struct timezone *)NULL);
708 		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
709 			as->state &= ~AUTH_ALLOW;
710 			as->state |= AUTH_EXPIRED;
711 		}
712 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
713 			return (-1);
714 		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
715 	}
716 	return (0);
717 }
718 
719 quad_t
720 auth_check_change(auth_session_t *as)
721 {
722 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
723 		as->state &= ~AUTH_ALLOW;
724 		as->state |= AUTH_PWEXPIRED;	/* XXX */
725 		return (-1);
726 	}
727 
728 	if (as->pwd == NULL)
729 		return (0);
730 
731 	if (as->pwd && (quad_t)as->pwd->pw_change) {
732 		if (as->now.tv_sec == 0)
733 			gettimeofday(&as->now, (struct timezone *)NULL);
734 		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
735 			as->state &= ~AUTH_ALLOW;
736 			as->state |= AUTH_PWEXPIRED;
737 		}
738 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
739 			return (-1);
740 		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
741 	}
742 	return (0);
743 }
744 
745 /*
746  * The down and dirty call to the login script
747  * okay contains the default return value, typically 0 but
748  * is AUTH_OKAY for approval like scripts.
749  *
750  * Internally additional trailing arguments can be read from as->ap
751  * Options will be placed be placed just after the first argument
752  * (not including path).
753  *
754  * Any data will sent (and freed) to the script
755  */
756 int
757 auth_call(auth_session_t *as, char *path, ...)
758 {
759 	char *line;
760 	struct authdata *data;
761 	struct authopts *opt;
762 	pid_t pid;
763 	int status;
764 	int okay;
765 	int pfd[2];
766 	int argc;
767 	char *argv[64];		/* 64 args should more than enough */
768 #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
769 
770 	va_start(as->ap0, path);
771 
772 	argc = 0;
773 	if ((argv[argc] = _auth_next_arg(as)) != NULL)
774 		++argc;
775 
776 	for (opt = as->optlist; opt != NULL; opt = opt->next) {
777 		if (argc < Nargc - 2) {
778 			argv[argc++] = "-v";
779 			argv[argc++] = opt->opt;
780 		} else {
781 			syslog(LOG_ERR, "too many authentication options");
782 			goto fail;
783 		}
784 	}
785 	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
786 		++argc;
787 
788 	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
789 		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
790 			va_end(as->ap0);
791 			memset(&(as->ap0), 0, sizeof(as->ap0));
792 		}
793 		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
794 			va_end(as->ap);
795 			memset(&(as->ap), 0, sizeof(as->ap));
796 		}
797 		syslog(LOG_ERR, "too many arguments");
798 		goto fail;
799 	}
800 
801 	argv[argc] = NULL;
802 
803 	if (secure_path(path) < 0) {
804 		syslog(LOG_ERR, "%s: path not secure", path);
805 		_warnx("invalid script: %s", path);
806 		goto fail;
807 	}
808 
809 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
810 		syslog(LOG_ERR, "unable to create backchannel %m");
811 		_warnx("internal resource failure");
812 		goto fail;
813 	}
814 
815 	switch (pid = fork()) {
816 	case -1:
817 		close(pfd[0]);
818 		close(pfd[1]);
819 		syslog(LOG_ERR, "%s: %m", path);
820 		_warnx("internal resource failure");
821 		goto fail;
822 	case 0:
823 #define	COMM_FD	3
824 		close(pfd[0]);
825 		if (pfd[1] != COMM_FD) {
826 			if (dup2(pfd[1], COMM_FD) < 0)
827 				err(1, "dup of backchannel");
828 			close(pfd[1]);
829 		}
830 
831 		for (status = getdtablesize() - 1; status > COMM_FD; status--)
832 			close(status);
833 
834 		execve(path, argv, auth_environ);
835 		syslog(LOG_ERR, "%s: %m", path);
836 		err(1, "%s", path);
837 	default:
838 		close(pfd[1]);
839 		while ((data = as->data) != NULL) {
840 			as->data = data->next;
841 			if (data->len > 0) {
842 				write(pfd[0], data->ptr, data->len);
843 				memset(data->ptr, 0, data->len);
844 			}
845 			free(data);
846 		}
847 		as->index = 0;
848 		_auth_spool(as, pfd[0]);
849 		close(pfd[0]);
850 		if (waitpid(pid, &status, 0) < 0) {
851 			if (errno != ECHILD) {
852 				syslog(LOG_ERR, "%s: waitpid: %m", path);
853 				_warnx("internal failure");
854 				goto fail;
855 			}
856 		} else if (!WIFEXITED(status))
857 			goto fail;
858 	}
859 
860 	/*
861 	 * Now scan the spooled data
862 	 * It is easier to wait for all the data before starting
863 	 * to scan it.
864 	 */
865     	for (line = as->spool; line < as->spool + as->index;) {
866 		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
867 			line += sizeof(BI_REJECT) - 1;
868 			if (!*line || *line == ' ' || *line == '\t') {
869 				while (*line == ' ' || *line == '\t')
870 					++line;
871 				if (!strcasecmp(line, "silent")) {
872 					as->state = AUTH_SILENT;
873 					break;
874 				}
875 				if (!strcasecmp(line, "challenge")) {
876 					as->state  = AUTH_CHALLENGE;
877 					break;
878 				}
879 				if (!strcasecmp(line, "expired")) {
880 					as->state  = AUTH_EXPIRED;
881 					break;
882 				}
883 				if (!strcasecmp(line, "pwexpired")) {
884 					as->state  = AUTH_PWEXPIRED;
885 					break;
886 				}
887 			}
888 			break;
889 		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
890 			line += sizeof(BI_AUTH) - 1;
891 			if (!*line || *line == ' ' || *line == '\t') {
892 				while (*line == ' ' || *line == '\t')
893 					++line;
894 				if (*line == '\0')
895 					as->state |= AUTH_OKAY;
896 				else if (!strcasecmp(line, "root"))
897 					as->state |= AUTH_ROOTOKAY;
898 				else if (!strcasecmp(line, "secure"))
899 					as->state |= AUTH_SECURE;
900 			}
901 		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
902 			line += sizeof(BI_REMOVE) - 1;
903 			while (*line == ' ' || *line == '\t')
904 				++line;
905 			if (*line)
906 				_add_rmlist(as, line);
907 		}
908 		while (*line++)
909 			;
910 	}
911 
912 	if (WEXITSTATUS(status))
913 		as->state &= ~AUTH_ALLOW;
914 
915 	okay = as->state & AUTH_ALLOW;
916 
917 	if (!okay)
918 		auth_clrenv(as);
919 
920 	if (0) {
921 fail:
922 		auth_clrenv(as);
923 		as->state = 0;
924 		okay = -1;
925 	}
926 
927 	while ((data = as->data) != NULL) {
928 		as->data = data->next;
929 		free(data);
930 	}
931 
932 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
933 		va_end(as->ap0);
934 		memset(&(as->ap0), 0, sizeof(as->ap0));
935 	}
936 
937 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
938 		va_end(as->ap);
939 		memset(&(as->ap), 0, sizeof(as->ap));
940 	}
941 	return (okay);
942 }
943 
944 static void
945 _auth_spool(auth_session_t *as, int fd)
946 {
947 	int r;
948 	char *b;
949 
950 	while (as->index < sizeof(as->spool) - 1) {
951 		r = read(fd, as->spool + as->index,
952 		    sizeof(as->spool) - as->index);
953 		if (r <= 0) {
954 			as->spool[as->index] = '\0';
955 			return;
956 		}
957 		b = as->spool + as->index;
958 		as->index += r;
959 		/*
960 		 * Go ahead and convert newlines into NULs to allow
961 		 * easy scanning of the file.
962 		 */
963 		while(r-- > 0)
964 			if (*b++ == '\n')
965 				b[-1] = '\0';
966 	}
967 
968 	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
969 	errx(1, "System error in authentication program");
970 }
971 
972 static void
973 _add_rmlist(auth_session_t *as, char *file)
974 {
975 	struct rmfiles *rm;
976 	int i = strlen(file) + 1;
977 
978 	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
979 		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
980 		return;
981 	}
982 	rm->file = (char *)(rm + 1);
983 	rm->next = as->rmlist;
984 	strcpy(rm->file, file);
985 	as->rmlist = rm;
986 }
987 
988 static char *
989 _auth_next_arg(auth_session_t *as)
990 {
991 	char *arg;
992 
993 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
994 		if ((arg = va_arg(as->ap0, char *)) != NULL)
995 			return (arg);
996 		va_end(as->ap0);
997 		memset(&(as->ap0), 0, sizeof(as->ap0));
998 	}
999 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1000 		if ((arg = va_arg(as->ap, char *)) != NULL)
1001 			return (arg);
1002 		va_end(as->ap);
1003 		memset(&(as->ap), 0, sizeof(as->ap));
1004 	}
1005 	return (NULL);
1006 }
1007