xref: /openbsd/lib/libc/gen/auth_subr.c (revision 036a3c97)
1 /*	$OpenBSD: auth_subr.c,v 1.56 2020/10/13 04:42:28 guenther Exp $	*/
2 
3 /*
4  * Copyright (c) 2000-2002,2004 Todd C. Miller <millert@openbsd.org>
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 * const 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  */
auth_setstate(auth_session_t * as,int s)150 void	auth_setstate(auth_session_t *as, int s){ as->state = s; }
auth_set_va_list(auth_session_t * as,va_list ap)151 void	auth_set_va_list(auth_session_t *as, va_list ap) { va_copy(as->ap, ap); }
auth_getstate(auth_session_t * as)152 int	auth_getstate(auth_session_t *as)	{ return (as->state); }
auth_getpwd(auth_session_t * as)153 struct passwd *auth_getpwd(auth_session_t *as)	{ return (as->pwd); }
154 DEF_WEAK(auth_setstate);
155 DEF_WEAK(auth_set_va_list);
156 DEF_WEAK(auth_getstate);
157 DEF_WEAK(auth_getpwd);
158 
159 /*
160  * Open a new BSD Authentication session with the default service
161  * (which can be changed later).
162  */
163 auth_session_t *
auth_open(void)164 auth_open(void)
165 {
166 	auth_session_t *as;
167 
168 	if ((as = calloc(1, sizeof(auth_session_t))) != NULL) {
169 		as->service = defservice;
170 		as->fd = -1;
171 	}
172 
173 	return (as);
174 }
175 DEF_WEAK(auth_open);
176 
177 /*
178  * Clean the specified BSD Authentication session.
179  */
180 void
auth_clean(auth_session_t * as)181 auth_clean(auth_session_t *as)
182 {
183 	struct rmfiles *rm;
184 	struct authdata *data;
185 
186 	as->state = 0;
187 
188 	auth_clrenv(as);
189 
190 	/*
191 	 * Clean out the rmlist and remove specified files
192 	 */
193 	while ((rm = as->rmlist) != NULL) {
194 		as->rmlist = rm->next;
195 		unlink(rm->file);
196 		free(rm);
197 	}
198 
199 	/*
200 	 * Clean out data
201 	 */
202 	while ((data = as->data) != NULL) {
203 		if (as->data->len)
204 			explicit_bzero(as->data->ptr, as->data->len);
205 		as->data = data->next;
206 		free(data);
207 	}
208 
209 	auth_setitem(as, AUTHV_ALL, NULL);
210 
211 	if (as->pwd != NULL) {
212 		explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
213 		free(as->pwd);
214 		as->pwd = NULL;
215 	}
216 
217 	if (as->fd != -1) {
218 		close(as->fd);
219 		as->fd = -1;
220 	}
221 }
222 DEF_WEAK(auth_clean);
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 			explicit_bzero(as->data->ptr, as->data->len);
272 		as->data = data->next;
273 		free(data);
274 	}
275 
276 	if (as->pwd != NULL) {
277 		explicit_bzero(as->pwd->pw_passwd, 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 	free(as->challenge);
288 	free(as->class);
289 	free(as->style);
290 	free(as->name);
291 
292 	free(as);
293 	return (s);
294 }
295 DEF_WEAK(auth_close);
296 
297 /*
298  * Request a challenge for the session.
299  * The name and style must have already been specified
300  */
301 char *
auth_challenge(auth_session_t * as)302 auth_challenge(auth_session_t *as)
303 {
304 	char path[PATH_MAX];
305 	int len;
306 
307 	if (as == NULL || as->style == NULL || as->name == NULL ||
308 	    !_auth_validuser(as->name))
309 		return (NULL);
310 
311 	len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
312 	if (len < 0 || len >= sizeof(path))
313 		return (NULL);
314 
315 	as->state = 0;
316 
317 	free(as->challenge);
318 	as->challenge = NULL;
319 
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 DEF_WEAK(auth_challenge);
329 
330 /*
331  * Set/unset the requested environment variables.
332  * Mark the variables as set so they will not be set a second time.
333  * XXX - should provide a way to detect setenv() failure.
334  */
335 void
auth_setenv(auth_session_t * as)336 auth_setenv(auth_session_t *as)
337 {
338 	char *line, *name;
339 
340 	/*
341 	 * Set any environment variables we were asked for
342 	 */
343     	for (line = as->spool; line < as->spool + as->index;) {
344 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
345 			if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
346 				/* only do it once! */
347 				line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
348 				line += sizeof(BI_SETENV) - 1;
349 				for (name = line;
350 				    isblank((unsigned char)*name); ++name)
351 					;
352 				for (line = name;
353 				    *line && !isblank((unsigned char)*line);
354 				    ++line)
355 					;
356 				if (*line)
357 					*line++ = '\0';
358 				for (; isblank((unsigned char)*line); ++line)
359 					;
360 				if (*line != '\0' && setenv(name, line, 1))
361 					warn("setenv(%s, %s)", name, line);
362 			}
363 		} else
364 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
365 			if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
366 				/* only do it once! */
367 				line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
368 				line += sizeof(BI_UNSETENV) - 1;
369 				for (name = line;
370 				    isblank((unsigned char)*name); ++name)
371 					;
372 				for (line = name;
373 				    *line && !isblank((unsigned char)*line);
374 				    ++line)
375 					;
376 				if (*line)
377 					*line++ = '\0';
378 				unsetenv(name);
379 			}
380 		}
381 		while (*line++)
382 			;
383 	}
384 }
385 DEF_WEAK(auth_setenv);
386 
387 /*
388  * Clear out any requested environment variables.
389  */
390 void
auth_clrenv(auth_session_t * as)391 auth_clrenv(auth_session_t *as)
392 {
393 	char *line;
394 
395 	for (line = as->spool; line < as->spool + as->index;) {
396 		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
397 			if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
398 				line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
399 			}
400 		} else
401 		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
402 			if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
403 				line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
404 			}
405 		}
406 		while (*line++)
407 			;
408 	}
409 }
410 DEF_WEAK(auth_clrenv);
411 
412 char *
auth_getitem(auth_session_t * as,auth_item_t item)413 auth_getitem(auth_session_t *as, auth_item_t item)
414 {
415 	if (as != NULL) {
416 		switch (item) {
417 		case AUTHV_CHALLENGE:
418 			return (as->challenge);
419 		case AUTHV_CLASS:
420 			return (as->class);
421 		case AUTHV_NAME:
422 			return (as->name);
423 		case AUTHV_SERVICE:
424 			return (as->service ? as->service : defservice);
425 		case AUTHV_STYLE:
426 			return (as->style);
427 		case AUTHV_INTERACTIVE:
428 			return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
429 		default:
430 			break;
431 		}
432 	}
433 	return (NULL);
434 }
435 DEF_WEAK(auth_getitem);
436 
437 int
auth_setitem(auth_session_t * as,auth_item_t item,char * value)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 		free(as->challenge);
465 		as->challenge = value;
466 		return (0);
467 
468 	case AUTHV_CLASS:
469 		if (value == as->class)
470 			return (0);
471 		if (value != NULL && (value = strdup(value)) == NULL)
472 			return (-1);
473 		free(as->class);
474 		as->class = value;
475 		return (0);
476 
477 	case AUTHV_NAME:
478 		if (value == as->name)
479 			return (0);
480 		if (value != NULL && !_auth_validuser(value)) {
481 			errno = EINVAL;
482 			return (-1);
483 		}
484 		if (value != NULL && (value = strdup(value)) == NULL)
485 			return (-1);
486 		free(as->name);
487 		as->name = value;
488 		return (0);
489 
490 	case AUTHV_SERVICE:
491 		if (value == as->service)
492 			return (0);
493 		if (value == NULL || strcmp(value, defservice) == 0)
494 			value = defservice;
495 		else if ((value = strdup(value)) == NULL)
496 			return (-1);
497 		if (as->service && as->service != defservice)
498 			free(as->service);
499 		as->service = value;
500 		return (0);
501 
502 	case AUTHV_STYLE:
503 		if (value == as->style)
504 			return (0);
505 		if (value == NULL || strchr(value, '/') != NULL ||
506 		    (value = strdup(value)) == NULL)
507 			return (-1);
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 DEF_WEAK(auth_setitem);
525 
526 int
auth_setoption(auth_session_t * as,char * n,char * v)527 auth_setoption(auth_session_t *as, char *n, char *v)
528 {
529 	struct authopts *opt;
530 	size_t len = strlen(n) + strlen(v) + 2;
531 	int ret;
532 
533 	if ((opt = malloc(sizeof(*opt) + len)) == NULL)
534 		return (-1);
535 
536 	opt->opt = (char *)(opt + 1);
537 
538 	ret = snprintf(opt->opt, len, "%s=%s", n, v);
539 	if (ret < 0 || ret >= len) {
540 		free(opt);
541 		errno = ENAMETOOLONG;
542 		return (-1);
543 	}
544 	opt->next = as->optlist;
545 	as->optlist = opt;
546 	return(0);
547 }
548 DEF_WEAK(auth_setoption);
549 
550 void
auth_clroptions(auth_session_t * as)551 auth_clroptions(auth_session_t *as)
552 {
553 	struct authopts *opt;
554 
555 	while ((opt = as->optlist) != NULL) {
556 		as->optlist = opt->next;
557 		free(opt);
558 	}
559 }
560 DEF_WEAK(auth_clroptions);
561 
562 void
auth_clroption(auth_session_t * as,char * option)563 auth_clroption(auth_session_t *as, char *option)
564 {
565 	struct authopts *opt, *oopt;
566 	size_t len;
567 
568 	len = strlen(option);
569 
570 	if ((opt = as->optlist) == NULL)
571 		return;
572 
573 	if (strncmp(opt->opt, option, len) == 0 &&
574 	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
575 		as->optlist = opt->next;
576 		free(opt);
577 		return;
578 	}
579 
580 	while ((oopt = opt->next) != NULL) {
581 		if (strncmp(oopt->opt, option, len) == 0 &&
582 		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
583 			opt->next = oopt->next;
584 			free(oopt);
585 			return;
586 		}
587 		opt = oopt;
588 	}
589 }
590 DEF_WEAK(auth_clroption);
591 
592 int
auth_setdata(auth_session_t * as,void * ptr,size_t len)593 auth_setdata(auth_session_t *as, void *ptr, size_t len)
594 {
595 	struct authdata *data, *dp;
596 
597 	if (len <= 0)
598 		return (0);
599 
600 	if ((data = malloc(sizeof(*data) + len)) == NULL)
601 		return (-1);
602 
603 	data->next = NULL;
604 	data->len = len;
605 	data->ptr = data + 1;
606 	memcpy(data->ptr, ptr, len);
607 
608 	if (as->data == NULL)
609 		as->data = data;
610 	else {
611 		for (dp = as->data; dp->next != NULL; dp = dp->next)
612 			;
613 		dp->next = data;
614 	}
615 	return (0);
616 }
617 DEF_WEAK(auth_setdata);
618 
619 int
auth_setpwd(auth_session_t * as,struct passwd * pwd)620 auth_setpwd(auth_session_t *as, struct passwd *pwd)
621 {
622 	struct passwd pwstore;
623 	char *instance, pwbuf[_PW_BUF_LEN];
624 
625 	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
626 		return (-1);		/* true failure */
627 
628 	if (pwd == NULL) {
629 		/*
630 		 * If we were not passed in a pwd structure we need to
631 		 * go find one for ourself.  Always look up the username
632 		 * (if it is defined) in the passwd database to see if there
633 		 * is an entry for the user.  If not, either use the current
634 		 * entry or simply return a 1 which implies there is
635 		 * no user by that name here.  This is not a failure, just
636 		 * a point of information.
637 		 */
638 		if (as->name == NULL)
639 			return (0);
640 		getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
641 		if (pwd == NULL) {
642 			instance = strchr(as->name, '/');
643 			if (instance == NULL)
644 				return (as->pwd ? 0 : 1);
645 			if (strcmp(instance, "/root") == 0) {
646 				getpwnam_r(instance + 1, &pwstore, pwbuf,
647 				    sizeof(pwbuf), &pwd);
648 			}
649 			if (pwd == NULL)
650 				return (as->pwd ? 0 : 1);
651 		}
652 	}
653 	if ((pwd = pw_dup(pwd)) == NULL)
654 		return (-1);		/* true failure */
655 	if (as->pwd) {
656 		explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
657 		free(as->pwd);
658 	}
659 	as->pwd = pwd;
660 	return (0);
661 }
662 DEF_WEAK(auth_setpwd);
663 
664 char *
auth_getvalue(auth_session_t * as,char * what)665 auth_getvalue(auth_session_t *as, char *what)
666 {
667 	char *line, *v, *value;
668 	int n, len;
669 
670 	len = strlen(what);
671 
672     	for (line = as->spool; line < as->spool + as->index;) {
673 		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
674 			goto next;
675 		line += sizeof(BI_VALUE) - 1;
676 
677 		if (!isblank((unsigned char)*line))
678 			goto next;
679 
680 		while (isblank((unsigned char)*++line))
681 			;
682 
683 		if (strncmp(line, what, len) != 0 ||
684 		    !isblank((unsigned char)line[len]))
685 			goto next;
686 		line += len;
687 		while (isblank((unsigned char)*++line))
688 			;
689 		value = strdup(line);
690 		if (value == NULL)
691 			return (NULL);
692 
693 		/*
694 		 * XXX - There should be a more standardized
695 		 * routine for doing this sort of thing.
696 		 */
697 		for (line = v = value; *line; ++line) {
698 			if (*line == '\\') {
699 				switch (*++line) {
700 				case 'r':
701 					*v++ = '\r';
702 					break;
703 				case 'n':
704 					*v++ = '\n';
705 					break;
706 				case 't':
707 					*v++ = '\t';
708 					break;
709 				case '0': case '1': case '2':
710 				case '3': case '4': case '5':
711 				case '6': case '7':
712 					n = *line - '0';
713 					if (isdigit((unsigned char)line[1])) {
714 						++line;
715 						n <<= 3;
716 						n |= *line-'0';
717 					}
718 					if (isdigit((unsigned char)line[1])) {
719 						++line;
720 						n <<= 3;
721 						n |= *line-'0';
722 					}
723 					break;
724 				default:
725 					*v++ = *line;
726 					break;
727 				}
728 			} else
729 				*v++ = *line;
730 		}
731 		*v = '\0';
732 		return (value);
733 next:
734 		while (*line++)
735 			;
736 	}
737 	return (NULL);
738 }
739 DEF_WEAK(auth_getvalue);
740 
741 quad_t
auth_check_expire(auth_session_t * as)742 auth_check_expire(auth_session_t *as)
743 {
744 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
745 		as->state &= ~AUTH_ALLOW;
746 		as->state |= AUTH_EXPIRED;	/* XXX */
747 		return (-1);
748 	}
749 
750 	if (as->pwd == NULL)
751 		return (0);
752 
753 	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
754 		if (as->now.tv_sec == 0)
755 			WRAP(gettimeofday)(&as->now, NULL);
756 		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
757 			as->state &= ~AUTH_ALLOW;
758 			as->state |= AUTH_EXPIRED;
759 		}
760 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
761 			return (-1);
762 		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
763 	}
764 	return (0);
765 }
766 DEF_WEAK(auth_check_expire);
767 
768 quad_t
auth_check_change(auth_session_t * as)769 auth_check_change(auth_session_t *as)
770 {
771 	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
772 		as->state &= ~AUTH_ALLOW;
773 		as->state |= AUTH_PWEXPIRED;	/* XXX */
774 		return (-1);
775 	}
776 
777 	if (as->pwd == NULL)
778 		return (0);
779 
780 	if (as->pwd && (quad_t)as->pwd->pw_change) {
781 		if (as->now.tv_sec == 0)
782 			WRAP(gettimeofday)(&as->now, NULL);
783 		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
784 			as->state &= ~AUTH_ALLOW;
785 			as->state |= AUTH_PWEXPIRED;
786 		}
787 		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
788 			return (-1);
789 		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
790 	}
791 	return (0);
792 }
793 DEF_WEAK(auth_check_change);
794 
795 /*
796  * The down and dirty call to the login script
797  * okay contains the default return value, typically 0 but
798  * is AUTH_OKAY for approval like scripts.
799  *
800  * Internally additional trailing arguments can be read from as->ap
801  * Options will be placed just after the first argument (not including path).
802  *
803  * Any data will be sent to (and freed by) the script
804  */
805 int
auth_call(auth_session_t * as,char * path,...)806 auth_call(auth_session_t *as, char *path, ...)
807 {
808 	char *line;
809 	struct authdata *data;
810 	struct authopts *opt;
811 	pid_t pid;
812 	int status;
813 	int okay;
814 	int pfd[2];
815 	int argc;
816 	char *argv[64];		/* 64 args should be more than enough */
817 #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
818 
819 	va_start(as->ap0, path);
820 
821 	argc = 0;
822 	if ((argv[argc] = _auth_next_arg(as)) != NULL)
823 		++argc;
824 
825 	if (as->fd != -1) {
826 		argv[argc++] = "-v";
827 		argv[argc++] = "fd=4";		/* AUTH_FD, see below */
828 	}
829 	/* XXX - fail if out of space in argv */
830 	for (opt = as->optlist; opt != NULL; opt = opt->next) {
831 		if (argc < Nargc - 2) {
832 			argv[argc++] = "-v";
833 			argv[argc++] = opt->opt;
834 		} else {
835 			syslog(LOG_ERR, "too many authentication options");
836 			goto fail;
837 		}
838 	}
839 	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
840 		++argc;
841 
842 	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
843 		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
844 			va_end(as->ap0);
845 			explicit_bzero(&(as->ap0), sizeof(as->ap0));
846 		}
847 		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
848 			va_end(as->ap);
849 			explicit_bzero(&(as->ap), sizeof(as->ap));
850 		}
851 		syslog(LOG_ERR, "too many arguments");
852 		goto fail;
853 	}
854 
855 	argv[argc] = NULL;
856 
857 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) == -1) {
858 		syslog(LOG_ERR, "unable to create backchannel %m");
859 		warnx("internal resource failure");
860 		goto fail;
861 	}
862 
863 	switch (pid = fork()) {
864 	case -1:
865 		syslog(LOG_ERR, "%s: %m", path);
866 		warnx("internal resource failure");
867 		close(pfd[0]);
868 		close(pfd[1]);
869 		goto fail;
870 	case 0:
871 #define	COMM_FD	3
872 #define	AUTH_FD	4
873 		if (dup2(pfd[1], COMM_FD) == -1)
874 			err(1, "dup of backchannel");
875 		if (as->fd != -1) {
876 			if (dup2(as->fd, AUTH_FD) == -1)
877 				err(1, "dup of auth fd");
878 			closefrom(AUTH_FD + 1);
879 		} else
880 			closefrom(COMM_FD + 1);
881 		execve(path, argv, auth_environ);
882 		syslog(LOG_ERR, "%s: %m", path);
883 		err(1, "%s", path);
884 	default:
885 		close(pfd[1]);
886 		if (as->fd != -1) {
887 			close(as->fd);		/* so child has only ref */
888 			as->fd = -1;
889 		}
890 		while ((data = as->data) != NULL) {
891 			as->data = data->next;
892 			if (data->len > 0) {
893 				write(pfd[0], data->ptr, data->len);
894 				explicit_bzero(data->ptr, data->len);
895 			}
896 			free(data);
897 		}
898 		as->index = 0;
899 		_auth_spool(as, pfd[0]);
900 		close(pfd[0]);
901 		do {
902 			if (waitpid(pid, &status, 0) != -1) {
903 				if (!WIFEXITED(status))
904 					goto fail;
905 				break;
906 			}
907 			/*
908 			 * could get ECHILD if it was waited for by
909 			 * another thread or from a signal handler
910 			 */
911 		} while (errno == EINTR);
912 	}
913 
914 	/*
915 	 * Now scan the spooled data
916 	 * It is easier to wait for all the data before starting
917 	 * to scan it.
918 	 */
919     	for (line = as->spool; line < as->spool + as->index;) {
920 		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
921 			line += sizeof(BI_REJECT) - 1;
922 			if (!*line || *line == ' ' || *line == '\t') {
923 				while (*line == ' ' || *line == '\t')
924 					++line;
925 				if (!strcasecmp(line, "silent")) {
926 					as->state = AUTH_SILENT;
927 					break;
928 				}
929 				if (!strcasecmp(line, "challenge")) {
930 					as->state  = AUTH_CHALLENGE;
931 					break;
932 				}
933 				if (!strcasecmp(line, "expired")) {
934 					as->state  = AUTH_EXPIRED;
935 					break;
936 				}
937 				if (!strcasecmp(line, "pwexpired")) {
938 					as->state  = AUTH_PWEXPIRED;
939 					break;
940 				}
941 			}
942 			break;
943 		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
944 			line += sizeof(BI_AUTH) - 1;
945 			if (!*line || *line == ' ' || *line == '\t') {
946 				while (*line == ' ' || *line == '\t')
947 					++line;
948 				if (*line == '\0')
949 					as->state |= AUTH_OKAY;
950 				else if (!strcasecmp(line, "root"))
951 					as->state |= AUTH_ROOTOKAY;
952 				else if (!strcasecmp(line, "secure"))
953 					as->state |= AUTH_SECURE;
954 			}
955 		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
956 			line += sizeof(BI_REMOVE) - 1;
957 			while (*line == ' ' || *line == '\t')
958 				++line;
959 			if (*line)
960 				_add_rmlist(as, line);
961 		}
962 		while (*line++)
963 			;
964 	}
965 
966 	if (WEXITSTATUS(status))
967 		as->state &= ~AUTH_ALLOW;
968 
969 	okay = as->state & AUTH_ALLOW;
970 
971 	if (!okay)
972 		auth_clrenv(as);
973 
974 	if (0) {
975 fail:
976 		auth_clrenv(as);
977 		as->state = 0;
978 		okay = -1;
979 	}
980 
981 	while ((data = as->data) != NULL) {
982 		as->data = data->next;
983 		free(data);
984 	}
985 
986 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
987 		va_end(as->ap0);
988 		explicit_bzero(&(as->ap0), sizeof(as->ap0));
989 	}
990 
991 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
992 		va_end(as->ap);
993 		explicit_bzero(&(as->ap), sizeof(as->ap));
994 	}
995 	return (okay);
996 }
997 DEF_WEAK(auth_call);
998 
999 static void
_recv_fd(auth_session_t * as,int fd)1000 _recv_fd(auth_session_t *as, int fd)
1001 {
1002 	struct msghdr msg;
1003 	struct cmsghdr *cmp;
1004 	union {
1005 		struct cmsghdr hdr;
1006 		char buf[CMSG_SPACE(sizeof(int))];
1007 	} cmsgbuf;
1008 
1009 	memset(&msg, 0, sizeof(msg));
1010 	msg.msg_control = &cmsgbuf.buf;
1011 	msg.msg_controllen = sizeof(cmsgbuf.buf);
1012 	if (recvmsg(fd, &msg, 0) == -1)
1013 		syslog(LOG_ERR, "recvmsg: %m");
1014 	else if (msg.msg_flags & MSG_TRUNC)
1015 		syslog(LOG_ERR, "message truncated");
1016 	else if (msg.msg_flags & MSG_CTRUNC)
1017 		syslog(LOG_ERR, "control message truncated");
1018 	else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
1019 		syslog(LOG_ERR, "missing control message");
1020 	else {
1021 		if (cmp->cmsg_level != SOL_SOCKET)
1022 			syslog(LOG_ERR, "unexpected cmsg_level %d",
1023 			    cmp->cmsg_level);
1024 		else if (cmp->cmsg_type != SCM_RIGHTS)
1025 			syslog(LOG_ERR, "unexpected cmsg_type %d",
1026 			    cmp->cmsg_type);
1027 		else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
1028 			syslog(LOG_ERR, "bad cmsg_len %d",
1029 			    cmp->cmsg_len);
1030 		else {
1031 			if (as->fd != -1)
1032 				close(as->fd);
1033 			as->fd = *(int *)CMSG_DATA(cmp);
1034 		}
1035 	}
1036 }
1037 
1038 static void
_auth_spool(auth_session_t * as,int fd)1039 _auth_spool(auth_session_t *as, int fd)
1040 {
1041 	ssize_t r;
1042 	char *b, *s;
1043 
1044 	for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
1045 		r = read(fd, as->spool + as->index,
1046 		    sizeof(as->spool) - as->index);
1047 		if (r <= 0) {
1048 			as->spool[as->index] = '\0';
1049 			return;
1050 		}
1051 		b = as->spool + as->index;
1052 		as->index += r;
1053 		/*
1054 		 * Convert newlines into NULs to allow easy scanning of the
1055 		 * file and receive an fd if there is a BI_FDPASS message.
1056 		 * XXX - checking for BI_FDPASS here is annoying but
1057 		 *       we need to avoid the read() slurping in control data.
1058 		 */
1059 		while (r-- > 0) {
1060 			if (*b++ == '\n') {
1061 				b[-1] = '\0';
1062 				if (strcasecmp(s, BI_FDPASS) == 0)
1063 					_recv_fd(as, fd);
1064 				s = b;
1065 			}
1066 		}
1067 	}
1068 
1069 	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
1070 	errx(1, "System error in authentication program");
1071 }
1072 
1073 static void
_add_rmlist(auth_session_t * as,char * file)1074 _add_rmlist(auth_session_t *as, char *file)
1075 {
1076 	struct rmfiles *rm;
1077 	size_t i = strlen(file) + 1;
1078 
1079 	// XXX should rangecheck i since we are about to add?
1080 
1081 	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
1082 		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
1083 		return;
1084 	}
1085 	rm->file = (char *)(rm + 1);
1086 	rm->next = as->rmlist;
1087 	strlcpy(rm->file, file, i);
1088 	as->rmlist = rm;
1089 }
1090 
1091 static char *
_auth_next_arg(auth_session_t * as)1092 _auth_next_arg(auth_session_t *as)
1093 {
1094 	char *arg;
1095 
1096 	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
1097 		if ((arg = va_arg(as->ap0, char *)) != NULL)
1098 			return (arg);
1099 		va_end(as->ap0);
1100 		explicit_bzero(&(as->ap0), sizeof(as->ap0));
1101 	}
1102 	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
1103 		if ((arg = va_arg(as->ap, char *)) != NULL)
1104 			return (arg);
1105 		va_end(as->ap);
1106 		explicit_bzero(&(as->ap), sizeof(as->ap));
1107 	}
1108 	return (NULL);
1109 }
1110