1 /*	$OpenBSD: auth_subr.c,v 1.30 2004/12/02 20:38:36 millert 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 #include <sys/param.h>
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 <paths.h>
63 #include <pwd.h>
64 #include <stdarg.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <syslog.h>
69 #include <unistd.h>
70 
71 #include <login_cap.h>
72 
73 #define	MAXSPOOLSIZE	(8*1024)	/* Spool up to 8K of back info */
74 
75 struct rmfiles {
76 	struct rmfiles	*next;
77 	char		*file;
78 };
79 
80 struct authopts {
81 	struct authopts	*next;
82 	char		*opt;
83 };
84 
85 struct authdata {
86 	struct	authdata *next;
87 	void	*ptr;
88 	size_t	len;
89 };
90 
91 struct auth_session_t {
92 	char	*name;			/* name of use being authenticated */
93 	char	*style;			/* style of authentication used */
94 	char	*class;			/* class of user */
95 	char	*service;		/* type of service being performed */
96 	char	*challenge;		/* last challenge issued */
97 	int	flags;			/* see below */
98 	struct	passwd *pwd;		/* password entry for user */
99 	struct	timeval now;		/* time of authentication */
100 
101 	int	state;			/* authenticated state */
102 
103 	struct	rmfiles *rmlist;	/* list of files to remove on failure */
104 	struct	authopts *optlist;	/* list of options to scripts */
105 	struct	authdata *data;		/* additional data to send to scripts */
106 
107 	char	spool[MAXSPOOLSIZE];	/* data returned from login script */
108 	int	index;			/* how much returned thus far */
109 
110 	int	fd;			/* connection to authenticator */
111 
112 	va_list	ap0;			/* argument list to auth_call */
113 	va_list	ap;			/* additional arguments to auth_call */
114 };
115 
116 /*
117  * Internal flags
118  */
119 #define	AF_INTERACTIVE		0x0001	/* This is an interactive session */
120 
121 /*
122  * We cannot include bsd_auth.h until we define the above structures
123  */
124 #include <bsd_auth.h>
125 
126 /*
127  * Internally used functions
128  */
129 static void _add_rmlist(auth_session_t *, char *);
130 static void _auth_spool(auth_session_t *, int);
131 static void _recv_fd(auth_session_t *, int);
132 static char *_auth_next_arg(auth_session_t *);
133 /*
134  * Set up a known environment for all authentication scripts.
135  */
136 static char *auth_environ[] = {
137 	"PATH=" _PATH_DEFPATH,
138 	"SHELL=" _PATH_BSHELL,
139 	NULL,
140 };
141 
142 static char defservice[] = LOGIN_DEFSERVICE;
143 
144 static va_list nilap;
145 
146 /*
147  * Quick one liners that only exist to keep auth_session_t opaque
148  */
auth_setstate(auth_session_t * as,int s)149 void	auth_setstate(auth_session_t *as, int s){ as->state = s; }
auth_set_va_list(auth_session_t * as,va_list ap)150 void	auth_set_va_list(auth_session_t *as, va_list ap) {
151 #if defined(__GNUC__) && __GNUC__ >= 3
152 	va_copy(as->ap, ap);
153 #else
154 	as->ap = ap;
155 #endif
156 }
auth_getstate(auth_session_t * as)157 int	auth_getstate(auth_session_t *as)	{ return (as->state); }
auth_getpwd(auth_session_t * as)158 struct passwd *auth_getpwd(auth_session_t *as)	{ return (as->pwd); }
159 
160 /*
161  * Open a new BSD Authentication session with the default service
162  * (which can be changed later).
163  */
164 auth_session_t *
auth_open(void)165 auth_open(void)
166 {
167 	auth_session_t *as;
168 
169 	if ((as = malloc(sizeof(auth_session_t))) != NULL) {
170 		memset(as, 0, sizeof(*as));
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
auth_clean(auth_session_t * as)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
auth_close(auth_session_t * as)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 *
auth_challenge(auth_session_t * as)305 auth_challenge(auth_session_t *as)
306 {
307 	char path[MAXPATHLEN];
308 
309 	if (as == NULL || as->style == NULL || as->name == NULL)
310 		return (NULL);
311 
312 	as->state = 0;
313 
314 	if (as->challenge) {
315 		free(as->challenge);
316 		as->challenge = NULL;
317 	}
318 
319 	snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
320 	auth_call(as, path, as->style, "-s", "challenge", as->name,
321 	    as->class, (char *)NULL);
322 	if (as->state & AUTH_CHALLENGE)
323 		as->challenge = auth_getvalue(as, "challenge");
324 	as->state = 0;
325 	as->index = 0;	/* toss our data */
326 	return (as->challenge);
327 }
328 
329 /*
330  * Set/unset the requested environment variables.
331  * Mark the variables as set so they will not be set a second time.
332  * XXX - should provide a way to detect setenv() failure.
333  */
334 void
auth_setenv(auth_session_t * as)335 auth_setenv(auth_session_t *as)
336 {
337 	char *line, *name;
338 
339 	/*
340 	 * Set any environment variables we were asked for
341 	 */
342     	for (line = as->spool; line < as->spool + as->index;) {
343 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
344 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
345 				/* only do it once! */
346 				line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
347 				line += sizeof(BI_SETENV) - 1;
348 				for (name = line; isblank(*name); ++name)
349 					;
350 				for (line = name; *line && !isblank(*line);
351 				    ++line)
352 					;
353 				if (*line)
354 					*line++ = '\0';
355 				for (; isblank(*line); ++line)
356 					;
357 				if (*line != '\0' && setenv(name, line, 1))
358 					_warn("setenv(%s, %s)", name, line);
359 			}
360 		} else
361 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
362 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
363 				/* only do it once! */
364 				line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
365 				line += sizeof(BI_UNSETENV) - 1;
366 				for (name = line; isblank(*name); ++name)
367 					;
368 				for (line = name; *line && !isblank(*line);
369 				    ++line)
370 					;
371 				if (*line)
372 					*line++ = '\0';
373 				unsetenv(name);
374 			}
375 		}
376 		while (*line++)
377 			;
378 	}
379 }
380 
381 /*
382  * Clear out any requested environment variables.
383  */
384 void
auth_clrenv(auth_session_t * as)385 auth_clrenv(auth_session_t *as)
386 {
387 	char *line;
388 
389     	for (line = as->spool; line < as->spool + as->index;) {
390 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
391 			if (isblank(line[sizeof(BI_SETENV) - 1])) {
392 				line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
393 			}
394 		} else
395 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
396 			if (isblank(line[sizeof(BI_UNSETENV) - 1])) {
397 				line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
398 			}
399 		}
400 		while (*line++)
401 			;
402 	}
403 }
404 
405 char *
auth_getitem(auth_session_t * as,auth_item_t item)406 auth_getitem(auth_session_t *as, auth_item_t item)
407 {
408 	if (as != NULL) {
409 		switch (item) {
410 		case AUTHV_CHALLENGE:
411 			return (as->challenge);
412 		case AUTHV_CLASS:
413 			return (as->class);
414 		case AUTHV_NAME:
415 			return (as->name);
416 		case AUTHV_SERVICE:
417 			return (as->service ? as->service : defservice);
418 		case AUTHV_STYLE:
419 			return (as->style);
420 		case AUTHV_INTERACTIVE:
421 			return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
422 		default:
423 			break;
424 		}
425 	}
426 	return (NULL);
427 }
428 
429 int
auth_setitem(auth_session_t * as,auth_item_t item,char * value)430 auth_setitem(auth_session_t *as, auth_item_t item, char *value)
431 {
432 	if (as == NULL) {
433 		errno = EINVAL;
434 		return (-1);
435 	}
436 
437 	switch (item) {
438 	case AUTHV_ALL:
439 		if (value != NULL) {
440 			errno = EINVAL;
441 			return (-1);
442 		}
443 		auth_setitem(as, AUTHV_CHALLENGE, NULL);
444 		auth_setitem(as, AUTHV_CLASS, NULL);
445 		auth_setitem(as, AUTHV_NAME, NULL);
446 		auth_setitem(as, AUTHV_SERVICE, NULL);
447 		auth_setitem(as, AUTHV_STYLE, NULL);
448 		auth_setitem(as, AUTHV_INTERACTIVE, NULL);
449 		return (0);
450 
451 	case AUTHV_CHALLENGE:
452 		if (value == as->challenge)
453 			return (0);
454 		if (value != NULL && (value = strdup(value)) == NULL)
455 			return (-1);
456 		if (as->challenge)
457 			free(as->challenge);
458 		as->challenge = value;
459 		return (0);
460 
461 	case AUTHV_CLASS:
462 		if (value == as->class)
463 			return (0);
464 		if (value != NULL && (value = strdup(value)) == NULL)
465 			return (-1);
466 		if (as->class)
467 			free(as->class);
468 		as->class = value;
469 		return (0);
470 
471 	case AUTHV_NAME:
472 		if (value == as->name)
473 			return (0);
474 		if (value != NULL && (value = strdup(value)) == NULL)
475 			return (-1);
476 		if (as->name)
477 			free(as->name);
478 		as->name = value;
479 		return (0);
480 
481 	case AUTHV_SERVICE:
482 		if (value == as->service)
483 			return (0);
484 		if (value == NULL || strcmp(value, defservice) == 0)
485 			value = defservice;
486 		else if ((value = strdup(value)) == NULL)
487 			return (-1);
488 		if (as->service && as->service != defservice)
489 			free(as->service);
490 		as->service = value;
491 		return (0);
492 
493 	case AUTHV_STYLE:
494 		if (value == as->style)
495 			return (0);
496 		if (value == NULL || strchr(value, '/') != NULL ||
497 		    (value = strdup(value)) == NULL)
498 			return (-1);
499 		if (as->style)
500 			free(as->style);
501 		as->style = value;
502 		return (0);
503 
504 	case AUTHV_INTERACTIVE:
505 		if (value == NULL)
506 			as->flags &= ~AF_INTERACTIVE;
507 		else
508 			as->flags |= ~AF_INTERACTIVE;
509 		return (0);
510 
511 	default:
512 		errno = EINVAL;
513 		return (-1);
514 	}
515 }
516 
517 int
auth_setoption(auth_session_t * as,char * n,char * v)518 auth_setoption(auth_session_t *as, char *n, char *v)
519 {
520 	struct authopts *opt;
521 	int i = strlen(n) + strlen(v) + 2;
522 
523 	if ((opt = malloc(sizeof(*opt) + i)) == NULL)
524 		return (-1);
525 
526 	opt->opt = (char *)(opt + 1);
527 
528 	snprintf(opt->opt, i, "%s=%s", n, v);
529 	opt->next = as->optlist;
530 	as->optlist = opt;
531 	return(0);
532 }
533 
534 void
auth_clroptions(auth_session_t * as)535 auth_clroptions(auth_session_t *as)
536 {
537 	struct authopts *opt;
538 
539 	while ((opt = as->optlist) != NULL) {
540 		as->optlist = opt->next;
541 		free(opt);
542 	}
543 }
544 
545 void
auth_clroption(auth_session_t * as,char * option)546 auth_clroption(auth_session_t *as, char *option)
547 {
548 	struct authopts *opt, *oopt;
549 	int len;
550 
551 	len = strlen(option);
552 
553 	if ((opt = as->optlist) == NULL)
554 		return;
555 
556 	if (strncmp(opt->opt, option, len) == 0 &&
557 	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
558 		as->optlist = opt->next;
559 		free(opt);
560 		return;
561 	}
562 
563 	while ((oopt = opt->next) != NULL) {
564 		if (strncmp(oopt->opt, option, len) == 0 &&
565 		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
566 			opt->next = oopt->next;
567 			free(oopt);
568 			return;
569 		}
570 		opt = oopt;
571 	}
572 }
573 
574 int
auth_setdata(auth_session_t * as,void * ptr,size_t len)575 auth_setdata(auth_session_t *as, void *ptr, size_t len)
576 {
577 	struct authdata *data, *dp;
578 
579 	if (len <= 0)
580 		return (0);
581 
582 	if ((data = malloc(sizeof(*data) + len)) == NULL)
583 		return (-1);
584 
585 	data->next = NULL;
586 	data->len = len;
587 	data->ptr = data + 1;
588 	memcpy(data->ptr, ptr, len);
589 
590 	if (as->data == NULL)
591 		as->data = data;
592 	else {
593 		for (dp = as->data; dp->next != NULL; dp = dp->next)
594 			;
595 		dp->next = data;
596 	}
597 	return (0);
598 }
599 
600 int
auth_setpwd(auth_session_t * as,struct passwd * pwd)601 auth_setpwd(auth_session_t *as, struct passwd *pwd)
602 {
603 	char *instance;
604 
605 	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
606 		return (-1);		/* true failure */
607 
608 	if (pwd == NULL) {
609 		/*
610 		 * If we were not passed in a pwd structure we need to
611 		 * go find one for ourself.  Always look up the username
612 		 * (if it is defined) in the passwd database to see if there
613 		 * is an entry for the user.  If not, either use the current
614 		 * entry or simply return a 1 which implies there is
615 		 * no user by that name here.  This is not a failure, just
616 		 * a point of information.
617 		 */
618 		if (as->name == NULL)
619 			return (0);
620 		if ((pwd = getpwnam(as->name)) == NULL) {
621 			instance = strpbrk(as->name, "./");
622 			if (instance++ == NULL)
623 				return (as->pwd ? 0 : 1);
624 			if (strcmp(instance, "root") == 0)
625 				pwd = getpwnam(instance);
626 			if (pwd == NULL)
627 				return (as->pwd ? 0 : 1);
628 		}
629 	}
630 	if ((pwd = pw_dup(pwd)) == NULL)
631 		return (-1);		/* true failure */
632 	if (as->pwd) {
633 		memset(as->pwd->pw_passwd, 0, strlen(as->pwd->pw_passwd));
634 		free(as->pwd);
635 	}
636 	as->pwd = pwd;
637 	return (0);
638 }
639 
640 char *
auth_getvalue(auth_session_t * as,char * what)641 auth_getvalue(auth_session_t *as, char *what)
642 {
643 	char *line, *v, *value;
644 	int n, len;
645 
646 	len = strlen(what);
647 
648     	for (line = as->spool; line < as->spool + as->index;) {
649 		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
650 			goto next;
651 		line += sizeof(BI_VALUE) - 1;
652 
653 		if (!isblank(*line))
654 			goto next;
655 
656 		while (isblank(*++line))
657 			;
658 
659 		if (strncmp(line, what, len) != 0 ||
660 		    !isblank(line[len]))
661 			goto next;
662 		line += len;
663 		while (isblank(*++line))
664 			;
665 		value = strdup(line);
666 		if (value == NULL)
667 			return (NULL);
668 
669 		/*
670 		 * XXX - There should be a more standardized
671 		 * routine for doing this sort of thing.
672 		 */
673 		for (line = v = value; *line; ++line) {
674 			if (*line == '\\') {
675 				switch (*++line) {
676 				case 'r':
677 					*v++ = '\r';
678 					break;
679 				case 'n':
680 					*v++ = '\n';
681 					break;
682 				case 't':
683 					*v++ = '\t';
684 					break;
685 				case '0': case '1': case '2':
686 				case '3': case '4': case '5':
687 				case '6': case '7':
688 					n = *line - '0';
689 					if (isdigit(line[1])) {
690 						++line;
691 						n <<= 3;
692 						n |= *line-'0';
693 					}
694 					if (isdigit(line[1])) {
695 						++line;
696 						n <<= 3;
697 						n |= *line-'0';
698 					}
699 					break;
700 				default:
701 					*v++ = *line;
702 					break;
703 				}
704 			} else
705 				*v++ = *line;
706 		}
707 		*v = '\0';
708 		return (value);
709 next:
710 		while (*line++)
711 			;
712 	}
713 	return (NULL);
714 }
715 
716 quad_t
auth_check_expire(auth_session_t * as)717 auth_check_expire(auth_session_t *as)
718 {
719 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
720 		as->state &= ~AUTH_ALLOW;
721 		as->state |= AUTH_EXPIRED;	/* XXX */
722 		return (-1);
723 	}
724 
725 	if (as->pwd == NULL)
726 		return (0);
727 
728 	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
729 		if (as->now.tv_sec == 0)
730 			gettimeofday(&as->now, (struct timezone *)NULL);
731 		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
732 			as->state &= ~AUTH_ALLOW;
733 			as->state |= AUTH_EXPIRED;
734 		}
735 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
736 			return (-1);
737 		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
738 	}
739 	return (0);
740 }
741 
742 quad_t
auth_check_change(auth_session_t * as)743 auth_check_change(auth_session_t *as)
744 {
745 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
746 		as->state &= ~AUTH_ALLOW;
747 		as->state |= AUTH_PWEXPIRED;	/* XXX */
748 		return (-1);
749 	}
750 
751 	if (as->pwd == NULL)
752 		return (0);
753 
754 	if (as->pwd && (quad_t)as->pwd->pw_change) {
755 		if (as->now.tv_sec == 0)
756 			gettimeofday(&as->now, (struct timezone *)NULL);
757 		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
758 			as->state &= ~AUTH_ALLOW;
759 			as->state |= AUTH_PWEXPIRED;
760 		}
761 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
762 			return (-1);
763 		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
764 	}
765 	return (0);
766 }
767 
768 /*
769  * The down and dirty call to the login script
770  * okay contains the default return value, typically 0 but
771  * is AUTH_OKAY for approval like scripts.
772  *
773  * Internally additional trailing arguments can be read from as->ap
774  * Options will be placed just after the first argument (not including path).
775  *
776  * Any data will be sent to (and freed by) the script
777  */
778 int
auth_call(auth_session_t * as,char * path,...)779 auth_call(auth_session_t *as, char *path, ...)
780 {
781 	char *line;
782 	struct authdata *data;
783 	struct authopts *opt;
784 	pid_t pid;
785 	int status;
786 	int okay;
787 	int pfd[2];
788 	int argc;
789 	char *argv[64];		/* 64 args should be more than enough */
790 #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
791 
792 	va_start(as->ap0, path);
793 
794 	argc = 0;
795 	if ((argv[argc] = _auth_next_arg(as)) != NULL)
796 		++argc;
797 
798 	if (as->fd != -1) {
799 		argv[argc++] = "-v";
800 		argv[argc++] = "fd=4";		/* AUTH_FD, see below */
801 	}
802 	for (opt = as->optlist; opt != NULL; opt = opt->next) {
803 		if (argc < Nargc - 2) {
804 			argv[argc++] = "-v";
805 			argv[argc++] = opt->opt;
806 		} else {
807 			syslog(LOG_ERR, "too many authentication options");
808 			goto fail;
809 		}
810 	}
811 	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
812 		++argc;
813 
814 	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
815 		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
816 			va_end(as->ap0);
817 			memset(&(as->ap0), 0, sizeof(as->ap0));
818 		}
819 		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
820 			va_end(as->ap);
821 			memset(&(as->ap), 0, sizeof(as->ap));
822 		}
823 		syslog(LOG_ERR, "too many arguments");
824 		goto fail;
825 	}
826 
827 	argv[argc] = NULL;
828 
829 	if (secure_path(path) < 0) {
830 		syslog(LOG_ERR, "%s: path not secure", path);
831 		_warnx("invalid script: %s", path);
832 		goto fail;
833 	}
834 
835 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
836 		syslog(LOG_ERR, "unable to create backchannel %m");
837 		_warnx("internal resource failure");
838 		goto fail;
839 	}
840 
841 	switch (pid = fork()) {
842 	case -1:
843 		syslog(LOG_ERR, "%s: %m", path);
844 		_warnx("internal resource failure");
845 		close(pfd[0]);
846 		close(pfd[1]);
847 		goto fail;
848 	case 0:
849 #define	COMM_FD	3
850 #define	AUTH_FD	4
851 		if (dup2(pfd[1], COMM_FD) < 0)
852 			err(1, "dup of backchannel");
853 		if (as->fd != -1) {
854 			if (dup2(as->fd, AUTH_FD) < 0)
855 				err(1, "dup of auth fd");
856 			closefrom(AUTH_FD + 1);
857 		} else
858 			closefrom(COMM_FD + 1);
859 		execve(path, argv, auth_environ);
860 		syslog(LOG_ERR, "%s: %m", path);
861 		err(1, "%s", path);
862 	default:
863 		close(pfd[1]);
864 		if (as->fd != -1) {
865 			close(as->fd);		/* so child has only ref */
866 			as->fd = -1;
867 		}
868 		while ((data = as->data) != NULL) {
869 			as->data = data->next;
870 			if (data->len > 0) {
871 				write(pfd[0], data->ptr, data->len);
872 				memset(data->ptr, 0, data->len);
873 			}
874 			free(data);
875 		}
876 		as->index = 0;
877 		_auth_spool(as, pfd[0]);
878 		close(pfd[0]);
879 		status = 0;
880 		do {
881 			pid = waitpid(pid, &status, 0);
882 		} while (pid < 0 && errno == EINTR);
883 		if (pid < 0) {
884 			if (errno != ECHILD) {
885 				syslog(LOG_ERR, "%s: waitpid: %m", path);
886 				_warnx("internal failure");
887 				goto fail;
888 			}
889 		} else if (!WIFEXITED(status))
890 			goto fail;
891 	}
892 
893 	/*
894 	 * Now scan the spooled data
895 	 * It is easier to wait for all the data before starting
896 	 * to scan it.
897 	 */
898     	for (line = as->spool; line < as->spool + as->index;) {
899 		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
900 			line += sizeof(BI_REJECT) - 1;
901 			if (!*line || *line == ' ' || *line == '\t') {
902 				while (*line == ' ' || *line == '\t')
903 					++line;
904 				if (!strcasecmp(line, "silent")) {
905 					as->state = AUTH_SILENT;
906 					break;
907 				}
908 				if (!strcasecmp(line, "challenge")) {
909 					as->state  = AUTH_CHALLENGE;
910 					break;
911 				}
912 				if (!strcasecmp(line, "expired")) {
913 					as->state  = AUTH_EXPIRED;
914 					break;
915 				}
916 				if (!strcasecmp(line, "pwexpired")) {
917 					as->state  = AUTH_PWEXPIRED;
918 					break;
919 				}
920 			}
921 			break;
922 		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
923 			line += sizeof(BI_AUTH) - 1;
924 			if (!*line || *line == ' ' || *line == '\t') {
925 				while (*line == ' ' || *line == '\t')
926 					++line;
927 				if (*line == '\0')
928 					as->state |= AUTH_OKAY;
929 				else if (!strcasecmp(line, "root"))
930 					as->state |= AUTH_ROOTOKAY;
931 				else if (!strcasecmp(line, "secure"))
932 					as->state |= AUTH_SECURE;
933 			}
934 		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
935 			line += sizeof(BI_REMOVE) - 1;
936 			while (*line == ' ' || *line == '\t')
937 				++line;
938 			if (*line)
939 				_add_rmlist(as, line);
940 		}
941 		while (*line++)
942 			;
943 	}
944 
945 	if (WEXITSTATUS(status))
946 		as->state &= ~AUTH_ALLOW;
947 
948 	okay = as->state & AUTH_ALLOW;
949 
950 	if (!okay)
951 		auth_clrenv(as);
952 
953 	if (0) {
954 fail:
955 		auth_clrenv(as);
956 		as->state = 0;
957 		okay = -1;
958 	}
959 
960 	while ((data = as->data) != NULL) {
961 		as->data = data->next;
962 		free(data);
963 	}
964 
965 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
966 		va_end(as->ap0);
967 		memset(&(as->ap0), 0, sizeof(as->ap0));
968 	}
969 
970 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
971 		va_end(as->ap);
972 		memset(&(as->ap), 0, sizeof(as->ap));
973 	}
974 	return (okay);
975 }
976 
977 static void
_recv_fd(auth_session_t * as,int fd)978 _recv_fd(auth_session_t *as, int fd)
979 {
980 	struct msghdr msg;
981 	struct cmsghdr *cmp;
982 	char cmsgbuf[CMSG_SPACE(sizeof(int))];
983 
984 	memset(&msg, 0, sizeof(msg));
985 	msg.msg_control = cmsgbuf;
986 	msg.msg_controllen = sizeof(cmsgbuf);
987 	if (recvmsg(fd, &msg, 0) < 0)
988 		syslog(LOG_ERR, "recvmsg: %m");
989 	else if (msg.msg_flags & MSG_TRUNC)
990 		syslog(LOG_ERR, "message truncated");
991 	else if (msg.msg_flags & MSG_CTRUNC)
992 		syslog(LOG_ERR, "control message truncated");
993 	else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
994 		syslog(LOG_ERR, "missing control message");
995 	else {
996 		if (cmp->cmsg_level != SOL_SOCKET)
997 			syslog(LOG_ERR, "unexpected cmsg_level %d",
998 			    cmp->cmsg_level);
999 		else if (cmp->cmsg_type != SCM_RIGHTS)
1000 			syslog(LOG_ERR, "unexpected cmsg_type %d",
1001 			    cmp->cmsg_type);
1002 		else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
1003 			syslog(LOG_ERR, "bad cmsg_len %d",
1004 			    cmp->cmsg_len);
1005 		else {
1006 			if (as->fd != -1)
1007 				close(as->fd);
1008 			as->fd = *(int *)CMSG_DATA(cmp);
1009 		}
1010 	}
1011 }
1012 
1013 static void
_auth_spool(auth_session_t * as,int fd)1014 _auth_spool(auth_session_t *as, int fd)
1015 {
1016 	ssize_t r;
1017 	char *b, *s;
1018 
1019 	for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
1020 		r = read(fd, as->spool + as->index,
1021 		    sizeof(as->spool) - as->index);
1022 		if (r <= 0) {
1023 			as->spool[as->index] = '\0';
1024 			return;
1025 		}
1026 		b = as->spool + as->index;
1027 		as->index += r;
1028 		/*
1029 		 * Convert newlines into NULs to allow easy scanning of the
1030 		 * file and receive an fd if there is a BI_FDPASS message.
1031 		 * XXX - checking for BI_FDPASS here is annoying but
1032 		 *       we need to avoid the read() slurping in control data.
1033 		 */
1034 		while (r-- > 0) {
1035 			if (*b++ == '\n') {
1036 				b[-1] = '\0';
1037 				if (strcasecmp(s, BI_FDPASS) == 0)
1038 					_recv_fd(as, fd);
1039 				s = b;
1040 			}
1041 		}
1042 	}
1043 
1044 	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
1045 	errx(1, "System error in authentication program");
1046 }
1047 
1048 static void
_add_rmlist(auth_session_t * as,char * file)1049 _add_rmlist(auth_session_t *as, char *file)
1050 {
1051 	struct rmfiles *rm;
1052 	int i = strlen(file) + 1;
1053 
1054 	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
1055 		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
1056 		return;
1057 	}
1058 	rm->file = (char *)(rm + 1);
1059 	rm->next = as->rmlist;
1060 	strlcpy(rm->file, file, i);
1061 	as->rmlist = rm;
1062 }
1063 
1064 static char *
_auth_next_arg(auth_session_t * as)1065 _auth_next_arg(auth_session_t *as)
1066 {
1067 	char *arg;
1068 
1069 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
1070 		if ((arg = va_arg(as->ap0, char *)) != NULL)
1071 			return (arg);
1072 		va_end(as->ap0);
1073 		memset(&(as->ap0), 0, sizeof(as->ap0));
1074 	}
1075 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1076 		if ((arg = va_arg(as->ap, char *)) != NULL)
1077 			return (arg);
1078 		va_end(as->ap);
1079 		memset(&(as->ap), 0, sizeof(as->ap));
1080 	}
1081 	return (NULL);
1082 }
1083