xref: /openbsd-src/lib/libc/gen/auth_subr.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: auth_subr.c,v 1.6 2001/07/02 19:22:34 millert 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 authdata *data;
161 
162 	as->state = 0;
163 
164 	auth_clrenv(as);
165 
166 	/*
167 	 * Clean out the rmlist and remove specified files
168 	 */
169 	while ((rm = as->rmlist) != NULL) {
170 		as->rmlist = rm->next;
171 		unlink(rm->file);
172 		free(rm);
173 	}
174 
175 	/*
176 	 * Clean out data
177 	 */
178 	while ((data = as->data) != NULL) {
179 		if (as->data->len)
180 			memset(as->data->ptr, 0, as->data->len);
181 		as->data = data->next;
182 		free(data);
183 	}
184 
185 	auth_setitem(as, AUTHV_ALL, NULL);
186 
187 	if (as->pwd != NULL) {
188 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
189 		free(as->pwd);
190 		as->pwd = NULL;
191 	}
192 }
193 
194 /*
195  * Close the specified BSD Authentication session.
196  * Return 0 if not authenticated.
197  */
198 int
199 auth_close(auth_session_t *as)
200 {
201 	struct rmfiles *rm;
202         struct authopts *opt;
203         struct authdata *data;
204 	int s;
205 
206 	/*
207 	 * Save our return value
208 	 */
209 	s = as->state & AUTH_ALLOW;
210 
211 	if (s == 0)
212 		as->index = 0;
213 
214 	auth_setenv(as);
215 
216 
217 	/*
218 	 * Clean out the rmlist and remove specified files if the
219 	 * authentication failed
220 	 */
221 	while ((rm = as->rmlist) != NULL) {
222 		as->rmlist = rm->next;
223 		if (s == 0)
224 			unlink(rm->file);
225 		free(rm);
226 	}
227 
228 	/*
229 	 * Clean out the opt list
230 	 */
231         while ((opt = as->optlist) != NULL) {
232                 as->optlist = opt->next;
233                 free(opt);
234         }
235 
236 	/*
237 	 * Clean out data
238 	 */
239 	while ((data = as->data) != NULL) {
240 		if (as->data->len)
241 			memset(as->data->ptr, 0, as->data->len);
242 		as->data = data->next;
243 		free(data);
244 	}
245 
246 	/*
247 	 * Clean up random variables
248 	 */
249 	if (as->service && as->service != defservice)
250 		free(as->service);
251 	if (as->challenge)
252 		free(as->challenge);
253 	if (as->class)
254 		free(as->class);
255 	if (as->style)
256 		free(as->style);
257 	if (as->name)
258 		free(as->name);
259 
260 	free(as);
261 	return (s);
262 }
263 
264 /*
265  * Request a challange for the session.
266  * The name and style must have already been specified
267  */
268 char *
269 auth_challenge(auth_session_t *as)
270 {
271 	char path[MAXPATHLEN];
272 
273 	as->state = 0;
274 
275 	if (as == NULL || as->style == NULL || as->name == NULL)
276 		return (NULL);
277 
278 	if (as->challenge) {
279 		free(as->challenge);
280 		as->challenge = NULL;
281 	}
282 
283 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
284         auth_call(as, path, as->style, "-s", "challenge", as->name,
285 	    as->class, NULL);
286 	if (as->state & AUTH_CHALLENGE)
287 		as->challenge = auth_getvalue(as, "challenge");
288 	as->state = 0;
289 	as->index = 0;	/* toss our data */
290 	return (as->challenge);
291 }
292 
293 /*
294  * Set/unset the requested environment variables.
295  * Mark the variables as set so they will not be set a second time.
296  * XXX - should provide a way to detect setenv() failure.
297  */
298 void
299 auth_setenv(auth_session_t *as)
300 {
301 	char *line, *name;
302 
303 	/*
304 	 * Set any environment variables we were asked for
305 	 */
306     	for (line = as->spool; line < as->spool + as->index;) {
307 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
308 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
309 				/* only do it once! */
310 				line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
311 				line += sizeof(BI_SETENV) - 1;
312 				for (name = line; isblank(*name); ++name)
313 					;
314 				for (line = name; *line && !isblank(*line);
315 				    ++line)
316 					;
317 				if (*line)
318 					*line++ = '\0';
319 				for (; isblank(*line); ++line)
320 					;
321 				if (*line != '\0' && setenv(name, line, 1))
322 					_warn("setenv(%s, %s)", name, line);
323 			}
324 		} else
325 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
326 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
327 				/* only do it once! */
328 				line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
329 				line += sizeof(BI_UNSETENV) - 1;
330 				for (name = line; isblank(*name); ++name)
331 					;
332 				for (line = name; *line && !isblank(*line);
333 				    ++line)
334 					;
335 				if (*line)
336 					*line++ = '\0';
337 				unsetenv(name);
338 			}
339 		}
340 		while (*line++)
341 			;
342 	}
343 }
344 
345 /*
346  * Clear out any requested environment variables.
347  */
348 void
349 auth_clrenv(auth_session_t *as)
350 {
351 	char *line;
352 
353     	for (line = as->spool; line < as->spool + as->index;) {
354 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
355 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
356 				line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
357 			}
358 		} else
359 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
360 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
361 				line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
362 			}
363 		}
364 		while (*line++)
365 			;
366 	}
367 }
368 
369 char *
370 auth_getitem(auth_session_t *as, auth_item_t item)
371 {
372 	if (as != NULL) {
373 		switch (item) {
374 		case AUTHV_CHALLENGE:
375 			return (as->challenge);
376 		case AUTHV_CLASS:
377 			return (as->class);
378 		case AUTHV_NAME:
379 			return (as->name);
380 		case AUTHV_SERVICE:
381 			return (as->service ? as->service : defservice);
382 		case AUTHV_STYLE:
383 			return (as->style);
384 		case AUTHV_INTERACTIVE:
385 			return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
386 		default:
387 			break;
388 		}
389 	}
390 	return (NULL);
391 }
392 
393 int
394 auth_setitem(auth_session_t *as, auth_item_t item, char *value)
395 {
396 	if (as == NULL) {
397 		errno = EINVAL;
398 		return (-1);
399 	}
400 
401 	switch (item) {
402 	case AUTHV_ALL:
403 		if (value != NULL) {
404 			errno = EINVAL;
405 			return (-1);
406 		}
407 		auth_setitem(as, AUTHV_CHALLENGE, NULL);
408 		auth_setitem(as, AUTHV_CLASS, NULL);
409 		auth_setitem(as, AUTHV_NAME, NULL);
410 		auth_setitem(as, AUTHV_SERVICE, NULL);
411 		auth_setitem(as, AUTHV_STYLE, NULL);
412 		auth_setitem(as, AUTHV_INTERACTIVE, NULL);
413 		return (0);
414 
415 	case AUTHV_CHALLENGE:
416 		if (value != NULL && (value = strdup(value)) == NULL)
417 			return (-1);
418 		if (as->challenge)
419 			free(as->challenge);
420 		as->challenge = value;
421 		return (0);
422 
423 	case AUTHV_CLASS:
424 		if (value != NULL && (value = strdup(value)) == NULL)
425 			return (-1);
426 
427 		if (as->class)
428 			free(as->class);
429 
430 		as->class = value;
431 		return (0);
432 
433 	case AUTHV_NAME:
434 		if (value != NULL && (value = strdup(value)) == NULL)
435 			return (-1);
436 
437 		if (as->name)
438 			free(as->name);
439 
440 		as->name = value;
441 		return (0);
442 
443 	case AUTHV_SERVICE:
444 		if (value == NULL || strcmp(value, defservice) == 0)
445 			value = defservice;
446 		else if ((value = strdup(value)) == NULL)
447 			return (-1);
448 
449 		if (as->service && as->service != defservice)
450 			free(as->service);
451 
452 		as->service = value;
453 		return (0);
454 
455 	case AUTHV_STYLE:
456 		if (value == NULL || strchr(value, '/') != NULL ||
457 		    (value = strdup(value)) == NULL)
458 			return (-1);
459 
460 		if (as->style)
461 			free(as->style);
462 
463 		as->style = value;
464 		return (0);
465 
466 	case AUTHV_INTERACTIVE:
467 		if (value == NULL)
468 			as->flags &= ~AF_INTERACTIVE;
469 		else
470 			as->flags |= ~AF_INTERACTIVE;
471 		return (0);
472 
473 	default:
474 		errno = EINVAL;
475 		return (-1);
476 	}
477 }
478 
479 int
480 auth_setoption(auth_session_t *as, char *n, char *v)
481 {
482 	struct authopts *opt;
483 	int i = strlen(n) + strlen(v) + 2;
484 
485 	if ((opt = malloc(sizeof(*opt) + i)) == NULL)
486 		return (-1);
487 
488 	opt->opt = (char *)(opt + 1);
489 
490 	sprintf(opt->opt, "%s=%s", n, v);
491 	opt->next = as->optlist;
492 	as->optlist = opt;
493 	return(0);
494 }
495 
496 void
497 auth_clroptions(auth_session_t *as)
498 {
499 	struct authopts *opt;
500 
501 	while ((opt = as->optlist) != NULL) {
502 		as->optlist = opt->next;
503 		free(opt);
504 	}
505 }
506 
507 void
508 auth_clroption(auth_session_t *as, char *option)
509 {
510 	struct authopts *opt, *oopt;
511 	int len;
512 
513 	len = strlen(option);
514 
515 	if ((opt = as->optlist) == NULL)
516 		return;
517 
518 	if (strncmp(opt->opt, option, len) == 0 &&
519 	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
520 		as->optlist = opt->next;
521 		free(opt);
522 		return;
523 	}
524 
525 	while ((oopt = opt->next) != NULL) {
526 		if (strncmp(oopt->opt, option, len) == 0 &&
527 		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
528 			opt->next = oopt->next;
529 			free(oopt);
530 			return;
531 		}
532 		opt = oopt;
533 	}
534 }
535 
536 int
537 auth_setdata(auth_session_t *as, void *ptr, size_t len)
538 {
539 	struct authdata *data, *dp;
540 
541 	if (len <= 0)
542 		return (0);
543 
544 	if ((data = malloc(sizeof(*data) + len)) == NULL)
545 		return (-1);
546 
547 	data->next = NULL;
548 	data->len = len;
549 	data->ptr = data + 1;
550 	memcpy(data->ptr, ptr, len);
551 
552 	if (as->data == NULL)
553 		as->data = data;
554 	else {
555 		for (dp = as->data; dp->next != NULL; dp = dp->next)
556 			;
557 		dp->next = data;
558 	}
559 	return (0);
560 }
561 
562 int
563 auth_setpwd(auth_session_t *as, struct passwd *pwd)
564 {
565 	char *instance;
566 
567 	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
568 		return (-1);		/* true failure */
569 
570 	if (pwd == NULL) {
571 		/*
572 		 * If we were not passed in a pwd structure we need to
573 		 * go find one for ourself.  Always look up the username
574 		 * (if it is defined) in the passwd database to see if there
575 		 * is an entry for the user.  If not, either use the current
576 		 * entry or simply return a 1 which implies there is
577 		 * no user by that name here.  This is not a failure, just
578 		 * a point of information.
579 		 */
580 		if (as->name == NULL)
581 			return (0);
582 		if ((pwd = getpwnam(as->name)) == NULL) {
583 			instance = strpbrk(as->name, "./");
584 			if (instance++ == NULL)
585 				return (as->pwd ? 0 : 1);
586 			if (strcmp(instance, "root") == 0)
587 				pwd = getpwnam(instance);
588 			if (pwd == NULL)
589 				return (as->pwd ? 0 : 1);
590 		}
591 	}
592 	if ((pwd = pw_dup(pwd)) == NULL)
593 		return (-1);		/* true failure */
594 	if (as->pwd) {
595 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
596 		free(as->pwd);
597 	}
598 	as->pwd = pwd;
599 	return (0);
600 }
601 
602 char *
603 auth_getvalue(auth_session_t *as, char *what)
604 {
605 	char *line, *v, *value;
606 	int n, len;
607 
608 	len = strlen(what);
609 
610     	for (line = as->spool; line < as->spool + as->index;) {
611 		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
612 			goto next;
613 		line += sizeof(BI_VALUE) - 1;
614 
615 		if (!isblank(*line))
616 			goto next;
617 
618 		while (isblank(*++line))
619 			;
620 
621 		if (strncmp(line, what, len) != 0 ||
622 		    !isblank(line[len]))
623 			goto next;
624 		line += len;
625 		while (isblank(*++line))
626 			;
627 		value = strdup(line);
628 		if (value == NULL)
629 			return (NULL);
630 
631 		/*
632 		 * XXX - There should be a more standardized
633 		 * routine for doing this sort of thing.
634 		 */
635 		for (line = v = value; *line; ++line) {
636 			if (*line == '\\') {
637 				switch (*++line) {
638 				case 'r':
639 					*v++ = '\r';
640 					break;
641 				case 'n':
642 					*v++ = '\n';
643 					break;
644 				case 't':
645 					*v++ = '\t';
646 					break;
647 				case '0': case '1': case '2':
648 				case '3': case '4': case '5':
649 				case '6': case '7':
650 					n = *line - '0';
651 					if (isdigit(line[1])) {
652 						++line;
653 						n <<= 3;
654 						n |= *line-'0';
655 					}
656 					if (isdigit(line[1])) {
657 						++line;
658 						n <<= 3;
659 						n |= *line-'0';
660 					}
661 					break;
662 				default:
663 					*v++ = *line;
664 					break;
665 				}
666 			} else
667 				*v++ = *line;
668 		}
669 		*v = '\0';
670 		return (value);
671 next:
672 		while (*line++)
673 			;
674 	}
675 	return (NULL);
676 }
677 
678 quad_t
679 auth_check_expire(auth_session_t *as)
680 {
681 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
682 		as->state &= ~AUTH_ALLOW;
683 		as->state |= AUTH_EXPIRED;	/* XXX */
684 		return (-1);
685 	}
686 
687 	if (as->pwd == NULL)
688 		return (0);
689 
690 	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
691 		if (as->now.tv_sec == 0)
692 			gettimeofday(&as->now, (struct timezone *)NULL);
693 		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
694 			as->state &= ~AUTH_ALLOW;
695 			as->state |= AUTH_EXPIRED;
696 		}
697 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
698 			return (-1);
699 		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
700 	}
701 	return (0);
702 }
703 
704 quad_t
705 auth_check_change(auth_session_t *as)
706 {
707 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
708 		as->state &= ~AUTH_ALLOW;
709 		as->state |= AUTH_PWEXPIRED;	/* XXX */
710 		return (-1);
711 	}
712 
713 	if (as->pwd == NULL)
714 		return (0);
715 
716 	if (as->pwd && (quad_t)as->pwd->pw_change) {
717 		if (as->now.tv_sec == 0)
718 			gettimeofday(&as->now, (struct timezone *)NULL);
719 		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
720 			as->state &= ~AUTH_ALLOW;
721 			as->state |= AUTH_PWEXPIRED;
722 		}
723 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
724 			return (-1);
725 		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
726 	}
727 	return (0);
728 }
729 
730 /*
731  * The down and dirty call to the login script
732  * okay contains the default return value, typically 0 but
733  * is AUTH_OKAY for approval like scripts.
734  *
735  * Internally additional trailing arguments can be read from as->ap
736  * Options will be placed be placed just after the first argument
737  * (not including path).
738  *
739  * Any data will sent (and freed) to the script
740  */
741 int
742 auth_call(auth_session_t *as, char *path, ...)
743 {
744 	char *line;
745 	struct authdata *data;
746 	struct authopts *opt;
747 	pid_t pid;
748 	int status;
749 	int okay;
750 	int pfd[2];
751 	int argc;
752 	char *argv[64];		/* 64 args should more than enough */
753 #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
754 
755 	va_start(as->ap0, path);
756 
757 	argc = 0;
758 	if ((argv[argc] = _auth_next_arg(as)) != NULL)
759 		++argc;
760 
761 	for (opt = as->optlist; opt != NULL; opt = opt->next) {
762 		if (argc < Nargc - 2) {
763 			argv[argc++] = "-v";
764 			argv[argc++] = opt->opt;
765 		} else {
766 			syslog(LOG_ERR, "too many authentication options");
767 			goto fail;
768 		}
769 	}
770 	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
771 		++argc;
772 
773 	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
774 		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
775 			va_end(as->ap0);
776 			memset(&(as->ap0), 0, sizeof(as->ap0));
777 		}
778 		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
779 			va_end(as->ap);
780 			memset(&(as->ap), 0, sizeof(as->ap));
781 		}
782 		syslog(LOG_ERR, "too many arguments");
783 		goto fail;
784 	}
785 
786 	argv[argc] = NULL;
787 
788 	if (secure_path(path) < 0) {
789 		syslog(LOG_ERR, "%s: path not secure", path);
790 		_warnx("invalid script: %s", path);
791 		goto fail;
792 	}
793 
794 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
795 		syslog(LOG_ERR, "unable to create backchannel %m");
796 		_warnx("internal resource failure");
797 		goto fail;
798 	}
799 
800 	switch (pid = fork()) {
801 	case -1:
802 		close(pfd[0]);
803 		close(pfd[1]);
804 		syslog(LOG_ERR, "%s: %m", path);
805 		_warnx("internal resource failure");
806 		goto fail;
807 	case 0:
808 #define	COMM_FD	3
809 		close(pfd[0]);
810 		if (pfd[1] != COMM_FD) {
811 			if (dup2(pfd[1], COMM_FD) < 0)
812 				err(1, "dup of backchannel");
813 			close(pfd[1]);
814 		}
815 
816 		for (status = getdtablesize() - 1; status > COMM_FD; status--)
817 			close(status);
818 
819 		execve(path, argv, auth_environ);
820 		syslog(LOG_ERR, "%s: %m", path);
821 		err(1, "%s", path);
822 	default:
823 		close(pfd[1]);
824 		while ((data = as->data) != NULL) {
825 			as->data = data->next;
826 			if (data->len > 0) {
827 				write(pfd[0], data->ptr, data->len);
828 				memset(data->ptr, 0, data->len);
829 			}
830 			free(data);
831 		}
832 		as->index = 0;
833 		_auth_spool(as, pfd[0]);
834 		close(pfd[0]);
835 		if (waitpid(pid, &status, 0) < 0) {
836 			syslog(LOG_ERR, "%s: waitpid: %m", path);
837 			_warnx("internal failure");
838 			goto fail;
839 		}
840 
841 		if (!WIFEXITED(status))
842 			goto fail;
843 	}
844 
845 	/*
846 	 * Now scan the spooled data
847 	 * It is easier to wait for all the data before starting
848 	 * to scan it.
849 	 */
850     	for (line = as->spool; line < as->spool + as->index;) {
851 		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
852 			line += sizeof(BI_REJECT) - 1;
853 			if (!*line || *line == ' ' || *line == '\t') {
854 				while (*line == ' ' || *line == '\t')
855 					++line;
856 				if (!strcasecmp(line, "silent")) {
857 					as->state = AUTH_SILENT;
858 					break;
859 				}
860 				if (!strcasecmp(line, "challenge")) {
861 					as->state  = AUTH_CHALLENGE;
862 					break;
863 				}
864 				if (!strcasecmp(line, "expired")) {
865 					as->state  = AUTH_EXPIRED;
866 					break;
867 				}
868 				if (!strcasecmp(line, "pwexpired")) {
869 					as->state  = AUTH_PWEXPIRED;
870 					break;
871 				}
872 			}
873 			break;
874 		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
875 			line += sizeof(BI_AUTH) - 1;
876 			if (!*line || *line == ' ' || *line == '\t') {
877 				while (*line == ' ' || *line == '\t')
878 					++line;
879 				if (*line == '\0')
880 					as->state |= AUTH_OKAY;
881 				else if (!strcasecmp(line, "root"))
882 					as->state |= AUTH_ROOTOKAY;
883 				else if (!strcasecmp(line, "secure"))
884 					as->state |= AUTH_SECURE;
885 			}
886 		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
887 			line += sizeof(BI_REMOVE) - 1;
888 			while (*line == ' ' || *line == '\t')
889 				++line;
890 			if (*line)
891 				_add_rmlist(as, line);
892 		}
893 		while (*line++)
894 			;
895 	}
896 
897 	if (WEXITSTATUS(status))
898 		as->state &= ~AUTH_ALLOW;
899 
900 	okay = as->state & AUTH_ALLOW;
901 
902 	if (!okay)
903 		auth_clrenv(as);
904 
905 	if (0) {
906 fail:
907 		auth_clrenv(as);
908 		as->state = 0;
909 		okay = -1;
910 	}
911 
912 	while ((data = as->data) != NULL) {
913 		as->data = data->next;
914 		free(data);
915 	}
916 
917 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
918 		va_end(as->ap0);
919 		memset(&(as->ap0), 0, sizeof(as->ap0));
920 	}
921 
922 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
923 		va_end(as->ap);
924 		memset(&(as->ap), 0, sizeof(as->ap));
925 	}
926 	return (okay);
927 }
928 
929 static void
930 _auth_spool(auth_session_t *as, int fd)
931 {
932 	int r;
933 	char *b;
934 
935 	while (as->index < sizeof(as->spool) - 1) {
936 		r = read(fd, as->spool + as->index,
937 		    sizeof(as->spool) - as->index);
938 		if (r <= 0) {
939 			as->spool[as->index] = '\0';
940 			return;
941 		}
942 		b = as->spool + as->index;
943 		as->index += r;
944 		/*
945 		 * Go ahead and convert newlines into NULs to allow
946 		 * easy scanning of the file.
947 		 */
948 		while(r-- > 0)
949 			if (*b++ == '\n')
950 				b[-1] = '\0';
951 	}
952 
953 	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
954 	errx(1, "System error in authentication program");
955 }
956 
957 static void
958 _add_rmlist(auth_session_t *as, char *file)
959 {
960 	struct rmfiles *rm;
961 	int i = strlen(file) + 1;
962 
963 	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
964 		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
965 		return;
966 	}
967 	rm->file = (char *)(rm + 1);
968 	rm->next = as->rmlist;
969 	strcpy(rm->file, file);
970 	as->rmlist = rm;
971 }
972 
973 static char *
974 _auth_next_arg(auth_session_t *as)
975 {
976 	char *arg;
977 
978 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
979 		if ((arg = va_arg(as->ap0, char *)) != NULL)
980 			return (arg);
981 		va_end(as->ap0);
982 		memset(&(as->ap0), 0, sizeof(as->ap0));
983 	}
984 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
985 		if ((arg = va_arg(as->ap, char *)) != NULL)
986 			return (arg);
987 		va_end(as->ap);
988 		memset(&(as->ap), 0, sizeof(as->ap));
989 	}
990 	return (NULL);
991 }
992