text: /*	$OpenBSD: auth_subr.c,v 1.50 2015/12/28 22:08:18 mmcc Exp $	*/
  
  /*
   * Copyright (c) 2000-2002,2004 Todd C. Miller <Todd.Miller@courtesan.com>
   *
   * Permission to use, copy, modify, and distribute this software for any
   * purpose with or without fee is hereby granted, provided that the above
   * copyright notice and this permission notice appear in all copies.
   *
   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */
  /*-
   * Copyright (c) 1995,1996,1997 Berkeley Software Design, Inc.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   * 3. All advertising materials mentioning features or use of this software
   *    must display the following acknowledgement:
   *	This product includes software developed by Berkeley Software Design,
   *	Inc.
   * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
   *    or promote products derived from this software without specific prior
   *    written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   *
   *	BSDI $From: auth_subr.c,v 2.4 1999/09/08 04:10:40 prb Exp $
   */
  
  #include <sys/time.h>
  #include <sys/resource.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
  
  #include <ctype.h>
  #include <err.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <limits.h>
  #include <paths.h>
  #include <pwd.h>
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <syslog.h>
  #include <unistd.h>
  
  #include <login_cap.h>
  
  #define	MAXSPOOLSIZE	(8*1024)	/* Spool up to 8K of back info */
  
  struct rmfiles {
  	struct rmfiles	*next;
  	char		*file;
  };
  
  struct authopts {
  	struct authopts	*next;
  	char		*opt;
  };
  
  struct authdata {
  	struct	authdata *next;
  	void	*ptr;
  	size_t	len;
  };
  
  struct auth_session_t {
  	char	*name;			/* name of use being authenticated */
  	char	*style;			/* style of authentication used */
  	char	*class;			/* class of user */
  	char	*service;		/* type of service being performed */
  	char	*challenge;		/* last challenge issued */
  	int	flags;			/* see below */
  	struct	passwd *pwd;		/* password entry for user */
  	struct	timeval now;		/* time of authentication */
  
  	int	state;			/* authenticated state */
  
  	struct	rmfiles *rmlist;	/* list of files to remove on failure */
  	struct	authopts *optlist;	/* list of options to scripts */ 
  	struct	authdata *data;		/* additional data to send to scripts */
  
  	char	spool[MAXSPOOLSIZE];	/* data returned from login script */
  	int	index;			/* how much returned thus far */
  
  	int	fd;			/* connection to authenticator */
  
  	va_list	ap0;			/* argument list to auth_call */
  	va_list	ap;			/* additional arguments to auth_call */
  };
  
  /*
   * Internal flags
   */
  #define	AF_INTERACTIVE		0x0001	/* This is an interactive session */
  
  /*
   * We cannot include bsd_auth.h until we define the above structures
   */
  #include <bsd_auth.h>
  
  /*
   * Internally used functions
   */
  static void _add_rmlist(auth_session_t *, char *);
  static void _auth_spool(auth_session_t *, int);
  static void _recv_fd(auth_session_t *, int);
  static char *_auth_next_arg(auth_session_t *);
  /*
   * Set up a known environment for all authentication scripts.
   */
  static char *auth_environ[] = {
  	"PATH=" _PATH_DEFPATH,
  	"SHELL=" _PATH_BSHELL,
  	NULL,
  };
  
  static char defservice[] = LOGIN_DEFSERVICE;
  
  static va_list nilap;
  
  /*
   * Quick one liners that only exist to keep auth_session_t opaque
   */
  void	auth_setstate(auth_session_t *as, int s){ as->state = s; }
  void	auth_set_va_list(auth_session_t *as, va_list ap) { va_copy(as->ap, ap); }
  int	auth_getstate(auth_session_t *as)	{ return (as->state); }
  struct passwd *auth_getpwd(auth_session_t *as)	{ return (as->pwd); }
  DEF_WEAK(auth_setstate);
  DEF_WEAK(auth_set_va_list);
  DEF_WEAK(auth_getstate);
  DEF_WEAK(auth_getpwd);
  
  /*
   * Open a new BSD Authentication session with the default service
   * (which can be changed later).
   */
  auth_session_t *
  auth_open(void)
  {
  	auth_session_t *as;
  
  	if ((as = calloc(1, sizeof(auth_session_t))) != NULL) {
  		as->service = defservice;
  		as->fd = -1;
  	}
  
  	return (as);
  }
  DEF_WEAK(auth_open);
  
  /*
   * Clean the specified BSD Authentication session.
   */
  void
  auth_clean(auth_session_t *as)
  {
  	struct rmfiles *rm;
  	struct authdata *data;
  
  	as->state = 0;
  
  	auth_clrenv(as);
  
  	/*
  	 * Clean out the rmlist and remove specified files
  	 */
  	while ((rm = as->rmlist) != NULL) {
  		as->rmlist = rm->next;
  		unlink(rm->file);
  		free(rm);
  	}
  
  	/*
  	 * Clean out data
  	 */
  	while ((data = as->data) != NULL) {
  		if (as->data->len)
  			explicit_bzero(as->data->ptr, as->data->len);
  		as->data = data->next;
  		free(data);
  	}
  
  	auth_setitem(as, AUTHV_ALL, NULL);
  
  	if (as->pwd != NULL) {
  		explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
  		free(as->pwd);
  		as->pwd = NULL;
  	}
  
  	if (as->fd != -1) {
  		close(as->fd);
  		as->fd = -1;
  	}
  }
  DEF_WEAK(auth_clean);
  
  /*
   * Close the specified BSD Authentication session.
   * Return 0 if not authenticated.
   */
  int
  auth_close(auth_session_t *as)
  {
  	struct rmfiles *rm;
  	struct authopts *opt;
  	struct authdata *data;
  	int s;
  
  	/*
  	 * Save our return value
  	 */
  	s = as->state & AUTH_ALLOW;
  
  	if (s == 0)
  		as->index = 0;
  
  	auth_setenv(as);
  
  
  	/*
  	 * Clean out the rmlist and remove specified files if the
  	 * authentication failed
  	 */
  	while ((rm = as->rmlist) != NULL) {
  		as->rmlist = rm->next;
  		if (s == 0)
  			unlink(rm->file);
  		free(rm);
  	}
  
  	/*
  	 * Clean out the opt list
  	 */
  	while ((opt = as->optlist) != NULL) {
  		as->optlist = opt->next;
  		free(opt);
  	}
  
  	/*
  	 * Clean out data
  	 */
  	while ((data = as->data) != NULL) {
  		if (as->data->len)
  			explicit_bzero(as->data->ptr, as->data->len);
  		as->data = data->next;
  		free(data);
  	}
  
  	if (as->pwd != NULL) {
  		explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
  		free(as->pwd);
  		as->pwd = NULL;
  	}
  
  	/*
  	 * Clean up random variables
  	 */
  	if (as->service && as->service != defservice)
  		free(as->service);
  	free(as->challenge);
  	free(as->class);
  	free(as->style);
  	free(as->name);
  
  	free(as);
  	return (s);
  }
  DEF_WEAK(auth_close);
  
  /*
   * Request a challenge for the session.
   * The name and style must have already been specified
   */
  char *
  auth_challenge(auth_session_t *as)
  {
  	char path[PATH_MAX];
  	int len;
  
  	if (as == NULL || as->style == NULL || as->name == NULL)
  		return (NULL);
  
  	len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
  	if (len < 0 || len >= sizeof(path))
  		return (NULL);
  
  	as->state = 0;
  
  	free(as->challenge);
  	as->challenge = NULL;
  
  	auth_call(as, path, as->style, "-s", "challenge", as->name,
  	    as->class, (char *)NULL);
  	if (as->state & AUTH_CHALLENGE)
  		as->challenge = auth_getvalue(as, "challenge");
  	as->state = 0;
  	as->index = 0;	/* toss our data */
  	return (as->challenge);
  }
  DEF_WEAK(auth_challenge);
  
  /*
   * Set/unset the requested environment variables.
   * Mark the variables as set so they will not be set a second time.
   * XXX - should provide a way to detect setenv() failure.
   */
  void
  auth_setenv(auth_session_t *as)
  {
  	char *line, *name;
  
  	/*
  	 * Set any environment variables we were asked for
  	 */
      	for (line = as->spool; line < as->spool + as->index;) {
  		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
  			if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
  				/* only do it once! */
  				line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
  				line += sizeof(BI_SETENV) - 1;
  				for (name = line;
  				    isblank((unsigned char)*name); ++name)
  					;
  				for (line = name;
  				    *line && !isblank((unsigned char)*line);
  				    ++line)
  					;
  				if (*line)
  					*line++ = '\0';
  				for (; isblank((unsigned char)*line); ++line)
  					;
  				if (*line != '\0' && setenv(name, line, 1))
  					warn("setenv(%s, %s)", name, line);
  			}
  		} else
  		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
  			if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
  				/* only do it once! */
  				line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
  				line += sizeof(BI_UNSETENV) - 1;
  				for (name = line;
  				    isblank((unsigned char)*name); ++name)
  					;
  				for (line = name;
  				    *line && !isblank((unsigned char)*line);
  				    ++line)
  					;
  				if (*line)
  					*line++ = '\0';
  				unsetenv(name);
  			}
  		}
  		while (*line++)
  			;
  	}
  }
  DEF_WEAK(auth_setenv);
  
  /*
   * Clear out any requested environment variables.
   */
  void
  auth_clrenv(auth_session_t *as)
  {
  	char *line;
  
  	for (line = as->spool; line < as->spool + as->index;) {
  		if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
  			if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
  				line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
  			}
  		} else
  		if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
  			if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
  				line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
  			}
  		}
  		while (*line++)
  			;
  	}
  }
  DEF_WEAK(auth_clrenv);
  
  char *
  auth_getitem(auth_session_t *as, auth_item_t item)
  {
  	if (as != NULL) {
  		switch (item) {
  		case AUTHV_CHALLENGE:
  			return (as->challenge);
  		case AUTHV_CLASS:
  			return (as->class);
  		case AUTHV_NAME:
  			return (as->name);
  		case AUTHV_SERVICE:
  			return (as->service ? as->service : defservice);
  		case AUTHV_STYLE:
  			return (as->style);
  		case AUTHV_INTERACTIVE:
  			return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
  		default:
  			break;
  		}
  	}
  	return (NULL);
  }
  DEF_WEAK(auth_getitem);
  
  int
  auth_setitem(auth_session_t *as, auth_item_t item, char *value)
  {
  	if (as == NULL) {
  		errno = EINVAL;
  		return (-1);
  	}
  
  	switch (item) {
  	case AUTHV_ALL:
  		if (value != NULL) {
  			errno = EINVAL;
  			return (-1);
  		}
  		auth_setitem(as, AUTHV_CHALLENGE, NULL);
  		auth_setitem(as, AUTHV_CLASS, NULL);
  		auth_setitem(as, AUTHV_NAME, NULL);
  		auth_setitem(as, AUTHV_SERVICE, NULL);
  		auth_setitem(as, AUTHV_STYLE, NULL);
  		auth_setitem(as, AUTHV_INTERACTIVE, NULL);
  		return (0);
  
  	case AUTHV_CHALLENGE:
  		if (value == as->challenge)
  			return (0);
  		if (value != NULL && (value = strdup(value)) == NULL)
  			return (-1);
  		free(as->challenge);
  		as->challenge = value;
  		return (0);
  
  	case AUTHV_CLASS:
  		if (value == as->class)
  			return (0);
  		if (value != NULL && (value = strdup(value)) == NULL)
  			return (-1);
  		free(as->class);
  		as->class = value;
  		return (0);
  
  	case AUTHV_NAME:
  		if (value == as->name)
  			return (0);
  		if (value != NULL && (value = strdup(value)) == NULL)
  			return (-1);
  		free(as->name);
  		as->name = value;
  		return (0);
  
  	case AUTHV_SERVICE:
  		if (value == as->service)
  			return (0);
  		if (value == NULL || strcmp(value, defservice) == 0)
  			value = defservice;
  		else if ((value = strdup(value)) == NULL)
  			return (-1);
  		if (as->service && as->service != defservice)
  			free(as->service);
  		as->service = value;
  		return (0);
  
  	case AUTHV_STYLE:
  		if (value == as->style)
  			return (0);
  		if (value == NULL || strchr(value, '/') != NULL ||
  		    (value = strdup(value)) == NULL)
  			return (-1);
  		free(as->style);
  		as->style = value;
  		return (0);
  
  	case AUTHV_INTERACTIVE:
  		if (value == NULL)
  			as->flags &= ~AF_INTERACTIVE;
  		else
  			as->flags |= ~AF_INTERACTIVE;
  		return (0);
  
  	default:
  		errno = EINVAL;
  		return (-1);
  	}
  }
  DEF_WEAK(auth_setitem);
  
  int
  auth_setoption(auth_session_t *as, char *n, char *v)
  {
  	struct authopts *opt;
  	size_t len = strlen(n) + strlen(v) + 2;
  	int ret;
  
  	if ((opt = malloc(sizeof(*opt) + len)) == NULL)
  		return (-1);
  
  	opt->opt = (char *)(opt + 1);
  
  	ret = snprintf(opt->opt, len, "%s=%s", n, v);
  	if (ret < 0 || ret >= len) {
  		free(opt);
  		errno = ENAMETOOLONG;
  		return (-1);
  	}
  	opt->next = as->optlist;
  	as->optlist = opt;
  	return(0);
  }
  DEF_WEAK(auth_setoption);
  
  void
  auth_clroptions(auth_session_t *as)
  {
  	struct authopts *opt;
  
  	while ((opt = as->optlist) != NULL) {
  		as->optlist = opt->next;
  		free(opt);
  	}
  }
  DEF_WEAK(auth_clroptions);
  
  void
  auth_clroption(auth_session_t *as, char *option)
  {
  	struct authopts *opt, *oopt;
  	size_t len;
  
  	len = strlen(option);
  
  	if ((opt = as->optlist) == NULL)
  		return;
  
  	if (strncmp(opt->opt, option, len) == 0 &&
  	    (opt->opt[len] == '=' || opt->opt[len] == '\0')) {
  		as->optlist = opt->next;
  		free(opt);
  		return;
  	}
  
  	while ((oopt = opt->next) != NULL) {
  		if (strncmp(oopt->opt, option, len) == 0 &&
  		    (oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
  			opt->next = oopt->next;
  			free(oopt);
  			return;
  		}
  		opt = oopt;
  	}
  }
  DEF_WEAK(auth_clroption);
  
  int
  auth_setdata(auth_session_t *as, void *ptr, size_t len)
  {
  	struct authdata *data, *dp;
  
  	if (len <= 0)
  		return (0);
  
  	if ((data = malloc(sizeof(*data) + len)) == NULL)
  		return (-1);
  
  	data->next = NULL;
  	data->len = len;
  	data->ptr = data + 1;
  	memcpy(data->ptr, ptr, len);
  
  	if (as->data == NULL)
  		as->data = data;
  	else {
  		for (dp = as->data; dp->next != NULL; dp = dp->next)
  			;
  		dp->next = data;
  	}
  	return (0);
  }
  DEF_WEAK(auth_setdata);
  
  int
  auth_setpwd(auth_session_t *as, struct passwd *pwd)
  {
  	struct passwd pwstore;
  	char *instance, pwbuf[_PW_BUF_LEN];
  
  	if (pwd == NULL && as->pwd == NULL && as->name == NULL)
  		return (-1);		/* true failure */
  
  	if (pwd == NULL) {
  		/*
  		 * If we were not passed in a pwd structure we need to
  		 * go find one for ourself.  Always look up the username
  		 * (if it is defined) in the passwd database to see if there
  		 * is an entry for the user.  If not, either use the current
  		 * entry or simply return a 1 which implies there is
  		 * no user by that name here.  This is not a failure, just
  		 * a point of information.
  		 */
  		if (as->name == NULL)
  			return (0);
  		getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
  		if (pwd == NULL) {
  			instance = strchr(as->name, '/');
  			if (instance == NULL)
  				return (as->pwd ? 0 : 1);
  			if (strcmp(instance, "/root") == 0) {
  				getpwnam_r(instance + 1, &pwstore, pwbuf,
  				    sizeof(pwbuf), &pwd);
  			}
  			if (pwd == NULL)
  				return (as->pwd ? 0 : 1);
  		}
  	}
  	if ((pwd = pw_dup(pwd)) == NULL)
  		return (-1);		/* true failure */
  	if (as->pwd) {
  		explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
  		free(as->pwd);
  	}
  	as->pwd = pwd;
  	return (0);
  }
  DEF_WEAK(auth_setpwd);
  
  char *
  auth_getvalue(auth_session_t *as, char *what)
  {
  	char *line, *v, *value;
  	int n, len;
  
  	len = strlen(what);
  
      	for (line = as->spool; line < as->spool + as->index;) {
  		if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
  			goto next;
  		line += sizeof(BI_VALUE) - 1;
  
  		if (!isblank((unsigned char)*line))
  			goto next;
  
  		while (isblank((unsigned char)*++line))
  			;
  
  		if (strncmp(line, what, len) != 0 ||
  		    !isblank((unsigned char)line[len]))
  			goto next;
  		line += len;
  		while (isblank((unsigned char)*++line))
  			;
  		value = strdup(line);
  		if (value == NULL)
  			return (NULL);
  
  		/*
  		 * XXX - There should be a more standardized
  		 * routine for doing this sort of thing.
  		 */
  		for (line = v = value; *line; ++line) {
  			if (*line == '\\') {
  				switch (*++line) {
  				case 'r':
  					*v++ = '\r';
  					break;
  				case 'n':
  					*v++ = '\n';
  					break;
  				case 't':
  					*v++ = '\t';
  					break;
  				case '0': case '1': case '2':
  				case '3': case '4': case '5':
  				case '6': case '7':
  					n = *line - '0';
  					if (isdigit((unsigned char)line[1])) {
  						++line;
  						n <<= 3;
  						n |= *line-'0';
  					}
  					if (isdigit((unsigned char)line[1])) {
  						++line;
  						n <<= 3;
  						n |= *line-'0';
  					}
  					break;
  				default:
  					*v++ = *line;
  					break;
  				}
  			} else
  				*v++ = *line;
  		}
  		*v = '\0';
  		return (value);
  next:
  		while (*line++)
  			;
  	}
  	return (NULL);
  }
  DEF_WEAK(auth_getvalue);
  
  quad_t
  auth_check_expire(auth_session_t *as)
  {
  	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
  		as->state &= ~AUTH_ALLOW;
  		as->state |= AUTH_EXPIRED;	/* XXX */
  		return (-1);
  	}
  
  	if (as->pwd == NULL)
  		return (0);
  
  	if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
  		if (as->now.tv_sec == 0)
  			gettimeofday(&as->now, NULL);
  		if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
  			as->state &= ~AUTH_ALLOW;
  			as->state |= AUTH_EXPIRED;
  		}
  		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
  			return (-1);
  		return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
  	}
  	return (0);
  }
  DEF_WEAK(auth_check_expire);
  
  quad_t
  auth_check_change(auth_session_t *as)
  {
  	if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
  		as->state &= ~AUTH_ALLOW;
  		as->state |= AUTH_PWEXPIRED;	/* XXX */
  		return (-1);
  	}
  
  	if (as->pwd == NULL)
  		return (0);
  
  	if (as->pwd && (quad_t)as->pwd->pw_change) {
  		if (as->now.tv_sec == 0)
  			gettimeofday(&as->now, NULL);
  		if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
  			as->state &= ~AUTH_ALLOW;
  			as->state |= AUTH_PWEXPIRED;
  		}
  		if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
  			return (-1);
  		return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
  	}
  	return (0);
  }
  DEF_WEAK(auth_check_change);
  
  /*
   * The down and dirty call to the login script
   * okay contains the default return value, typically 0 but
   * is AUTH_OKAY for approval like scripts.
   *
   * Internally additional trailing arguments can be read from as->ap
   * Options will be placed just after the first argument (not including path).
   *
   * Any data will be sent to (and freed by) the script
   */
  int
  auth_call(auth_session_t *as, char *path, ...)
  {
  	char *line;
  	struct authdata *data;
  	struct authopts *opt;
  	pid_t pid;
  	int status;
  	int okay;
  	int pfd[2];
  	int argc;
  	char *argv[64];		/* 64 args should be more than enough */
  #define	Nargc	(sizeof(argv)/sizeof(argv[0]))
  
  	va_start(as->ap0, path);
  
  	argc = 0;
  	if ((argv[argc] = _auth_next_arg(as)) != NULL)
  		++argc;
  
  	if (as->fd != -1) {
  		argv[argc++] = "-v";
  		argv[argc++] = "fd=4";		/* AUTH_FD, see below */
  	}
  	for (opt = as->optlist; opt != NULL; opt = opt->next) {
  		if (argc < Nargc - 2) {
  			argv[argc++] = "-v";
  			argv[argc++] = opt->opt;
  		} else {
  			syslog(LOG_ERR, "too many authentication options");
  			goto fail;
  		}
  	}
  	while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
  		++argc;
  
  	if (argc >= Nargc - 1 && _auth_next_arg(as)) {
  		if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
  			va_end(as->ap0);
  			explicit_bzero(&(as->ap0), sizeof(as->ap0));
  		}
  		if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
  			va_end(as->ap);
  			explicit_bzero(&(as->ap), sizeof(as->ap));
  		}
  		syslog(LOG_ERR, "too many arguments");
  		goto fail;
  	}
  
  	argv[argc] = NULL;
  
  	if (secure_path(path) < 0) {
  		syslog(LOG_ERR, "%s: path not secure", path);
  		warnx("invalid script: %s", path);
  		goto fail;
  	}
  
  	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) < 0) {
  		syslog(LOG_ERR, "unable to create backchannel %m");
  		warnx("internal resource failure");
  		goto fail;
  	}
  
  	switch (pid = fork()) {
  	case -1:
  		syslog(LOG_ERR, "%s: %m", path);
  		warnx("internal resource failure");
  		close(pfd[0]);
  		close(pfd[1]);
  		goto fail;
  	case 0:
  #define	COMM_FD	3
  #define	AUTH_FD	4
  		if (dup2(pfd[1], COMM_FD) < 0)
  			err(1, "dup of backchannel");
  		if (as->fd != -1) {
  			if (dup2(as->fd, AUTH_FD) < 0)
  				err(1, "dup of auth fd");
  			closefrom(AUTH_FD + 1);
  		} else
  			closefrom(COMM_FD + 1);
  		execve(path, argv, auth_environ);
  		syslog(LOG_ERR, "%s: %m", path);
  		err(1, "%s", path);
  	default:
  		close(pfd[1]);
  		if (as->fd != -1) {
  			close(as->fd);		/* so child has only ref */
  			as->fd = -1;
  		}
  		while ((data = as->data) != NULL) {
  			as->data = data->next;
  			if (data->len > 0) {
  				write(pfd[0], data->ptr, data->len);
  				explicit_bzero(data->ptr, data->len);
  			}
  			free(data);
  		}
  		as->index = 0;
  		_auth_spool(as, pfd[0]);
  		close(pfd[0]);
  		do {
  			if (waitpid(pid, &status, 0) != -1) {
  				if (!WIFEXITED(status))
  					goto fail;
  				break;
  			}
  			/*
  			 * could get ECHILD if it was waited for by
  			 * another thread or from a signal handler
  			 */
  		} while (errno == EINTR);
  	}
  
  	/*
  	 * Now scan the spooled data
  	 * It is easier to wait for all the data before starting
  	 * to scan it.
  	 */
      	for (line = as->spool; line < as->spool + as->index;) {
  		if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
  			line += sizeof(BI_REJECT) - 1;
  			if (!*line || *line == ' ' || *line == '\t') {
  				while (*line == ' ' || *line == '\t')
  					++line;
  				if (!strcasecmp(line, "silent")) {
  					as->state = AUTH_SILENT;
  					break;
  				}
  				if (!strcasecmp(line, "challenge")) {
  					as->state  = AUTH_CHALLENGE;
  					break;
  				}
  				if (!strcasecmp(line, "expired")) {
  					as->state  = AUTH_EXPIRED;
  					break;
  				}
  				if (!strcasecmp(line, "pwexpired")) {
  					as->state  = AUTH_PWEXPIRED;
  					break;
  				}
  			}
  			break;
  		} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
  			line += sizeof(BI_AUTH) - 1;
  			if (!*line || *line == ' ' || *line == '\t') {
  				while (*line == ' ' || *line == '\t')
  					++line;
  				if (*line == '\0')
  					as->state |= AUTH_OKAY;
  				else if (!strcasecmp(line, "root"))
  					as->state |= AUTH_ROOTOKAY;
  				else if (!strcasecmp(line, "secure"))
  					as->state |= AUTH_SECURE;
  			}
  		} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
  			line += sizeof(BI_REMOVE) - 1;
  			while (*line == ' ' || *line == '\t')
  				++line;
  			if (*line)
  				_add_rmlist(as, line);
  		}
  		while (*line++)
  			;
  	}
  
  	if (WEXITSTATUS(status))
  		as->state &= ~AUTH_ALLOW;
  
  	okay = as->state & AUTH_ALLOW;
  
  	if (!okay)
  		auth_clrenv(as);
  
  	if (0) {
  fail:
  		auth_clrenv(as);
  		as->state = 0;
  		okay = -1;
  	}
  
  	while ((data = as->data) != NULL) {
  		as->data = data->next;
  		free(data);
  	}
  
  	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
  		va_end(as->ap0);
  		explicit_bzero(&(as->ap0), sizeof(as->ap0));
  	}
  
  	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
  		va_end(as->ap);
  		explicit_bzero(&(as->ap), sizeof(as->ap));
  	}
  	return (okay);
  }
  DEF_WEAK(auth_call);
  
  static void
  _recv_fd(auth_session_t *as, int fd)
  {
  	struct msghdr msg;
  	struct cmsghdr *cmp;
  	union {
  		struct cmsghdr hdr;
  		char buf[CMSG_SPACE(sizeof(int))];
  	} cmsgbuf;
  
  	memset(&msg, 0, sizeof(msg));
  	msg.msg_control = &cmsgbuf.buf;
  	msg.msg_controllen = sizeof(cmsgbuf.buf);
  	if (recvmsg(fd, &msg, 0) < 0)
  		syslog(LOG_ERR, "recvmsg: %m");
  	else if (msg.msg_flags & MSG_TRUNC)
  		syslog(LOG_ERR, "message truncated");
  	else if (msg.msg_flags & MSG_CTRUNC)
  		syslog(LOG_ERR, "control message truncated");
  	else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
  		syslog(LOG_ERR, "missing control message");
  	else {
  		if (cmp->cmsg_level != SOL_SOCKET)
  			syslog(LOG_ERR, "unexpected cmsg_level %d",
  			    cmp->cmsg_level);
  		else if (cmp->cmsg_type != SCM_RIGHTS)
  			syslog(LOG_ERR, "unexpected cmsg_type %d",
  			    cmp->cmsg_type);
  		else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
  			syslog(LOG_ERR, "bad cmsg_len %d",
  			    cmp->cmsg_len);
  		else {
  			if (as->fd != -1)
  				close(as->fd);
  			as->fd = *(int *)CMSG_DATA(cmp);
  		}
  	}
  }
  
  static void
  _auth_spool(auth_session_t *as, int fd)
  {
  	ssize_t r;
  	char *b, *s;
  
  	for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
  		r = read(fd, as->spool + as->index,
  		    sizeof(as->spool) - as->index);
  		if (r <= 0) {
  			as->spool[as->index] = '\0';
  			return;
  		}
  		b = as->spool + as->index;
  		as->index += r;
  		/*
  		 * Convert newlines into NULs to allow easy scanning of the
  		 * file and receive an fd if there is a BI_FDPASS message.
  		 * XXX - checking for BI_FDPASS here is annoying but
  		 *       we need to avoid the read() slurping in control data.
  		 */
  		while (r-- > 0) {
  			if (*b++ == '\n') {
  				b[-1] = '\0';
  				if (strcasecmp(s, BI_FDPASS) == 0)
  					_recv_fd(as, fd);
  				s = b;
  			}
  		}
  	}
  
  	syslog(LOG_ERR, "Overflowed backchannel spool buffer");
  	errx(1, "System error in authentication program");
  }
  
  static void
  _add_rmlist(auth_session_t *as, char *file)
  {
  	struct rmfiles *rm;
  	size_t i = strlen(file) + 1;
  
  	// XXX should rangecheck i since we are about to add?
  
  	if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
  		syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
  		return;
  	}
  	rm->file = (char *)(rm + 1);
  	rm->next = as->rmlist;
  	strlcpy(rm->file, file, i);
  	as->rmlist = rm;
  }
  
  static char *
  _auth_next_arg(auth_session_t *as)
  {
  	char *arg;
  
  	if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
  		if ((arg = va_arg(as->ap0, char *)) != NULL)
  			return (arg);
  		va_end(as->ap0);
  		explicit_bzero(&(as->ap0), sizeof(as->ap0));
  	}
  	if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
  		if ((arg = va_arg(as->ap, char *)) != NULL)
  			return (arg);
  		va_end(as->ap);
  		explicit_bzero(&(as->ap), sizeof(as->ap));
  	}
  	return (NULL);
  }
  