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