/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 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.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * This is the DACS authentication service.
 * Its job is to invoke and coordinate local authentication service modules,
 * based on run-time configuration information.
 * If authentication is successful, encrypted credentials may be returned as a
 * cookie.
 *
 * XXX Note that existing identity information is not provided to expression
 * evaluation of configuration directives.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: auth.c 2586 2012-03-15 16:21:40Z brachman $";
#endif

#include "dacs.h"
#include "dacs_api.h"

static MAYBE_UNUSED char *log_module_name = "dacs_authenticate";

#ifndef PROG

#if defined(__linux__)
#include <sys/time.h>
#endif

typedef enum {
  ERROR_HANDLER_URL     = 1,
  ERROR_HANDLER_FILE    = 2,
  ERROR_HANDLER_REASON  = 3,
  ERROR_HANDLER_MESSAGE = 4,
  ERROR_HANDLER_NONE    = 5
} Auth_error_handler_type;

typedef struct Auth_error_handler {
  Auth_error_handler_type type;
  unsigned int error_value;
  char *error_code;
  char *error_string;
} Auth_error_handler;

typedef enum {
  SUCCESS_HANDLER_URL         = 1,
  SUCCESS_HANDLER_FILE        = 2,
  SUCCESS_HANDLER_MESSAGE     = 3,
  SUCCESS_HANDLER_CREDENTIALS = 4,
  SUCCESS_HANDLER_NONE        = 5
} Auth_success_handler_type;

typedef struct Auth_success_handler {
  Auth_success_handler_type type;
  char *success_string;
} Auth_success_handler;

/* Web service arguments. */
typedef struct Auth_args {
  char *username;
  char *password;
  char *auxiliary;
  char *xmlToken;
  char *jurisdiction;
  char *dacs_request_method;
  char *ticket;
  char *dacs_browser;
  char *auth_id;
  char *auth_transid;
  char *format;
  char *enable_auth_handlers;
  char *cookie_syntax;
  char *www_authenticate;
  char *authorization;
  char *dacs_auth_success_handler;
  char *dacs_error_url;
  char *dacs_version;
  char *dacs_debug;
  char *operation;
  Dsvec *auth_prompt_var_prefixes;
} Auth_args;

static Kwv *kwv_auth = NULL;

static int is_dacs_authenticate = 1;

static Auth_args *
init_args(void)
{
  Auth_args *args;

  args = ALLOC(Auth_args);
  args->username = NULL;
  args->password = NULL;
  args->auxiliary = NULL;
  args->xmlToken = NULL;
  args->jurisdiction = NULL;
  args->dacs_request_method = NULL;
  args->ticket = NULL;
  args->dacs_browser = NULL;
  args->auth_id = NULL;
  args->auth_transid = NULL;
  args->format = NULL;
  args->enable_auth_handlers = NULL;
  args->cookie_syntax = NULL;
  args->www_authenticate = NULL;
  args->authorization = NULL;
  args->dacs_auth_success_handler = NULL;
  args->dacs_error_url = NULL;
  args->dacs_version = NULL;
  args->dacs_debug = NULL;
  args->operation = NULL;
  args->auth_prompt_var_prefixes = dsvec_init(NULL, sizeof(char *));

  return(args);
}

static Auth_module_desc auth_module_desc_tab[] = {
  { AUTH_MODULE_EXPR, 1, AUTH_STYLE_EXPR,
	"expr", 
	{ "expr", NULL },
  },

#ifdef ENABLE_PASSWD_AUTH
  { AUTH_MODULE_PASSWD, 1, AUTH_STYLE_PASSWORD,
	"local_passwd_authenticate",
	{ "local_passwd_authenticate", "passwd", "password", NULL }
  },
#endif

#ifdef ENABLE_SIMPLE_AUTH
  { AUTH_MODULE_SIMPLE, 1, AUTH_STYLE_SIMPLE,
	"local_simple_authenticate",
	{ "local_simple_authenticate", "simple", NULL }
  },
#endif

#ifdef ENABLE_INFOCARD_AUTH
  { AUTH_MODULE_INFOCARD, 1, AUTH_STYLE_INFOCARD,
	"local_infocard_authenticate",
	{ "local_infocard_authenticate", "infocard", NULL }
  },
  { AUTH_MODULE_INFOCARD, 1, AUTH_STYLE_MINFOCARD,
	"local_infocard_authenticate",
	{ "local_minfocard_authenticate", "minfocard", "managed_infocard", NULL }
  },
  { AUTH_MODULE_INFOCARD, 1, AUTH_STYLE_SINFOCARD,
	"local_infocard_authenticate",
	{ "local_sinfocard_authenticate", "sinfocard", "selfissued_infocard", NULL }
  },
#endif

#ifdef ENABLE_UNIX_AUTH
  { AUTH_MODULE_UNIX, 1, AUTH_STYLE_PASSWORD,
	"local_unix_authenticate",
	{ "local_unix_authenticate", "unix", NULL }
  },
#endif

#ifdef ENABLE_NTLM_AUTH
  { AUTH_MODULE_NTLM, 1, AUTH_STYLE_PASSWORD,
	"local_ntlm_authenticate",
	{ "local_ntlm_authenticate", "ntlm", NULL }
  },
#endif

#ifdef ENABLE_APACHE_AUTH
  { AUTH_MODULE_APACHE, 1, AUTH_STYLE_PASSWORD,
	"local_apache_authenticate",
	{ "local_apache_authenticate", "apache", NULL }
  },
#endif

#ifdef ENABLE_CAS_AUTH
  { AUTH_MODULE_CAS, 1, AUTH_STYLE_CAS,
	"local_cas_authenticate",
	{ "local_cas_authenticate", "cas", NULL }
  },
#endif

#ifdef ENABLE_GRID_AUTH
  { AUTH_MODULE_GRID, 1, AUTH_STYLE_PASSWORD,
	"local_grid_authenticate",
	{ "local_grid_authenticate", "grid", NULL }
  },
#endif

#ifdef ENABLE_HTTP_AUTH
  { AUTH_MODULE_HTTP, 1, AUTH_STYLE_PASSWORD,
	"local_http_authenticate",
	{ "local_http_authenticate", "http", NULL }
  },
#endif

#ifdef ENABLE_TOKEN_AUTH
  { AUTH_MODULE_TOKEN, 1, AUTH_STYLE_PASSWORD,
	"local_token_authenticate",
	{ "local_token_authenticate", "token", NULL }
  },
#endif

#ifdef ENABLE_PAM_AUTH
  { AUTH_MODULE_PAM, 1, AUTH_STYLE_PROMPTED,
	"local_pam_authenticate",
	{ "local_pam_authenticate", "pam", NULL }
  },
#endif

  { AUTH_MODULE_UNKNOWN, -1, AUTH_STYLE_UNKNOWN,
	NULL,
	{ NULL }
  }
};

static Auth_module_desc *
auth_module_lookup_desc_by_name(char *name)
{
  int i;
  Auth_module_desc *desc;

  for (desc = &auth_module_desc_tab[0]; desc->id != AUTH_MODULE_UNKNOWN;
	   desc++) {
	for (i = 0; i < AUTH_MODULE_MAX_ALIASES && desc->aliases[i] != NULL; i++) {
	  if (strcaseeq(desc->aliases[i], name))
		return(desc);
	}
  }

  return(NULL);
}

static Auth_module_id
auth_module_lookup_id_by_name(char *name)
{
  int i;
  Auth_module_desc *desc;

  for (desc = &auth_module_desc_tab[0]; desc->id != AUTH_MODULE_UNKNOWN;
	   desc++) {
	for (i = 0; i < AUTH_MODULE_MAX_ALIASES && desc->aliases[i] != NULL; i++) {
	  if (strcaseeq(desc->aliases[i], name))
		return(desc->id);
	}
  }

  return(AUTH_MODULE_UNKNOWN);
}

/*
 * Also see conf.c:conf_auth_vartab_by_id()
 */
static Auth_module *
auth_module_lookup_by_id(Auth_module_info *ami, char *id)
{
  Auth_module *am;

  for (am = ami->modules; am != NULL; am = am->next) {
	if (am->id != NULL && streq(am->id, id))
	  return(am);
  }

  return(NULL);
}

static Auth_module *
new_auth_module(Kwv_vartab *auth_vartab)
{
  Auth_module *am;

  am = ALLOC(Auth_module);
  am->id = NULL;
  am->auth_vartab = auth_vartab;
  am->url = NULL;
  am->url_eval = NULL;
  am->expr = NULL;
  am->init_eval = NULL;
  am->exit_eval = NULL;
  am->style = 0;
  am->control_flag = 0;
  am->predicate = NULL;
  am->flags = NULL;
  am->options = dsvec_init(NULL, sizeof(char *));
  am->options_eval = NULL;
  am->kwv_options = kwv_init(10);
  /* Do not allow duplicate OPTIONs */
  kwv_set_mode(am->kwv_options, "dn");
  am->dsv_audits = NULL;
  am->kwv_conf = kwv_init(10);
  am->kwv_conf->dup_mode = KWV_REPLACE_DUPS;
  am->have_req_auth = 0;
  am->result = 0;
  am->argv_spec = NULL;
  am->next = NULL;

  return(am);
}

static Auth_module_info *
auth_parse_auth_clause_init(Auth_module_info *o_ami)
{
  Auth_module_info *ami;

  if (o_ami == NULL)
	ami = ALLOC(Auth_module_info);
  else
	ami = o_ami;

  ami->modules = NULL;
  ami->mcount = 0;
  ami->saw_native = ami->saw_prompted = ami->saw_expr = 0;
  ami->saw_set_roles = ami->saw_add_roles = 0;
  ami->have_req_auth = 0;

  return(ami);
}

/*
 * Parse all of the Auth clauses.
 */
static Auth_module_info *
auth_parse_auth_clauses(Auth_module_info *o_ami)
{
  int i, n, vtnum;
  char *p;
  Auth_module *am;
  Auth_module_info *ami;
  Kwv_pair *v;
  Kwv_vartab *auth_vartab;

  ami = auth_parse_auth_clause_init(o_ami);

  vtnum = 0;

  while ((auth_vartab = conf_auth_vartab(vtnum++)) != NULL) {
	char *expr, *url, *control_flag_arg, *style_arg_str, **style_args;
	Auth_module **mp;
	Mkargv conf;

	am = new_auth_module(auth_vartab);

	if ((p = conf_vartab_val(auth_vartab, CONF_AUTH_AUTH_ID)) != NULL)
	  am->id = p;
	else {
	  log_msg((LOG_ERROR_LEVEL, "Missing AUTH_ID?"));
	  return(NULL);
	}

	am->init_eval = conf_vartab_val(auth_vartab, CONF_AUTH_INIT_EVAL);
	am->exit_eval = conf_vartab_val(auth_vartab, CONF_AUTH_EXIT_EVAL);

	style_arg_str = conf_vartab_val(auth_vartab, CONF_AUTH_STYLE);
	control_flag_arg = conf_vartab_val(auth_vartab, CONF_AUTH_CONTROL);

	if (style_arg_str == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid AUTH_STYLE directive (no style)"));
	  return(NULL);
	}

#ifdef NOTDEF
	conf.keepq = conf.keepws = 0;
	conf.ifs = ",";
	conf.startq = conf.endq = NULL;
	if ((n = mkargv(style_arg_str, &conf, &style_args)) < 1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid AUTH_STYLE directive (style syntax): %s",
			   style_arg_str));
	  return(NULL);
	}
#endif

	am->style = auth_style_from_string(style_arg_str);
	if (am->style == AUTH_STYLE_UNKNOWN) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid AUTH_STYLE directive: %s", style_arg_str));
	  return(NULL);
	}

	if ((am->style & AUTH_STYLE_EXPR)) {
	  if ((expr = conf_vartab_val(auth_vartab, CONF_AUTH_EXPR)) != NULL)
		am->expr = expr;
	  else {
		log_msg((LOG_ERROR_LEVEL,
				 "EXPR directive is required with STYLE \"expr\""));
		return(NULL);
	  }
	  ami->saw_expr++;
	}
	else if ((am->style & AUTH_STYLE_NATIVE))
	  ami->saw_native++;
	else if ((am->style & AUTH_STYLE_PROMPTED))
	  ami->saw_prompted++;
	else if ((am->style & AUTH_STYLE_SET_ROLES)) {
	  ami->saw_set_roles++;
	  log_msg((LOG_TRACE_LEVEL, "Module spec has set_roles"));
	}
	else if ((am->style & AUTH_STYLE_ADD_ROLES)) {
	  ami->saw_add_roles++;
	  log_msg((LOG_TRACE_LEVEL, "Module spec has add_roles"));
	}

#ifdef NOTDEF
	for (i = 0; i < n; i++) {
	  if (strncaseprefix(style_args[i], "password", 4)
		  || strcaseeq(style_args[i], "passwd"))
		am->style |= AUTH_STYLE_PASSWORD;
	  else if (streq(style_args[i], "simple"))
		am->style |= AUTH_STYLE_SIMPLE;
	  else if (streq(style_args[i], "infocard"))
		am->style |= AUTH_STYLE_INFOCARD;
	  else if (streq(style_args[i], "minfocard"))
		am->style |= AUTH_STYLE_MINFOCARD;
	  else if (streq(style_args[i], "sinfocard"))
		am->style |= AUTH_STYLE_SINFOCARD;
	  else if (strncaseprefix(style_args[i], "certificate", 4))
		am->style |= AUTH_STYLE_CERT;
	  else if (strncaseprefix(style_args[i], "digest", 6))
		am->style |= AUTH_STYLE_DIGEST;
	  else if (strncaseprefix(style_args[i], "cas", 3))
		am->style |= AUTH_STYLE_CAS;
	  else if (strcaseeq(style_args[i], "expr")) {
		if ((am->style & AUTH_STYLE_PASSWORD)
			|| (am->style && AUTH_STYLE_SIMPLE)
			|| (am->style && AUTH_STYLE_INFOCARD)
			|| (am->style && AUTH_STYLE_MINFOCARD)
			|| (am->style && AUTH_STYLE_SINFOCARD)
			|| (am->style && AUTH_STYLE_CAS)
			|| (am->style && AUTH_STYLE_CERT)
			|| (am->style && AUTH_STYLE_NATIVE)
			|| (am->style && AUTH_STYLE_PROMPTED)) {
		  log_msg((LOG_ERROR_LEVEL, "STYLE expr cannot be combined"));
		  return(NULL);
		}
		if ((expr = conf_vartab_val(auth_vartab, CONF_AUTH_EXPR)) != NULL)
		  am->expr = expr;
		else {
		  log_msg((LOG_ERROR_LEVEL,
				   "EXPR directive is required with STYLE \"expr\""));
		  return(NULL);
		}
		am->style |= AUTH_STYLE_EXPR;
		ami->saw_expr++;
	  }
	  else if (strncaseprefix(style_args[i], "native", 3)) {
		am->style |= AUTH_STYLE_NATIVE;
		ami->saw_native++;
	  }
	  else if (strncaseprefix(style_args[i], "prompted", 6)) {
		am->style |= AUTH_STYLE_PROMPTED;
		ami->saw_prompted++;
	  }
	  else if (strcaseeq(style_args[i], "set_roles")) {
		am->role_control = AUTH_SET_ROLES;
		ami->saw_set_roles++;
	  }
	  else if (strcaseeq(style_args[i], "add_roles")) {
		am->role_control = AUTH_ADD_ROLES;
		ami->saw_add_roles++;
	  }
	  else {
		log_msg((LOG_ERROR_LEVEL,
				 "Invalid AUTH_STYLE directive: %s", style_arg_str));
		log_msg((LOG_ERROR_LEVEL,
				 "Unrecognized style keyword \"%s\"", style_args[i]));
		return(NULL);
	  }
	}
#endif

	if ((url = conf_vartab_val(auth_vartab, CONF_AUTH_URL_EVAL)) != NULL)
	  am->url_eval = url;
	else if ((url = conf_vartab_val(auth_vartab, CONF_AUTH_URL)) != NULL)
	  am->url = url;
	else if (am->style != AUTH_STYLE_EXPR) {
	  log_msg((LOG_ERROR_LEVEL,
			   "One of the directives URL or URL* is required"));
	  return(NULL);
	}

	if (control_flag_arg == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid AUTH_CONTROL directive (no control)"));
	  return(NULL);
	}
	if (strncaseprefix(control_flag_arg, "required", 7)) {
	  am->control_flag = AUTH_REQUIRED;
	  ami->have_req_auth++;
	}
	else if (strncaseprefix(control_flag_arg, "requisite", 9)) {
	  am->control_flag = AUTH_REQUISITE;
	  ami->have_req_auth++;
	}
	else if (strncaseprefix(control_flag_arg, "optional", 3))
	  am->control_flag = AUTH_OPTIONAL;
	else if (strncaseprefix(control_flag_arg, "sufficient", 4))
	  am->control_flag = AUTH_SUFFICIENT;
	else if (strncaseprefix(control_flag_arg, "user_sufficient", 9))
	  am->control_flag = AUTH_USER_SUFFICIENT;
	else {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid AUTH_CONTROL directive: %s", control_flag_arg));
	  return(NULL);
	}

	am->predicate = conf_vartab_val(auth_vartab, CONF_AUTH_PREDICATE);

	if ((p = conf_vartab_val(auth_vartab, CONF_AUTH_FLAGS)) != NULL) {
	  if ((n = mkargv(p, NULL, &am->flags)) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "Invalid AUTH_FLAGS directive: %s", p));
		return(NULL);
	  }
	}

	/*
	 * Process each OPTION, checking for errors and keeping the original
	 * OPTION string.
	 * XXX Check for proper encoding, etc.
	 */
	for (v = conf_vartab_var(auth_vartab, CONF_AUTH_OPTION);
		 v != NULL; v = v->next) {
	  char *o_name, *o_val;

	  log_msg((LOG_NOTICE_LEVEL, "OPTION %s", v->val));
	  if (am->kwv_options == NULL) {
		am->kwv_options = kwv_init(10);
		/* Do not allow duplicate OPTIONs */
		kwv_set_mode(am->kwv_options, "dn");
	  }

	  if (kwv_parse_qstr(v->val, &o_name, &o_val, NULL, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid OPTION value: \"%s\"", v->val));
		return(NULL);
	  }

	  log_msg((LOG_TRACE_LEVEL, "name=\"%s\", value=\"%s\"", o_name, o_val));
	  am->kwv_options = kwv_add_nocopy(am->kwv_options, o_name, o_val);
	  if (am->kwv_options == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Error processing option: %s", v->val));
		return(NULL);
	  }
	  if (am->options == NULL)
		am->options = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(am->options, v->val);
	}

	/*
	 * Get each OPTION* directive for processing later if/when the
	 * module is run.
	 */
	for (v = conf_vartab_var(auth_vartab, CONF_AUTH_OPTION_EVAL);
		 v != NULL; v = v->next) {
	  log_msg((LOG_NOTICE_LEVEL, "OPTION* %s", v->val));
	  if (am->options_eval == NULL)
		am->options_eval = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(am->options_eval, v->val);
	}

	for (v = conf_vartab_var(auth_vartab, CONF_AUTH_PASSWORD_AUDIT);
		 v != NULL; v = v->next) {
	  Auth_password_audit *pa;
	  Strfields *fields;

	  if (v->val == NULL || *v->val == '\0') {
		log_msg((LOG_ERROR_LEVEL, "Invalid directive: %s", v->name));
		return(NULL);
	  }

	  if ((fields = strpfields(v->val, NULL)) == NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "Error parsing PASSWORD_AUDIT directive: \"%s\"", v->val));
		return(NULL);
	  }
	  if (fields->argc == 1) {
		pa = ALLOC(Auth_password_audit);
		pa->varname = fields->argv[0];
		pa->constraints = NULL;
	  }
	  else if (fields->argc == 2) {
		pa = ALLOC(Auth_password_audit);
		pa->varname = fields->argv[0];
		pa->constraints = fields->argv[1];
	  }
 	  else {
		log_msg((LOG_ERROR_LEVEL,
				 "Error parsing PASSWORD_AUDIT directive: \"%s\"", v->val));
		return(NULL);
	  }
	  if (am->dsv_audits == NULL)
		am->dsv_audits = dsvec_init(NULL, sizeof(Auth_password_audit *));
	  log_msg((LOG_DEBUG_LEVEL,
			   "Will audit variable \"%s\" using constraints: \"%s\"",
			   pa->varname,
			   pa->constraints == NULL ? "default" : pa->constraints));
	  dsvec_add_ptr(am->dsv_audits, pa);
	}

	am->result = 0;
	am->next = NULL;

	for (mp = &ami->modules; *mp != NULL; mp = &(*mp)->next)
	  ;
	*mp = am;
	ami->mcount++;
  }

  return(ami);
}

/*
 * Authenticate USERNAME/PASSWORD within JURISDICTION.
 * Return 1 if authentication succeeds, 0 if it does not, and -1 if
 * an error occurs.
 */
static int
call_auth_module(Auth_module *auth_module, char *username, char *password,
				 char *aux, char *jurisdiction, Kwv *kwv,
				 Http_auth_authorization *aa, char *xmlToken,
				 Cas_auth *cas_auth, Ic_auth *ic_auth,
				 char *client_cert, Kwv *kwv_trans,
				 char **mapped_username, char **lifetime, char **role_str,
				 int *role_rc, Auth_prompts **prompts, Common_status *status)
{
  int i, reply_len, st;
  char *assigned_username, *cookies[2], *reply;
  Auth_module_desc *desc;
  Auth_module_id module_id;
  Auth_reply *auth_reply;
  Credentials *admin_cr;
  Dsvec *dsv;
  Http_params *params;
  Kwv *kwv_options, *kwv_std;

  st = -2;
  assigned_username = NULL;
  if ((desc = auth_module_lookup_desc_by_name(auth_module->url)) != NULL) {
	module_id = desc->id;
	log_msg((LOG_INFO_LEVEL, "Using built-in module: %s",
			 desc->canonical_name));
  }
  else {
	module_id = AUTH_MODULE_UNKNOWN;
	log_msg((LOG_INFO_LEVEL, "Using external module: %s", auth_module->url));
  }

  if (module_id == AUTH_MODULE_PASSWD) {
#ifdef ENABLE_PASSWD_AUTH
	st = local_passwd_auth(username, password, aux);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_SIMPLE) {
#ifdef ENABLE_SIMPLE_AUTH
	st = local_simple_auth(username, password, aux);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_UNIX) {
#ifdef ENABLE_UNIX_AUTH
	st = local_unix_auth(username, password, aux);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_GRID) {
#ifdef ENABLE_GRID_AUTH
	st = local_grid_auth(username, password, aux);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_INFOCARD) {
#ifdef ENABLE_INFOCARD_AUTH
	int self_issued;

	st = local_infocard_auth(xmlToken, aux, ic_auth);
	if (st != -1) {
	  self_issued = (ic_auth->type == IC_SELF_ISSUED_TYPE);
	  assigned_username = ic_auth->username;
	  if (role_str != NULL && ic_auth->roles != NULL) {
		*role_str = ic_auth->roles;
		*role_rc = 1;
	  }

	  if (auth_module->style & AUTH_STYLE_SINFOCARD) {
		if (!self_issued) {
		  st = -1;
		  log_msg((LOG_ERROR_LEVEL,
				   "Require self-issued InfoCard, got managed InfoCard"));
		}
	  }

	  if (auth_module->style & AUTH_STYLE_MINFOCARD) {
		if (self_issued) {
		  st = -1;
		  log_msg((LOG_ERROR_LEVEL,
				   "Require managed InfoCard, got self-issued InfoCard"));
		}
	  }

	  /* Change to the actual InfoCard type. */
	  auth_module->style &= (~AUTH_STYLE_INFOCARD);
	  if (self_issued)
		auth_module->style |= AUTH_STYLE_SINFOCARD;
	  else
		auth_module->style |= AUTH_STYLE_MINFOCARD;
	}
	else
	  assigned_username = NULL;
#endif
  }
  else if (module_id == AUTH_MODULE_HTTP) {
#ifdef ENABLE_HTTP_AUTH
	Kwv *kwv;
	Kwv_iter *iter;
	Kwv_pair *option;
	Url_auth url_auth;

	url_auth.method = HTTP_POST_METHOD;
	url_auth.url = NULL;
	url_auth.username_parameter = NULL;
	url_auth.password_parameter = NULL;
	url_auth.options = NULL;

	if ((kwv = auth_module->kwv_options) != NULL) {
	  iter = kwv_iter_begin(kwv, NULL);
	  for (option = kwv_iter_first(iter); option != NULL;
		   option = kwv_iter_next(iter)) {
		if (streq(option->name, "AUTH_URL"))
		  url_auth.url = option->val;
		else if (streq(option->name, "AUTH_METHOD")) {
		  if ((url_auth.method = http_string_to_method(option->val))
			  == HTTP_UNKNOWN_METHOD) {
			log_msg((LOG_ERROR_LEVEL, "Unrecognized AUTH_METHOD: \"%s\"",
					 option->val));
			return(-1);
		  }
		}
		else if (streq(option->name, "USERNAME_PARAMETER"))
		  url_auth.username_parameter = option->val;
		else if (streq(option->name, "PASSWORD_PARAMETER"))
		  url_auth.password_parameter = option->val;
		else {
		  if (url_auth.options == NULL)
			url_auth.options = dsvec_init(NULL, sizeof(char *));
		  dsvec_add_ptr(url_auth.options,
						ds_xprintf("%s=%s", option->name, option->val));
		}
	  }
	  kwv_iter_end(iter);
	}

	if (url_auth.url == NULL) {
	  log_msg((LOG_ALERT_LEVEL, "Require AUTH_URL argument to be specified"));
	  return(-1);
	}

	st = local_http_auth(username, password, aux, &url_auth);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_TOKEN) {
#ifdef ENABLE_TOKEN_AUTH
	int accept_window;
	char *p;
	Kwv *kwv;

	accept_window = -1;
	if ((kwv = auth_module->kwv_options) != NULL) {
	  if ((p = kwv_lookup_value(kwv, "ACCEPT_WINDOW")) != NULL) {
		if (strnum(p, STRNUM_UL, &accept_window) == -1 || accept_window < 0) {
		  log_msg((LOG_ALERT_LEVEL, "Invalid ACCEPT_WINDOW: '%s'", p));
		  return(-1);
		}
	  }
	}

	if (accept_window == -1)
	  accept_window = TOKEN_HOTP_DEFAULT_ACCEPT_WINDOW;
	st = local_token_auth(username, password, aux, accept_window);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_NTLM) {
#ifdef ENABLE_NTLM_AUTH
	unsigned long smb_port;
	char *p, *smb_domain, *smb_server;
	Kwv *kwv;

	smb_port = 0;
	smb_server = NULL;
	smb_domain = NULL;

	if ((kwv = auth_module->kwv_options) != NULL) {
	  if ((p = kwv_lookup_value(kwv, "SAMBA_PORT")) != NULL) {
		if (strnum(p, STRNUM_UL, &smb_port) == -1) {
		  log_msg((LOG_ALERT_LEVEL, "Invalid SAMBA_PORT: '%s'", p));
		  return(-1);
		}
	  }
	  if ((p = kwv_lookup_value(kwv, "SAMBA_SERVER")) != NULL)
		smb_server = p;
	  if ((p = kwv_lookup_value(kwv, "SAMBA_DOMAIN")) != NULL)
		smb_domain = p;
	}

	st = local_ntlm_auth(username, password, aux,
						 (int) smb_port, smb_server, smb_domain);
	assigned_username = strdup(username);
#endif
  }
  else if (module_id == AUTH_MODULE_PAM) {
#ifdef ENABLE_PAM_AUTH
	in_port_t port;
	char *errmsg, *hostname, *lifetime, *p, *portname;
	Kwv *kwv;
	Pam_auth_tid *tid;

	/* Both the hostname and port can be missing if the transid is given. */
	hostname = conf_val(CONF_PAMD_HOST);
	portname = NULL;
	if ((kwv = auth_module->kwv_options) != NULL) {
	  if ((p = kwv_lookup_value(kwv, "PAMD_HOST")) != NULL)
		hostname = p;
	  if ((p = kwv_lookup_value(kwv, "PAMD_PORT")) != NULL)
		portname = p;
	}
	if ((port = pam_get_pamd_port(portname, &errmsg)) == 0) {
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	  return(-1);
	}

	tid = NULL;
	*prompts = NULL;
	assigned_username = NULL;
	st = local_pam_auth(kwv_trans, username, hostname, port,
						&assigned_username, &lifetime, NULL, &tid, prompts);
	if (st == 0 && assigned_username != NULL)
	  *mapped_username = assigned_username;
#endif
  }
  else if (module_id == AUTH_MODULE_CAS) {
#ifdef ENABLE_CAS_AUTH
	char *p;
	Kwv *kwv;

	if ((kwv = auth_module->kwv_options) != NULL) {
	  if ((p = kwv_lookup_value(kwv, "CAS_SERVER_URI")) != NULL)
		cas_auth->server_uri = p;
	}

	st = local_cas_auth(username, password, aux, cas_auth);
	if (username == NULL && cas_auth->username != NULL)
	  assigned_username = strdup(cas_auth->username);
	else
	  assigned_username = username;
#endif
  }
  else if (module_id == AUTH_MODULE_APACHE) {
#ifdef ENABLE_APACHE_AUTH
	char *file, *module, *dbm_type, *realm;
	Apache_module mod;
	Apache_dbm dbm;
	Http_auth_authorization *haa;
	Kwv *kwv;

	mod = MOD_AUTH_NONE;
	dbm = DBM_NONE;
	file = NULL;
	realm = NULL;
	haa = NULL;
	if ((kwv = auth_module->kwv_options) != NULL) {
	  if ((module = kwv_lookup_value(kwv, "AUTH_MODULE")) != NULL)
		mod = lookup_module(module);
	  file = kwv_lookup_value(kwv, "AUTH_FILE");
	  if ((dbm_type = kwv_lookup_value(kwv, "DBM_TYPE")) != NULL)
		dbm = lookup_dbm_type(dbm_type);
	  realm = kwv_lookup_value(kwv, "AUTH_REALM");
	}

	if (file == NULL) {
		log_msg((LOG_ALERT_LEVEL, "Require AUTH_FILE to be specified"));
		return(-1);
	}

	if (mod == MOD_AUTH_NONE) {
	  if (module == NULL)
		log_msg((LOG_ALERT_LEVEL, "Require AUTH_MODULE to be specified"));
	  else
		log_msg((LOG_ALERT_LEVEL, "Invalid AUTH_MODULE: \"%s\"", module));
	  return(-1);
	}

	if (mod == MOD_AUTH_DBM) {
	  if (dbm_type == NULL) {
		log_msg((LOG_ALERT_LEVEL, "Require DBM_TYPE to be specified"));
		return(-1);
	  }
	  if (dbm == DBM_NONE) {
		log_msg((LOG_ALERT_LEVEL, "Invalid DBM_TYPE: \"%s\"", dbm_type));
		return(-1);
	  }
	}
	else if (mod == MOD_AUTH_DIGEST) {
	  if (realm == NULL) {
		log_msg((LOG_ALERT_LEVEL, "Require AUTH_REALM to be specified"));
		return(-1);
	  }
	  if ((haa = aa) == NULL) {
		haa = http_auth_authorization_init(username, "Digest", realm);
		haa->password = password;
	  }
	}

	st = local_apache_auth(username, password, aux, haa, mod, dbm, file);
	assigned_username = strdup(username);
#endif
  }

  if (st == 0) {
	if (!is_valid_auth_username(assigned_username) && *prompts == NULL) {
	  init_common_status(status, NULL, NULL, "Invalid DACS_USERNAME");
	  return(-1);
	}
	*lifetime = NULL;
	*mapped_username = assigned_username;
	return(1);
  }
  else if (st == -1) {
	init_common_status(status, NULL, NULL, "Authentication failed");
	return(0);
  }

  dsv = dsvec_init_size(NULL, sizeof(Http_params),
						5 + kwv_count(kwv_trans, NULL));

  /*
   * The arguments need to be determined so that the standard arguments
   * are overridden by OPTION arguments, which are in turn overridden by
   * OPTION* arguments.
   * The OPTION* arguments need to be evaluated in a context that includes
   * the standard arguments overridden by the OPTION arguments.
   * We cannot determine duplicate arguments arising from OPTION* until they
   * have been evaluated since the keyword (argument name) may not be known
   * prior to evaluation.
   */
  kwv_std = kwv_init(8);
  kwv_add_nocopy(kwv_std, "USERNAME", (username != NULL) ? username : "");
  kwv_add_nocopy(kwv_std, "PASSWORD", (password != NULL) ? password : "");
  kwv_add_nocopy(kwv_std, "AUXILIARY", (aux != NULL) ? aux : "");
  kwv_add_nocopy(kwv_std, "DACS_JURISDICTION", jurisdiction);
  kwv_add_nocopy(kwv_std, "DACS_VERSION", DACS_VERSION_NUMBER);

  if (auth_module->kwv_options == NULL)
	auth_module->kwv_options = kwv_init(8);

  /* Override the standard option arguments with any OPTION directives. */
  if (kwv_merge(auth_module->kwv_options, kwv_std, KWV_NO_DUPS) == NULL) {
	init_common_status(status, NULL, NULL, "Internal error: kwv_merge");
	return(-1);
  }

  if (auth_module->options_eval != NULL) {
	for (i = 0; i < dsvec_len(auth_module->options_eval); i++) {
	  char *option, *p, *varname, *varvalue;

	  /*
	   * Evaluate with namespace "Options" set to the merge of kwv_std,
	   * Option directives, and Option* directives evaluated so far.
	   */
	  p = dsvec_ptr(auth_module->options_eval, i, char *);
	  st = auth_eval(p, kwv, kwv_auth, auth_module->kwv_options, &option);
	  if (st != ACS_EXPR_TRUE || option == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid OPTION* directive: %s", p));
		init_common_status(status, NULL, NULL, "OPTION* evaluation failed");
		return(-1);
	  }

	  if (kwv_parse_qstr(option, &varname, &varvalue, NULL, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid OPTION* directive: %s", p));
		init_common_status(status, NULL, NULL, "OPTION* evaluation failed");
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "OPTION* sets \"%s\" to \"%s\"",
			   varname, varvalue));

	  /* OPTION* directives override everything else. */
	  if (kwv_replace(auth_module->kwv_options, varname, varvalue) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Error processing OPTION*: %s", p));
		init_common_status(status, NULL, NULL, "OPTION* evaluation failed");
		return(-1);
	  }
	}
  }

  kwv_options = auth_module->kwv_options;

  if (kwv_options != NULL) {
	Kwv_iter *iter;
	Kwv_pair *v;

	iter = kwv_iter_begin(kwv_options, NULL);
	for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter)) {
	  params = http_param(dsv, v->name, v->val, NULL, 0);
	}
	kwv_iter_end(iter);
  }

  if (client_cert != NULL) {
	int nargs;
	extern char **environ;

	/*
	 * Pass all mod_ssl SSL environment variables, including SSL_CLIENT_CERT
	 * which is to be verified by the local authentication module.
	 */
	nargs = 0;
	for (i = 0; environ[i] != NULL; i++) {
	  if (strneq(environ[i], "SSL_", 4)) {
		params = http_param(dsv, NULL, NULL, NULL, 0);
		env_parse(environ[i], &params->name, &params->value);
		nargs++;
	  }
	}

	log_msg((LOG_DEBUG_LEVEL, "Passing %d SSL environment vars", nargs));
  }

  for (i = 0; i < kwv_count(kwv_trans, NULL); i++) {
	params = http_param(dsv, kwv_trans->pairs[i]->name,
						kwv_trans->pairs[i]->val, NULL, 0);
  }

  /*
   * We need to pass the directives in the <Auth> section as arguments so
   * that the module can know how it's been configured, even if it's on a
   * different system.
   *
   * XXX shouldn't the information be packed up
   * and encoded... for example, multiple OPTION directives cause a problem
   * if the recipient doesn't handle duplicate kwv keys...
   * XXX What if xxx_eval type directives are passed?
   */
  if (auth_module->auth_vartab != NULL) {
	int j, need_unique;
	Kwv_pair *v;
	Kwv_vartab *vt;

	for (i = 0; auth_module->auth_vartab[i].name != NULL; i++) {
	  vt = &auth_module->auth_vartab[i];
	  need_unique = (vt->pair != NULL && vt->pair->next != NULL);
	  j = 1;
	  for (v = vt->pair; v != NULL; v = v->next) {
		params = http_param(dsv, NULL, v->val, NULL, 0);
		if (need_unique)
		  params->name = ds_xprintf("%s.%d", v->name, j++);
		else
		  params->name = v->name;
	  }
	}
  }

  if (cas_auth != NULL) {
	if (cas_auth->redirect_args != NULL) {
	  params = http_param(dsv, "CAS_REDIRECT_ARGS", cas_auth->redirect_args,
						  NULL, 0);
	}
	if (cas_auth->session_ticket != NULL) {
	  params = http_param(dsv, "CAS_TICKET", cas_auth->session_ticket,
						  NULL, 0);
	}
  }

  if (username != NULL)
	log_msg((LOG_DEBUG_LEVEL, "Authenticating \"%s\"...", username));
  else
	log_msg((LOG_DEBUG_LEVEL, "Authenticating (no username)..."));
  log_msg((LOG_DEBUG_LEVEL, "with %d POST parameters", dsvec_len(dsv)));

  admin_cr = make_admin_credentials();
  credentials_to_auth_cookies(admin_cr, &cookies[0]);
  cookies[1] = NULL;

  reply_len = -1;
  if (http_invoke(auth_module->url, HTTP_POST_METHOD,
				  ssl_verify ? HTTP_SSL_ON_VERIFY
				  : (use_ssl ? HTTP_SSL_ON : HTTP_SSL_URL_SCHEME),
				  dsvec_len(dsv), (Http_params *) dsvec_base(dsv),
				  NULL, cookies, &reply, &reply_len, NULL, NULL) == -1) {
	init_common_status(status, NULL, NULL, reply);
	return(-1);
  }

  if (reply_len == 0) {
	init_common_status(status, NULL, NULL,
					   "auth module returned 0 bytes: no XML document");
	return(-1);
  }

  log_msg((LOG_DEBUG_LEVEL, "authentication module returned: %s", reply));

  if (parse_xml_auth_reply(reply, &auth_reply) == -1) {
	init_common_status(status, NULL, NULL, "parse_xml_auth_reply error");
	return(-1);
  }

  if (auth_reply->ok != NULL) {
	Roles_reply *rr;

	if (auth_reply->ok->lifetime != NULL
		&& auth_reply->ok->lifetime[0] != '\0')
	  *lifetime = strdup(auth_reply->ok->lifetime);
	else
	  *lifetime = NULL;

	/* Don't completely trust the local auth service... */
	if (!is_valid_auth_username(auth_reply->ok->username)) {
	  init_common_status(status, NULL, NULL,
						"Invalid DACS_USERNAME returned by local auth!");
	  return(-1);
	}

	*mapped_username = strdup(auth_reply->ok->username);

	/*
	 * If the caller is willing to accept roles information, return it
	 * if it is available.
	 */
	if (role_str != NULL) {
	  *role_str = "";
	  *role_rc = 0;
	  if ((rr = auth_reply->ok->roles_reply) != NULL) {
		if (rr->ok != NULL) {
		  if (rr->ok->roles != NULL) {
			log_msg((LOG_DEBUG_LEVEL,
					 "authentication module returned roles: %s",
					 rr->ok->roles));
			/* Don't completely trust the local auth service... */
			if (is_valid_role_str(rr->ok->roles)) {
			  *role_rc = 1;
			  *role_str = rr->ok->roles;
			}
			else
			  log_msg((LOG_WARN_LEVEL, "Invalid roles - ignoring"));
		  }
		}
		else if (rr->failed != NULL) {
		  if (rr->failed->reason != NULL)
			init_common_status(status, NULL, NULL, rr->failed->reason);
		  else
			init_common_status(status, NULL, NULL,
							   "Role string is unavailable");
		}
		else if (rr->status != NULL) {
		  *status = *rr->status;
		  *role_rc = -1;
		}
		else {
		  init_common_status(status, NULL, NULL, "Invalid roles_reply result");
		  *role_rc = -1;
		}
	  }
	}

	return(1);
  }

  if (auth_reply->failed != NULL) {
	if (auth_reply->failed->username != NULL)
	  *mapped_username = auth_reply->failed->username;
	if (auth_reply->failed->redirect_url != NULL) {
	  if (cas_auth != NULL)
		cas_auth->redirect_url = auth_reply->failed->redirect_url;
	}
	init_common_status(status, NULL, NULL, "Authentication failed");
	return(0);
  }

  if (auth_reply->status != NULL) {
	*status = *auth_reply->status;
	return(-1);
  }

  if (auth_reply->prompts != NULL) {
	*prompts = auth_reply->prompts;
	return(1);
  }

  init_common_status(status, NULL, NULL, "Internal authentication error");
  return(-1);
}

typedef struct Auth_state {
  char *mapped_username;
  enum { AUTH_FAILING, AUTH_OK } state;
  int success_count;
  int mindex;

  Auth_module *current_module;
  Auth_module *modules;
  char *username;
  char *jurisdiction;
  char *password;
  char *aux;
  Kwv *kwv, *kwv_trans;
  int browser;
  char *format_arg;
  int enable_auth_handlers;
  char *dacs_error_url;
  Cas_auth *cas_auth;
  char *dacs_request_method;
  char *lifetime;
  int got_authorization;
  Http_auth_authorization *aa;
  int role_rc;
  char *role_str;
  char *auth_user;
  char *auth_password;
  char *auth_aux;
  Common_status common_status;
  Auth_prompts *prompts;
  Auth_failure_reason failure_reason;
} Auth_state;

static char *
describe_auth_module(Auth_module *am)
{
  int j;
  Ds ds;

  ds_init(&ds);
  if (am->id != NULL)
	ds_asprintf(&ds, "id=\"%s\", ", am->id);

  if (am->url != NULL)
	ds_asprintf(&ds, "url=\"%s\", ", am->url);
  else if (am->url_eval != NULL)
	ds_asprintf(&ds, "url_eval=\"%s\", ", am->url_eval);
  else if (am->expr != NULL)
	ds_asprintf(&ds, "expr=\"%s\", ", am->expr);

  ds_asprintf(&ds, "style=\"(");
  if (am->style & AUTH_STYLE_PASSWORD)
	ds_asprintf(&ds, "+pass");
  if (am->style & AUTH_STYLE_SIMPLE)
	ds_asprintf(&ds, "+simple");
  if (am->style & AUTH_STYLE_INFOCARD)
	ds_asprintf(&ds, "+infocard");
  if (am->style & AUTH_STYLE_MINFOCARD)
	ds_asprintf(&ds, "+managed_infocard");
  if (am->style & AUTH_STYLE_SINFOCARD)
	ds_asprintf(&ds, "+selfissued_infocard");
  if (am->style & AUTH_STYLE_DIGEST)
	ds_asprintf(&ds, "+digest");
  if (am->style & AUTH_STYLE_CERT)
	ds_asprintf(&ds, "+cert");
  if (am->style & AUTH_STYLE_NATIVE)
	ds_asprintf(&ds, "+native");
  if (am->style & AUTH_STYLE_PROMPTED)
	ds_asprintf(&ds, "+prompt");

  ds_asprintf(&ds, ")\", roles=\"(");
  if (am->style & AUTH_STYLE_SET_ROLES)
	ds_asprintf(&ds, "+set_roles");
  else if (am->style & AUTH_STYLE_ADD_ROLES)
	ds_asprintf(&ds, "+add_roles");
  else
	ds_asprintf(&ds, "default");

  ds_asprintf(&ds, ")\", control=");
  if (am->control_flag == AUTH_REQUISITE)
	ds_asprintf(&ds, "\"requisite\"");
  else if (am->control_flag == AUTH_REQUIRED)
	ds_asprintf(&ds, "\"required\"");
  else if (am->control_flag == AUTH_SUFFICIENT)
	ds_asprintf(&ds, "\"sufficient\"");
  else if (am->control_flag == AUTH_USER_SUFFICIENT)
	ds_asprintf(&ds, "\"user_sufficient\"");
  else if (am->control_flag == AUTH_OPTIONAL)
	ds_asprintf(&ds, "\"optional\"");

  if (am->flags != NULL && am->flags[0] != NULL) {
	ds_asprintf(&ds, ", flags=\"");
	for (j = 0; am->flags[j] != NULL; j++)
	  ds_asprintf(&ds, "%s\"%s\"", (j != 0) ? " " : "", am->flags[j]);
	ds_asprintf(&ds, "\"");
  }

  if (am->options != NULL) {
	char **options;

	options = (char **) dsvec_base(am->options);
	for (j = 0; j < dsvec_len(am->options); j++)
	  ds_asprintf(&ds, ", option=\"%s\"", options[j]);
  }

  if (am->options_eval != NULL) {
	char **options;

	options = (char **) dsvec_base(am->options_eval);
	for (j = 0; j < dsvec_len(am->options_eval); j++)
	  ds_asprintf(&ds, ", option_eval=\"%s\"", options[j]);
  }

  return(ds_buf(&ds));
}

static Auth_error_handler *
lookup_error_handler(Auth_failure_reason reason)
{
  int n;
  char **argv;
  Kwv_pair *v;
  static Auth_error_handler current;

  current.type = ERROR_HANDLER_REASON;
  current.error_code = NULL;
  current.error_value = 0;
  current.error_string = NULL;

  if ((v = conf_var(CONF_AUTH_ERROR_HANDLER)) == NULL)
	return(&current);

  while (v != NULL) {
	Mkargv conf = { 1, 0, NULL, NULL, NULL };

	if ((n = mkargv(v->val, &conf, &argv)) != 2 && n != 3) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid directive: %s \"%s\"", v->name, v->val));
	  return(NULL);
	}

	if (streq(argv[0], "*")) {
	  current.error_code = argv[0];
	  break;
	}
	else {
	  unsigned long ec;

	  if (strnum(argv[0], STRNUM_UL, &ec) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "Invalid error code in AUTH_ERROR_HANDLER directive"));
		return(NULL);
	  }

	  if (ec == reason) {
		current.error_code = argv[0];
		current.error_value = ec;
		break;
	  }
	}

	v = v->next;
  }

  if (v != NULL) {
	if (n == 3) {
	  current.error_string = argv[2];
	  if (strcaseeq(argv[1], "url"))
		current.type = ERROR_HANDLER_URL;
	  else if (strcaseeq(argv[1], "file"))
		current.type = ERROR_HANDLER_FILE;
	  else if (strcaseeq(argv[1], "message"))
		current.type = ERROR_HANDLER_MESSAGE;
	  else {
		log_msg((LOG_ERROR_LEVEL,
				 "Error parsing AUTH_ERROR_HANDLER directive: %s", v->val));
		return(NULL);
	  }
	  log_msg((LOG_INFO_LEVEL, "Using error handler: %s %s %s",
			   argv[0], argv[1], argv[2]));
	}
	else {
	  current.error_string = argv[1];
	  if (strcaseprefix(argv[1], "http"))
		current.type = ERROR_HANDLER_URL;
	  else if (argv[1][0] == '/')
		current.type = ERROR_HANDLER_FILE;
	  else if (strcaseeq(argv[1], "reason"))
		current.type = ERROR_HANDLER_REASON;
	  else if (argv[1][0] == '"')
		current.type = ERROR_HANDLER_MESSAGE;
	  else {
		log_msg((LOG_ERROR_LEVEL,
				 "Error parsing AUTH_ERROR_HANDLER directive: %s", v->val));
		return(NULL);
	  }
	  log_msg((LOG_INFO_LEVEL, "Using error handler: %s %s",
			   argv[0], argv[1]));
	}
  }

  return(&current);
}

static Auth_success_handler *
lookup_success_handler(void)
{
  int n;
  char **argv;
  Kwv_pair *v;
  Mkargv conf = { 1, 0, NULL, NULL, NULL };
  static Auth_success_handler current;

  current.type = SUCCESS_HANDLER_CREDENTIALS;
  current.success_string = NULL;

  if ((v = conf_var(CONF_AUTH_SUCCESS_HANDLER)) == NULL)
	return(&current);

  if ((n = mkargv(v->val, &conf, &argv)) != 1 && n != 2) {
	log_msg((LOG_ERROR_LEVEL, "Invalid directive: %s", v->val));
	return(NULL);
  }

  if (n == 2) {
	current.success_string = argv[1];
	if (strcaseeq(argv[0], "url"))
	  current.type = SUCCESS_HANDLER_URL;
	else if (strcaseeq(argv[0], "file"))
	  current.type = SUCCESS_HANDLER_FILE;
	else if (strcaseeq(argv[0], "message"))
	  current.type = SUCCESS_HANDLER_MESSAGE;
	else {
	  log_msg((LOG_ERROR_LEVEL,
			   "Error parsing AUTH_ERROR_HANDLER directive: %s", v->val));
	  return(NULL);
	}
	log_msg((LOG_INFO_LEVEL, "Using success handler: %s %s",
			 argv[0], argv[1]));
  }
  else {
	current.success_string = argv[0];
	if (strcaseprefix(argv[0], "http"))
	  current.type = SUCCESS_HANDLER_URL;
	else if (argv[0][0] == '/')
	  current.type = SUCCESS_HANDLER_FILE;
	else if (strcaseeq(argv[0], "credentials"))
	  current.type = SUCCESS_HANDLER_CREDENTIALS;
	else if (argv[0][0] == '"')
	  current.type = SUCCESS_HANDLER_MESSAGE;
	else {
	  log_msg((LOG_ERROR_LEVEL,
			   "Error parsing AUTH_SUCCESS_HANDLER directive: %s", v->val));
	  return(NULL);
	}

	log_msg((LOG_INFO_LEVEL, "Using success handler: %s", argv[0]));
  }

  return(&current);
}

extern Kwv_vartab conf_vartab[];

/*
 * Parse a command-line argument string authentication module specification
 * Module specification syntax:
 *   auth-module-spec := module style control [-Of file] [-Oname=value]*
 *                           [-vfs VFS]*
 *   module := abs-url | builtin-module-name
 *
 * The starting vector index into ARGV is IND.
 * If a valid module spec is found, return it and set IND to the vector index
 * of the next element, otherwise return -1 if this is an invalid module spec.
 *
 * Required Auth clause directives: CONTROL, STYLE
 *   o if STYLE == "expr", EXPR
 *   o if STYLE != "expr", URL or URL*
 *
 * Also update the Auth_module_info, which describes a chain of one or more
 * ordered authentication modules.
 */
Auth_module *
auth_module_config(char **argv, int *ind, Auth_module_info *ami)
{
  int i;
  Auth_module *am;
  Auth_module_desc *desc;

  am = new_auth_module(NULL);

  i = *ind;
  am->argv_spec = dsvec_init(NULL, sizeof(char *));

  /*
   * The first component identifies a) a builtin module (which implies an
   * authentication style) OR b) a URL.
   */
  if ((desc = auth_module_lookup_desc_by_name(argv[i])) != NULL)
	am->url = desc->canonical_name;
  else {
	Uri *url;

	/* A URL for an external authentication module. */
	if ((url = http_parse_uri(argv[i])) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid auth module URL specification"));
	  return(NULL);
	}
	am->url = argv[i];
  }

  /*
   * Build a canonical representation of the module specification, in case
   * the module spec needs to be reinterpreted (e.g., by dacsauth_main()).
   * First comes the authentication module/URL.
   */
  dsvec_add_ptr(am->argv_spec, am->url);

  if (argv[++i] == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Incomplete module specification, expected style specifier"));
	return(NULL);
  }

  /* Next comes the STYLE specifier. */
  if ((am->style = auth_style_from_string(argv[i])) == AUTH_STYLE_UNKNOWN) {
	log_msg((LOG_ERROR_LEVEL,
			 "Invalid module specification, invalid style specifier: \"%s\"",
			 argv[i]));
	return(NULL);
  }

  /* Add the authentication style keyword(s). */
  dsvec_add_ptr(am->argv_spec, auth_style_to_string(am->style));

  /* Next comes the CONTROL specifier. */
  if (argv[++i] == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Incomplete module specification, expected control specifier"));
	return(NULL);
  }

  if (strncaseprefix(argv[i], "required", 7)) {
	am->control_flag = AUTH_REQUIRED;
	am->have_req_auth++;
	dsvec_add_ptr(am->argv_spec, "required");
  }
  else if (strncaseprefix(argv[i], "requisite", 9)) {
	am->control_flag = AUTH_REQUISITE;
	am->have_req_auth++;
	dsvec_add_ptr(am->argv_spec, "requisite");
  }
  else if (strncaseprefix(argv[i], "optional", 3)) {
	am->control_flag = AUTH_OPTIONAL;
	dsvec_add_ptr(am->argv_spec, "optional");
  }
  else if (strncaseprefix(argv[i], "sufficient", 4)) {
	am->control_flag = AUTH_SUFFICIENT;
	dsvec_add_ptr(am->argv_spec, "sufficient");
  }
  else if (strncaseprefix(argv[i], "user_sufficient", 9)) {
	am->control_flag = AUTH_USER_SUFFICIENT;
	dsvec_add_ptr(am->argv_spec, "user_sufficient");
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "Invalid control directive: \"%s\"", argv[i]));
	return(NULL);
  }

  i++;
  while (argv[i] != NULL) {
	if (streq(argv[i], "-Of")) {
	  if (argv[++i] == NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "Auth module's filename argument is missing"));
		return(NULL);
	  }
	  if (auth_get_options_from_file(argv[i], am->kwv_options, am->options,
									 am->argv_spec) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "Error loading auth options from \"%s\"", argv[i]));
		return(NULL);
	  }
	}
	else if (strprefix(argv[i], "-O") != NULL) {
	  char *varname, *varvalue;

	  /* An OPTION: -Oname=value */
	  if (auth_add_option(&argv[i][2], am->kwv_options, am->options,
						  am->argv_spec) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid auth module option: %s", argv[i]));
		return(NULL);
	  }
	}
	else if (streq(argv[i], "-expr")) {
	  if (am->expr != NULL) {
		log_msg((LOG_ERROR_LEVEL, "Only one EXPR can be specified per module"));
		return(NULL);
	  }
	  if (argv[++i] == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Module's EXPR argument is missing"));
		return(NULL);
	  }
	  am->expr = argv[i];
	  dsvec_add_ptr(am->argv_spec, "-expr");
	  dsvec_add_ptr(am->argv_spec, argv[i]);
	}
	else if (streq(argv[i], "-vfs")) {
	  Vfs_directive *vd;

	  if (argv[++i] == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Module's VFS argument is missing"));
		return(NULL);
	  }

	  /* A VFS spec */
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid vfs_uri"));
		return(NULL);
	  }
	  conf_set_directive(am->kwv_conf, "VFS", argv[i]);
	  dsvec_add_ptr(am->argv_spec, "-vfs");
	  dsvec_add_ptr(am->argv_spec, argv[i]);
	}
	else
	  break;

	i++;
  }

  if ((am->style & AUTH_STYLE_EXPR) && am->expr == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Module's EXPR argument is missing"));
	return(NULL);
  }
  if ((am->style & AUTH_STYLE_EXPR) == 0 && am->expr != NULL) {
	log_msg((LOG_ERROR_LEVEL, "Module does not accept an EXPR argument"));
	return(NULL);
  }

  *ind = i;

  if (ami != NULL) {
	ami->mcount++;
	ami->have_req_auth += am->have_req_auth;
	if ((am->style & AUTH_STYLE_EXPR))
	  ami->saw_expr++;
	else if ((am->style & AUTH_STYLE_NATIVE))
	  ami->saw_native++;
	else if ((am->style & AUTH_STYLE_PROMPTED))
	  ami->saw_prompted++;
	else if ((am->style & AUTH_STYLE_SET_ROLES))
	  ami->saw_set_roles++;
	else if ((am->style & AUTH_STYLE_ADD_ROLES))
	  ami->saw_add_roles++;
  }

  return(am);
}

static int
load_pam_auth_item(char *name, char **buf)
{
  Vfs_directive *vd;
  Vfs_handle *handle;

  *buf = NULL;
  if ((vd = vfs_lookup_item_type(ITEM_TYPE_LOCAL_PAM_AUTHENTICATE)) == NULL)
    return(-1);

  if (vfs_open(vd, &handle) == -1)
    return(-1);

  if (vfs_get(handle, name, (void *) buf, NULL) == -1) {
    vfs_close(handle);
    return(-1);
  }

  if (vfs_close(handle) == -1)
    return(-1);

  return(0);
}

int
auth_add_option(char *option_str, Kwv *kwv_options, Dsvec *options,
				Dsvec *argv_spec)
{
  char *flag_str, *varname, *varvalue;

  if (kwv_parse_str(option_str, &varname, &varvalue) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Invalid module option: %s", option_str));
	return(-1);
  }

  kwv_add_nocopy(kwv_options, varname, varvalue);

  flag_str = ds_xprintf("-O%s", option_str);
  dsvec_add_ptr(options, flag_str);
  dsvec_add_ptr(argv_spec, flag_str);

  return(0);
}

/*
 * For the command-line utility and HTTP_AUTH directive, get options
 * (as in -O<varname>=<varvalue>) from FILENAME, one per line, and assign
 * to KWV.  The "-O" flag prefix is elided from the beginning of each line.
 * All quotes are preserved and not interpreted here.
 */
int
auth_get_options_from_file(char *filename, Kwv *kwv_options, Dsvec *options,
						   Dsvec *argv_spec)
{
  int i;
  Ds *ds;
  Dsvec *dsv;

  ds = ds_init(NULL);

  if (ds_load_file(ds, filename) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot read \"%s\"", filename));
	ds_free(ds);
	return(-1);
  }

  if ((dsv = dsvec_lines(NULL, ds_buf(ds))) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot parse \"%s\"", filename));
	ds_free(ds);
	return(-1);
  }

  for (i = 0; i < dsvec_len(dsv); i++) {
	char *line;

	line = dsvec_ptr(dsv, i, char *);
	if (*line == '\0' || *line == '#')
	  continue;
	if (auth_add_option(line, kwv_options, options, argv_spec) == -1)
	  return(-1);
  }

  return(0);
}

static Auth_out *dacsauth_out = NULL;

/*
 * Display usage message and exit, if invoked as a utility; otherwise
 * just return a non-zero value.
 */
static int
usage(void)
{

  if (dacsauth_out == NULL)
	return(1);

  fprintf(stderr,
		  "Usage: dacsauth [-m auth-module-spec]* [-r roles-module-spec]* [flag ...]\n");
  fprintf(stderr,
		  "auth-module-spec: module [style] control [-Oname=value]* [-vfs VFS]*\n");
  fprintf(stderr,
		  "roles-module-spec: module control [-Oname=value]* [-vfs VFS]*\n");
  fprintf(stderr, "A flag is:\n");
  fprintf(stderr, "-Ddirective=value: process a config directive\n");
  fprintf(stderr, "-aux:              switch to input aux string\n");
  fprintf(stderr, "-fj jurname:       jurisdiction name is jurname\n");
  fprintf(stderr, "-fn fedname:       federation name is fedname\n");
  fprintf(stderr, "-h|-help:          print this usage message and exit\n");
  fprintf(stderr, "-id:               display identity\n");
  fprintf(stderr, "-ll log_level:     set log level\n");
  fprintf(stderr, "-modules:          list builtin modules and exit\n");
  fprintf(stderr, "-p password:       specify the password\n");
  fprintf(stderr, "-pf file:          read password from file (- is stdin)\n");
  fprintf(stderr, "-prompt:           prompt for password\n");
  fprintf(stderr, "-u|-user username: authenticate as username\n");
  fprintf(stderr, "-v:                increase verbosity\n");
  fprintf(stderr, "Flags must appear after the last module specification\n");

  exit(1);
  /*NOTREACHED*/
}

static char *
logging_callback(Log_desc *logd, Log_level level, void *arg)
{
  char *prefix;

  prefix = log_format(logd, level, "[%l]");

  return(prefix);
}

/*
 * If invoked as a function (rather than as the dacsauth(1) command), MAIN_OUT
 * will be non-NULL; return 0 to indicate success, -1 otherwise.
 */
int
dacsauth_main(int argc, char **argv, int do_init, void *main_out)
{
  int browser, i, handler_error, rc, using_cert;
  int role_rc, got_authorization;
  int auth_handlers_enabled, selected;
  int emit_xml, show_identity, qflag, vflag;
  unsigned int fail_delay, remaining_delay;
  char *identity, *mapped_username, *client_cert, *errmsg;
  char *default_lifetime, *lifetime, *remote_addr, *role_str;
  char *auth_type, *auth_lockfile, *ua_str;
  char *next_www_authenticate;
  char *p, *bp, *it, *sp;
  char *federation_name, *jurisdiction_name, *read_data_name, **read_data_ptr;
  Acs_expr_result st;
  Auth_args *args;
  Auth_failure_reason failure_reason;
  Auth_module *m;
  Auth_module_info *ami;
  Auth_prompts *prompts;
  Auth_state auth_state;
  Auth_style auth_style;
  Auth_success_handler *success_handler;
  Cas_auth cas_auth;
  Common_status common_status, saved_common_status;
  Credentials *credentials;
  DACS_app_type app_type;
  Ds *ds_roles;
  Http_auth_authorization *aa;
  Ic_auth ic_auth;
  Kwv *kwv, *kwv_conf, *kwv_dacs, *kwv_trans;
  Kwv_pair *v;
  Log_desc *ld;
  Roles_module_info *rmi;

  /*
   * N.B.: This program must be executed as a "secure" process, meaning it
   * should either be run setuid or be inaccessible to the identity
   * on whose behalf it is being executed (as when it is run on a remote
   * system).
   * It must be able to access the keys used to encrypt credentials, which
   * must be kept private.
   * If run as a command, it should be setuid; in particular, it
   * should not run as the user being authenticated lest he manipulate the
   * outcome through ptrace(2) or signals.
   */

  if ((dacsauth_out = (Auth_out *) main_out) != NULL) {
	/*
	 * Initialize the result to the "error" value.
	 */
	dacsauth_out->result = -1;
	dacsauth_out->identity = "";
	dacsauth_out->username = "";
	dacsauth_out->role_string = "";
  }

  remote_addr = getenv("REMOTE_ADDR");
  is_dacs_authenticate = 0;
  if (!do_init) {
	kwv = kwv_init(10);
    app_type = DACS_STANDALONE_NOARGS;
	log_module_name = "dacsauth";
  }
  else if (remote_addr != NULL) {
    app_type = DACS_WEB_SERVICE;
	log_module_name = "dacs_authenticate";
	is_dacs_authenticate = 1;
  }
  else {
    app_type = DACS_STANDALONE_NOARGS;
	log_module_name = "dacsauth";
  }

  args = init_args();
  client_cert = NULL;
  fail_delay = AUTH_DEFAULT_FAIL_DELAY_SECS;
  auth_lockfile = NULL;
  failure_reason = AUTH_FAILURE_REASON_UNKNOWN;
  handler_error = 0;
  auth_handlers_enabled = 0;
  ds_roles = NULL;
  common_status.message = NULL;
  saved_common_status.message = NULL;
  selected = 0;
  credentials = NULL;
  got_authorization = 0;
  next_www_authenticate = NULL;
  aa = NULL;
  kwv_conf = kwv_dacs = NULL;
  show_identity = 0;
  qflag = vflag = 0;
  cas_auth.session_ticket = cas_auth.server_uri = NULL;
  cas_auth.username = cas_auth.redirect_args = cas_auth.redirect_url = NULL;
  cas_auth.roles = NULL;
  auth_style = AUTH_STYLE_UNKNOWN;

  ami = NULL;
  rmi = NULL;

  if (do_init && dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
	Auth_error_handler *handler;

  fail:
	if (app_type == DACS_STANDALONE_NOARGS)
	  return(-1);

	emit_xml = test_emit_xml_format();

	/* Erase the password! */
	if (args->password != NULL)
	  strzap(args->password);
	if (args->auxiliary != NULL)
	  strzap(args->auxiliary);

	if (errmsg != NULL)
	  log_msg((LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG,
			   "Authentication failed: %s", errmsg));
	else if (common_status.message != NULL)
	  log_msg((LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG,
			   "Authentication failed: %s", common_status.message));
	else
	  log_msg((LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG, "Authentication failed"));

	/*
	 * Authentication failed, so introduce a delay to deter
	 * repeated guessing for the particular username (unless this has
	 * explicitly been disabled by setting AUTH_FAIL_DELAY_SECS to zero).
	 * In the unlikely case that USERNAME is not available, we'll
	 * use the mapped name.
	 */
	remaining_delay = 0;
	if (args->username != NULL) {
	  if (auth_lockfile == NULL)
		auth_lockfile = create_temp_filename(args->username);
	  set_lock(auth_lockfile, fail_delay, &remaining_delay);
	}

	if (fail_delay > 0)
	  sleep(fail_delay + remaining_delay);

	/*
	 * Trigger another prompt.
	 * Be careful to avoid infinite recursion.
	 */
	if (got_authorization && args->www_authenticate != NULL
		&& args->dacs_error_url != NULL && next_www_authenticate != NULL) {
	  Html_header_conf *hc;

	  hc = emit_html_header_conf(NULL);
	  hc->redirect_url = args->dacs_error_url;
	  hc->headers = dsvec_init(NULL, sizeof(char *));
	  hc->status_code = "401";
	  dsvec_add_ptr(hc->headers, next_www_authenticate);
	  log_msg((LOG_NOTICE_LEVEL,
			   "Redirect and reissue WWW-Authenticate: \"%s\"",
			   next_www_authenticate));
	  emit_html_header(stdout, hc);
	  log_msg((LOG_NOTICE_LEVEL, "Authentication failed - exiting"));

	  return(-1);
	}

	/* Authentication failed.  Handle it as directed. */

	if ((p = conf_val(CONF_AUTH_FAIL)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Evaluating AUTH_FAIL expr: \"%s\"", p));
	  st = auth_eval(p, kwv, kwv_auth, NULL, NULL);
	  if (acs_expr_error_occurred(st))
		log_msg((LOG_ERROR_LEVEL, "Invalid AUTH_FAIL directive: \"%s\"", p));
	}

	if ((handler = lookup_error_handler(failure_reason)) == NULL)
	  return(-1);

	if (!auth_handlers_enabled) {
	  log_msg((LOG_INFO_LEVEL, "Auth handlers are not enabled"));
	  handler_error = 1;
	}
	else if (handler->type == ERROR_HANDLER_URL) {
	  Ds ds;

	  ds_init(&ds);
	  ds_asprintf(&ds, "%s%s", handler->error_string,
				  (strchr(handler->error_string, '?') != NULL) ? "&" : "?");
	  ds_asprintf(&ds, "DACS_ERROR_CODE=%d", failure_reason);
	  ds_asprintf(&ds, "&DACS_VERSION=%s", DACS_VERSION_NUMBER);
	  if ((p = conf_val(CONF_FEDERATION_NAME)) != NULL)
		ds_asprintf(&ds, "&DACS_FEDERATION=%s", p);
	  if ((p = conf_val(CONF_JURISDICTION_NAME)) != NULL)
		ds_asprintf(&ds, "&DACS_JURISDICTION=%s", p);
	  /*
	   * When this is present, it must be retained for authentication retries
	   * See logingen.
	   */
	  p = args->dacs_auth_success_handler
		= kwv_lookup_value(kwv, "DACS_AUTH_SUCCESS_HANDLER");
	  if (p != NULL && *p != '\0')
		ds_asprintf(&ds, "&DACS_AUTH_SUCCESS_HANDLER=%s", url_encode(p, 0));
	  ds_asprintf(&ds, "&FORMAT=%s", get_emit_format());
	  emit_http_header_redirect(stdout, ds_buf(&ds));
	  emit_html_trailer(stdout);
	}
	else if (handler->type == ERROR_HANDLER_FILE)	{
	  char *ptr;
	  size_t file_size;

	  if (load_file(handler->error_string, &ptr, &file_size) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "Could not load file '%s' for AUTH_ERROR_HANDLER",
				 handler->error_string));
		handler_error = 1;
	  }
	  else {
		fflush(stdout);
		/* XXX Errors ignored... */
		write_buffer(fileno(stdout), ptr, file_size);
	  }
	}

	if (handler_error || handler->type == ERROR_HANDLER_REASON) {
	  char *msg;

	  if (saved_common_status.message != NULL)
		p = saved_common_status.message;
	  else if (common_status.message != NULL)
		p = common_status.message;
	  else
		p = errmsg;

	  if (p == NULL)
		p = "no detail available";

	  switch (failure_reason) {
	  case AUTH_FAILURE_REASON_INVALID_INFO:
		msg = ds_xprintf("%d Authentication failed, invalid authenticating information (%s)",
						 failure_reason, p);
		break;
	  case AUTH_FAILURE_REASON_USER_ERROR:
		msg = ds_xprintf("%d Authentication failed, invalid or missing argument (%s)",
						 failure_reason, p);
		break;
	  case AUTH_FAILURE_REASON_ABORT:
		msg = ds_xprintf("%d Authentication failed, abort requested (%s)",
						 failure_reason, p);
		break;
	  case AUTH_FAILURE_REASON_INTERNAL_ERROR:
		msg = ds_xprintf("%d Authentication failed, internal error (%s)",
						 failure_reason, p);
		break;
	  default:
	  case AUTH_FAILURE_REASON_UNKNOWN:
		msg = ds_xprintf("%d Authentication failed, reason unknown (config error?) (%s)",
						 failure_reason, p);
		break;
	  }

	  if (emit_xml) {
		emit_xml_header(stdout, "dacs_auth_reply");
		printf("<%s>\n", make_xml_root_element("dacs_auth_reply"));
		init_common_status(&common_status, NULL, NULL, msg);
		printf("%s", make_xml_common_status(&common_status));
		printf("</dacs_auth_reply>\n");
		emit_xml_trailer(stdout);
	  }
	  else if (test_emit_format(EMIT_FORMAT_HTML)) {
		emit_html_header(stdout, NULL);
		printf("%s\n", msg);
		emit_html_trailer(stdout);
	  }
	  else if (test_emit_format(EMIT_FORMAT_JSON)) {
		emit_json_header(stdout, "dacs_auth_reply");
		init_common_status(&common_status, NULL, NULL, msg);
		printf("%s", make_json_common_status(&common_status));
		emit_json_trailer(stdout);
	  }
	  else {
		emit_plain_header(stdout);
		printf("%s\n", msg);
		emit_plain_trailer(stdout);
	  }
	}
	else if (handler->type == ERROR_HANDLER_MESSAGE) {
	  if (emit_xml) {
		emit_xml_header(stdout, "dacs_auth_reply");
		printf("<%s>", make_xml_root_element("dacs_auth_reply"));
		init_common_status(&common_status, NULL, NULL, handler->error_string);
		printf("%s", make_xml_common_status(&common_status));
		printf("</dacs_auth_reply>\n");
		emit_xml_trailer(stdout);
	  }
	  else if (test_emit_format(EMIT_FORMAT_JSON)) {
		emit_json_header(stdout, "dacs_auth_reply");
		init_common_status(&common_status, NULL, NULL, handler->error_string);
		printf("%s", make_json_common_status(&common_status));
		emit_json_trailer(stdout);
	  }
	  else if (test_emit_format(EMIT_FORMAT_HTML)) {
		emit_html_header(stdout, NULL);
		printf("%s\n", handler->error_string);
		emit_html_trailer(stdout);
	  }
	  else {
		emit_plain_header(stdout);
		printf("%s\n", handler->error_string);
		emit_plain_trailer(stdout);
	  }
	}
	else if (handler->type != ERROR_HANDLER_FILE
			 && handler->type != ERROR_HANDLER_URL) {
	  /* Error - emit nothing */
	  log_msg((LOG_ALERT_LEVEL,
			   "Broken AUTH_ERROR_HANDLER handling? type=%d", handler->type));
	  return(-1);
	}

	log_msg((LOG_NOTICE_LEVEL, "Authentication failed - exiting"));
	return(-1);
  }

  /* Reset this after dacs_init(). */
  errmsg = NULL;

  args->dacs_auth_success_handler
	= kwv_lookup_value(kwv, "DACS_AUTH_SUCCESS_HANDLER");

  /* Grab configuration directives. */
  jurisdiction_name = conf_val(CONF_JURISDICTION_NAME);
  federation_name = conf_val(CONF_FEDERATION_NAME);
  default_lifetime = conf_val(CONF_AUTH_CREDENTIALS_DEFAULT_LIFETIME_SECS);

  if ((st = conf_val_uint(CONF_AUTH_FAIL_DELAY_SECS, &fail_delay)) == -1) {
	fail_delay = AUTH_DEFAULT_FAIL_DELAY_SECS;
	log_msg((LOG_WARN_LEVEL,
			 "Using default because AUTH_FAIL_DELAY_SECS is invalid: \"%s\"",
			 conf_val(CONF_AUTH_FAIL_DELAY_SECS)));
  }
  else if (st == 0) {
	log_msg((LOG_TRACE_LEVEL,
			 "AUTH_FAIL_DELAY_SECS is not configured, using default"));
	fail_delay = AUTH_DEFAULT_FAIL_DELAY_SECS;
  }

  if (should_use_argv) {
	if (app_type != DACS_STANDALONE_NOARGS && argc > 1) {
	  init_common_status(&common_status, NULL, NULL,
						 "Usage: unrecognized parameter");
	  goto fail;
	}

	dacs_disable_dump();

	if (do_init) {
	  ld = log_init(NULL, 0, NULL, "dacsauth", LOG_NONE_LEVEL, NULL);
	  log_set_level(ld, LOG_WARN_LEVEL);
	  log_set_user_callback(ld, logging_callback, (void *) NULL);
	  log_set_desc(ld, LOG_ENABLED);
	}

	/* Process command line arguments, but only when used as a utility. */
	i = 1;
	kwv_conf = kwv_init(10);
	kwv_dacs = kwv_init(10);

	read_data_ptr = &args->password;
	read_data_name = "Password";

	ami = auth_parse_auth_clause_init(NULL);
	rmi = auth_parse_roles_clause_init(NULL);

	while (argv[i] != NULL) {
	  Auth_module *am, **amp;
	  
	  if (strcaseeq(argv[i], "-modules")) {
		int ii;
		Auth_module_desc *adesc;
		Roles_module_desc *rdesc;
		extern Roles_module_desc roles_module_desc_tab[];

		for (adesc = &auth_module_desc_tab[0]; adesc->id != AUTH_MODULE_UNKNOWN;
			 adesc++) {
		  for (ii = 0;
			   ii < AUTH_MODULE_MAX_ALIASES && adesc->aliases[ii] != NULL;
			   ii++)
			printf("%s%s", (ii == 0) ? "Auth: " : ", ", adesc->aliases[ii]);
		  printf(" (%s)\n", auth_style_to_string(adesc->style));
		}

		for (rdesc = &roles_module_desc_tab[0]; rdesc->id != ROLES_MODULE_UNKNOWN;
			 rdesc++) {
		  for (ii = 0;
			   ii < ROLES_MODULE_MAX_ALIASES && rdesc->aliases[ii] != NULL;
			   ii++)
			printf("%s%s", (ii == 0) ? "Roles: " : ", ", rdesc->aliases[ii]);
		  printf("\n");
		}

		return(-1);
	  }

	  if (streq(argv[i], "-m")) {
		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Auth module specification is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}

		if ((am = auth_module_config(argv, &i, ami)) == NULL) {
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
#ifdef NOTDEF
		ami->mcount++;
		ami->have_req_auth += am->have_req_auth;
#endif
		am->id = ds_xprintf("auth_module_%d", ami->mcount);
		for (amp = &ami->modules; *amp != NULL; amp = &(*amp)->next)
		  ;
		*amp = am;
		continue;
	  }

	  if (streq(argv[i], "-r")) {
		Roles_module *rm, **rmp;

		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Roles module specification is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}

		if ((rm = roles_module_config(argv, &i)) == NULL) {
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		rmi->mcount++;
		rm->id = ds_xprintf("roles_module_%d", rmi->mcount);
		for (rmp = &rmi->modules; *rmp != NULL; rmp = &(*rmp)->next)
		  ;
		*rmp = rm;
		continue;
	  }

	  if (strprefix(argv[i], "-D") != NULL) {
		char *directive, *value;

		if (kwv_parse_qstr(&argv[i][2], &directive, &value, NULL, NULL) == -1) {
		  log_msg((LOG_ERROR_LEVEL, "Invalid -D argument: %s", argv[i]));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		conf_set_directive(kwv_conf, directive, value);
	  }
	  else if (streq(argv[i], "-fj")) {
		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Jurisdiction name is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		jurisdiction_name = argv[i];
	  }
	  else if (streq(argv[i], "-fn")) {
		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Federation name is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		federation_name = argv[i];
	  }
	  else if (streq(argv[i], "-aux")) {
		read_data_ptr = &args->auxiliary;
		read_data_name = "Auxiliary/PIN";
	  }
	  else if (streq(argv[i], "-h") || streq(argv[i], "-help")) {
		if (usage())
		  return(-1);
		/*NOTREACHED*/
	  }
	  else if (streq(argv[i], "-id")) {
		show_identity = 1;
	  }
	  else if (streq(argv[i], "-ll")) {
		Log_level ll;

		if (++i == argc) {
		  log_msg((LOG_ERROR_LEVEL, "Missing log_level argument"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		if ((ll = log_lookup_level(argv[i])) == LOG_INVALID_LEVEL
			|| log_set_level(ld, ll) == LOG_INVALID_LEVEL) {
		  log_msg((LOG_ERROR_LEVEL, "Invalid log_level"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
      }
	  else if (streq(argv[i], "-p")) {
		if (*read_data_ptr != NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Multiple requests for the same %s", read_data_name));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		if (argv[++i] == NULL) {
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}

		*read_data_ptr = strdup(argv[i]);
		strzap(argv[i]);
	  }
	  else if (streq(argv[i], "-pf")) {
		if (*read_data_ptr != NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Multiple requests for the same %s", read_data_name));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Password file is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}

		if (streq(argv[i], "-"))
		  *read_data_ptr = get_passwd(GET_PASSWD_STDIN, NULL);
		else
		  *read_data_ptr = get_passwd(GET_PASSWD_FILE, argv[i]);
		if (*read_data_ptr == NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Error reading %s from \"%s\"", read_data_name, argv[i]));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
	  }
	  else if (streq(argv[i], "-prompt")) {
		if (*read_data_ptr != NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Multiple requests for the same %s", read_data_name));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}

		*read_data_ptr = get_passwd(GET_PASSWD_PROMPT,
								   ds_xprintf("%s? ", read_data_name));
		if (*read_data_ptr == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Error reading %s", read_data_name));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
	  }
	  else if (streq(argv[i], "-u") || streq(argv[i], "-user")) {
		if (args->username != NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Duplicate username argument"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		if (argv[++i] == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Username is missing"));
		  if (usage())
			return(-1);
		  /*NOTREACHED*/
		}
		args->username = argv[i];
		kwv_replace(kwv, "USERNAME", args->username);
	  }
	  else if (streq(argv[i], "-q"))
		qflag++;
	  else if (streq(argv[i], "-v"))
		vflag++;
	  else {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized argument: %s", argv[i]));
		if (usage())
		  return(-1);
		/*NOTREACHED*/
	  }

	  i++;
	}

	if (*read_data_ptr != NULL)
	  kwv_replace(kwv, "PASSWORD", *read_data_ptr);

	if (vflag == 1)
	  log_set_level(ld, LOG_DEBUG_LEVEL);
	else if (vflag > 1)
	  log_set_level(ld, LOG_TRACE_LEVEL);
	else if (qflag)
	  log_set_level(ld, LOG_ERROR_LEVEL);

	if (ami->modules == NULL && rmi->modules == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "At least one module must be specified"));
	  if (usage())
		return(-1);
	  /*NOTREACHED*/
	}

	set_conf_from_host(kwv_conf, kwv_dacs, NULL);
	if (federation_name == NULL)
	  get_conf_from_host(NULL, NULL, &federation_name, NULL, NULL);
	conf_set_directive(kwv_conf, "FEDERATION_NAME", federation_name);
	log_msg((LOG_DEBUG_LEVEL,
			 "Using FEDERATION_NAME: \"%s\"", federation_name));

	if (jurisdiction_name == NULL)
	  get_conf_from_host(NULL, NULL, NULL, &jurisdiction_name, NULL);
	conf_set_directive(kwv_conf, "JURISDICTION_NAME", jurisdiction_name);
	args->jurisdiction = jurisdiction_name;
	log_msg((LOG_DEBUG_LEVEL,
			 "Using JURISDICTION_NAME: \"%s\"", jurisdiction_name));

	if (conf_val(CONF_SSL_PROG) == NULL)
	  conf_set_directive(kwv_conf, "SSL_PROG", DACS_BINDIR/**/"/sslclient");

	/* The lifetime is irrelevant for dacsauth. */
	default_lifetime = "43200";

	kwv_vartab_init(kwv_conf, conf_vartab, NULL, NULL);
  }

  /* Do this as early as possible. */
  emit_xml = test_emit_xml_format();

  /* Do this as early as possible. */
  if ((p = kwv_lookup_value(kwv, "ENABLE_AUTH_HANDLERS")) != NULL) {
	if (streq(p, "1"))
	  auth_handlers_enabled = 1;
	else
	  auth_handlers_enabled = 0;
	args->enable_auth_handlers = p;
  }

  browser = 0;
  kwv_trans = NULL;

  if ((kwv_auth = kwv_init(10)) == NULL)
	goto fail;
  kwv_auth->dup_mode = KWV_NO_DUPS;

  if (dacs_is_https_request()) {
	char *ssl_name, *ssl_val;
	extern char **environ;

	kwv_add(kwv_auth, "HTTPS", "on");
	log_msg((LOG_TRACE_LEVEL, "${Auth::HTTPS} = \"on\""));
	for (i = 0; environ[i] != NULL; i++) {
	  if (strneq(environ[i], "SSL_", 4)) {
		env_parse(environ[i], &ssl_name, &ssl_val);
		kwv_add(kwv_auth, ssl_name, ssl_val);
		log_msg((LOG_TRACE_LEVEL, "${Auth::%s} = \"%s\"", ssl_name, ssl_val));
	  }
	}
  }

  /* We need this before the AUTHORIZATION header. */
  if ((p = kwv_lookup_value(kwv, "WWW_AUTHENTICATE")) != NULL) {
	/* This is passed in case auth fails and we need to prompt again. */
	args->www_authenticate = url_decode(p, NULL, NULL);
	log_msg((LOG_TRACE_LEVEL, "Got WWW_AUTHENTICATE: %s",
			 args->www_authenticate));
  }

  for (i = 0; i < kwv->nused; i++) {
	if (streq(kwv->pairs[i]->name, "USERNAME"))
	  args->username = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "PASSWORD"))
	  args->password = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUXILIARY"))
	  args->auxiliary = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "xmlToken")) {
	  /*
	   * The InfoCard argument is called "xmlToken" by convention, but we
	   * will try AUXILIARY if there's no xmlToken argument...
	   */
	  args->xmlToken = kwv->pairs[i]->val;
	}
	else if (streq(kwv->pairs[i]->name, "DACS_VERSION"))
	  args->dacs_version = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_JURISDICTION"))
	  args->jurisdiction = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_DEBUG")) {
	  args->dacs_debug = kwv->pairs[i]->val;
	  trace_level++, verbose_level++;
	}
	else if (streq(kwv->pairs[i]->name, "DACS_REQUEST_METHOD"))
	  args->dacs_request_method = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "ticket")) {
	  args->ticket = kwv->pairs[i]->val;
	  cas_auth.session_ticket = kwv->pairs[i]->val;
	}
	else if (streq(kwv->pairs[i]->name, "DACS_BROWSER")) {
	  args->dacs_browser = kwv->pairs[i]->val;
	  browser = (kwv->pairs[i]->val != NULL
				 && streq(kwv->pairs[i]->val, "1"));
	}
	else if (streq(kwv->pairs[i]->name, "AUTH_ID"))
	  args->auth_id = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUTH_TRANSID")) {
	  args->auth_transid = kwv->pairs[i]->val;
	  if (kwv_trans == NULL && (kwv_trans = kwv_init(10)) == NULL)
		goto fail;
	  kwv_add_pair(kwv_trans, kwv->pairs[i]);
	  log_msg((LOG_DEBUG_LEVEL, "Given transid=\"%s\"", kwv->pairs[i]->val));
	}
	else if (strneq(kwv->pairs[i]->name, AUTH_PROMPT_VAR_PREFIX,
					strlen(AUTH_PROMPT_VAR_PREFIX))
			 && is_digit_string(kwv->pairs[i]->name
								+ strlen(AUTH_PROMPT_VAR_PREFIX))) {
	  p = ds_xprintf("%s=%s", kwv->pairs[i]->name, kwv->pairs[i]->val);
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "Auth prompt: %s", p));
	  dsvec_add_ptr(args->auth_prompt_var_prefixes, p);
	  if (kwv_trans == NULL && (kwv_trans = kwv_init(10)) == NULL)
		goto fail;
	  kwv_add_pair(kwv_trans, kwv->pairs[i]);
	}
	else if (streq(kwv->pairs[i]->name, "OPERATION")) {
	  /* XXX Might handle ADD operation too... */
	  if (strcaseeq(kwv->pairs[i]->val, "SELECT"))
		selected = 1;
	  else {
		log_msg((LOG_WARN_LEVEL, "Unrecognized OPERATION parameter"));
		failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
		goto fail;
	  }
	  args->operation = kwv->pairs[i]->val;
	}
	else if (streq(kwv->pairs[i]->name, "FORMAT"))
	  args->format = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "ENABLE_AUTH_HANDLERS"))
	  ;
	else if (streq(kwv->pairs[i]->name, "COOKIE_SYNTAX")) {
	  if (configure_cookie_syntax(kwv->pairs[i]->val, &errmsg) == -1)
		goto fail;
	  args->cookie_syntax = kwv->pairs[i]->val;
	}
	else if (streq(kwv->pairs[i]->name, "WWW_AUTHENTICATE")) {
	  ;
	}
	else if (streq(kwv->pairs[i]->name, "AUTHORIZATION")) {
	  char *auth_str, *authorization;
	  unsigned char *encrypted_authorization;
	  unsigned int len;
	  Crypt_keys *ck;
	  Http_auth auth;

	  args->authorization = kwv->pairs[i]->val;
	  if ((auth_str = getenv("DACS_AUTHORIZATION")) != NULL)
		log_msg((LOG_TRACE_LEVEL, "Using DACS_AUTHORIZATION"));
	  else
		auth_str = kwv->pairs[i]->val;

	  got_authorization = 1;
	  if (stra64b(auth_str, &encrypted_authorization, &len) == NULL) {
		errmsg = "Base64 decoding error";
		goto fail;
	  }
	  if ((ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL) {
		errmsg = "Cannot get federation_keys";
		goto fail;
	  }
	  if (crypto_decrypt_string(ck, encrypted_authorization, len,
								(unsigned char **) &authorization,
								NULL) == -1) {
		errmsg = "Decryption error";
		goto fail;
	  }
	  crypt_keys_free(ck);
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "Got Authorization: \"%s\"", authorization));

	  aa = http_auth_authorization_parse(authorization, &errmsg);
	  if (aa == NULL) {
		if (errmsg != NULL)
		  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
		/* RFC 2617, S3.2.2 says return "400 Bad Request" */
		emit_http_header_status(stdout, "400", NULL);
		return(-1);
	  }

	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "HTTP auth scheme is: \"%s\"", aa->scheme_name));

	  if (aa->scheme == HTTP_AUTH_BASIC) {
		Http_auth_www_authenticate *wwwa;

		wwwa = http_auth_www_authenticate_parse(args->www_authenticate,
												&errmsg);
		if (wwwa == NULL)
		  goto fail;
		
		auth.scheme = HTTP_AUTH_BASIC;
		auth.scheme_name = "Basic";
		auth.realm = wwwa->realm;
		auth.url_patterns = NULL;
		auth.param = NULL;
		next_www_authenticate
		  = ds_xprintf("WWW-Authenticate: %s", http_auth_basic_auth(&auth));

		args->username = aa->username;
		args->password = aa->password;
		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "Setting USERNAME=\"%s\", PASSWORD=\"%s\"",
				 args->username, args->password));
	  }
	  else if (aa->scheme == HTTP_AUTH_DIGEST) {
		auth.scheme = HTTP_AUTH_DIGEST;
		auth.scheme_name = "Digest";
		auth.realm = aa->realm;
		auth.url_patterns = NULL;
		auth.param = NULL;
		next_www_authenticate
		  = ds_xprintf("WWW-Authenticate: %s",
					   http_auth_digest_auth(&auth, aa->digest_uri));

		args->username = aa->username;
		if (args->www_authenticate != NULL) {
		  aa->www_auth
			= http_auth_www_authenticate_parse(args->www_authenticate, &errmsg);
		  if (aa->www_auth == NULL) {
			if (errmsg != NULL)
			  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
			log_msg((LOG_ERROR_LEVEL, "Error parsing WWW-Authenticate"));
			/* RFC 2617, S3.2.2 says return "400 Bad Request" */
			emit_http_header_status(stdout, "400", NULL);
			return(-1);
		  }
		}

		log_msg((LOG_TRACE_LEVEL, "username=\"%s\"", aa->username));
		log_msg((LOG_TRACE_LEVEL, "realm=\"%s\"", aa->realm));
		log_msg((LOG_TRACE_LEVEL, "nonce=\"%s\"", aa->nonce));
		log_msg((LOG_TRACE_LEVEL, "uri=\"%s\"", aa->digest_uri));
		log_msg((LOG_TRACE_LEVEL, "response=\"%s\"", aa->response));
	  }
	  else {
		errmsg = "Unrecognized HTTP auth scheme";
		goto fail;
	  }
	}
	else if (streq(kwv->pairs[i]->name, "DACS_AUTH_SUCCESS_HANDLER"))
	  ;
	else if (streq(kwv->pairs[i]->name, "DACS_ERROR_URL"))
	  args->dacs_error_url = kwv->pairs[i]->val;
	else {
	  /* Ignore the argument... */
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "Unrecognized parameter: \"%s\"", kwv->pairs[i]->name));
	}
  }

  lifetime = NULL;

  if (args->jurisdiction == NULL
	  || !name_eq(args->jurisdiction, jurisdiction_name,
				  DACS_NAME_CMP_CONFIG)) {
	if (args->jurisdiction == NULL)
	  init_common_status(&common_status, NULL, NULL,
						 "Missing DACS_JURISDICTION argument");
	else {
	  init_common_status(&common_status, NULL, NULL,
						 "Authentication request was received by the wrong jurisdiction");
	  log_msg((LOG_ERROR_LEVEL,
			   "Request for jurisdiction \"%s\" received by jurisdiction \"%s\"",
			   args->jurisdiction, jurisdiction_name));
	}
	failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
	goto fail;
  }

  /*
   * Check username, password, and aux for reasonable syntax
   */
  if (args->username != NULL && *args->username != '\0') {
	if (strlen(args->username) > AUTH_MAX_INPUT_USERNAME_LENGTH) {
	  log_msg((LOG_ERROR_LEVEL, "Length of username exceeds limit"));
	  goto fail;
	}
	if (!is_strclass(args->username, STRCLASS_PRINTABLE)) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid username character"));
	  goto fail;
	}
  }
  if (args->password != NULL && *args->password != '\0') {
	if (strlen(args->password) > AUTH_MAX_INPUT_PASSWORD_LENGTH) {
	  log_msg((LOG_ERROR_LEVEL, "Length of password exceeds limit"));
	  goto fail;
	}
	if (!is_strclass(args->password, STRCLASS_PRINTABLE)) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid password character"));
	  goto fail;
	}
  }
  if (args->auxiliary != NULL && *args->auxiliary != '\0') {
	if (strlen(args->auxiliary) > AUTH_MAX_INPUT_USERNAME_LENGTH) {
	  log_msg((LOG_ERROR_LEVEL, "Length of auxiliary exceeds limit"));
	  goto fail;
	}
	if (!is_strclass(args->auxiliary, STRCLASS_PRINTABLE)) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid auxiliary character"));
	  goto fail;
	}
  }

  /*
   * If a previous attempt failed for this user, then we may need to
   * delay before trying again.
   */
  if (auth_lockfile == NULL && args->username != NULL) {
	auth_lockfile = create_temp_filename(args->username);
	if (check_lock(auth_lockfile, fail_delay, &remaining_delay) == 1
		&& remaining_delay > 0)
	  sleep(remaining_delay);
  }

  auth_state.mapped_username = NULL;
  auth_state.state = AUTH_OK;
  auth_state.success_count = 0;

  /*
   * First we must parse each of the authentication directives.
   * We call them "local authentication modules" or "modules" since
   * we're following the PAM conventions.
   * Note that we must retain the original ordering.
   */

  if (app_type != DACS_STANDALONE_NOARGS) {
	if ((ami = auth_parse_auth_clauses(NULL)) == NULL)
	  goto fail;
  }
  else {
	if (ami == NULL || ami->modules == NULL)
	  goto do_roles;
  }

  if (ami->modules == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Authentication is not configured for this jurisdiction"));
	goto fail;
  }

  log_msg((LOG_INFO_LEVEL,
		   "Local authentication modules found: %d", ami->mcount));

  if (ami->saw_set_roles > 1) {
	log_msg((LOG_ERROR_LEVEL, "Only one set_roles style is allowed"));
	goto fail;
  }

  if (ami->saw_set_roles && ami->saw_add_roles) {
	log_msg((LOG_ERROR_LEVEL,
			 "Modes set_roles and add_roles are mutually exclusive"));
	goto fail;
  }

  /*
   * At present, if there is a prompted style of authentication being used
   * then only one module can be used.  The problem is that when a prompt
   * is returned to the user and the reply submitted, module processing should
   * not start from the first module again, it should resume with the "current"
   * module (the one that issued the prompt).
   * XXX This might be fixed by saving the current state of module processing
   * (e.g., by returning it to the user and having him resubmit it with the
   * reply to the prompt).  This needs to be tamper-proof so that a) the user
   * isn't able to reply the state in a different authentication transaction
   * and b) he can't modify the state.  Also, we need to guard against the
   * unlikely case of the configuration file being modified while state
   * information is "out there" (e.g., by hashing the configuration file,
   * including it with the state, and recomputing the hash when the reply to
   * a prompt starts to be processed), which might lead to confusion.
   */
  if (ami->saw_prompted && ami->mcount > 1) {
	log_msg((LOG_ERROR_LEVEL,
			 "Only one prompted authentication style is allowed"));
	goto fail;
  }

  if (log_would_log(LOG_INFO_LEVEL)) {
	for (i = 0, m = ami->modules; m != NULL; m = m->next, i++)
	  log_msg((LOG_INFO_LEVEL, "Auth module %d: %s",
			   i, describe_auth_module(m)));
  }

  /*
   * Now process the list of modules, in the order in which they appear,
   * according to each module's CONTROL directive.
   */
  mapped_username = NULL;
  rc = -1;
  using_cert = 0;

  kwv_replace(kwv_auth, "CURRENT_USERNAME", "");

  for (auth_state.mindex = 0, m = ami->modules; m != NULL;
	   m = m->next, auth_state.mindex++) {
	char *auth_user, *auth_password, *auth_aux;

	prompts = NULL;
	client_cert = NULL;
	auth_user = NULL;
	auth_password = NULL;
	auth_aux = NULL;
	if (args->auth_id != NULL) {
	  log_msg((LOG_DEBUG_LEVEL, "Request for module id=\"%s\"", args->auth_id));
	  if (m->control_flag == AUTH_SUFFICIENT) {
		log_msg((LOG_DEBUG_LEVEL,
				 "Skipping \"sufficient\" module id=\"%s\"", m->id));
		continue;
	  }
	  if (m->control_flag == AUTH_USER_SUFFICIENT
		  && !streq(args->auth_id, m->id)) {
		log_msg((LOG_DEBUG_LEVEL,
				 "Skipping \"user_sufficient\" module id=\"%s\"", m->id));
		continue;
	  }
	  log_msg((LOG_TRACE_LEVEL,
			   "Selecting \"user_sufficient\" module id=\"%s\"", m->id));
	}
	else {
	  if (m->control_flag == AUTH_USER_SUFFICIENT) {
		log_msg((LOG_DEBUG_LEVEL,
				 "Skipping \"user_sufficient\" module id=\"%s\"", m->id));
		continue;
	  }
	}

	if (m->predicate != NULL && m->predicate[0] != '\0') {
	  Acs_expr_result st;

	  st = auth_eval(m->predicate, kwv, kwv_auth, NULL, NULL);

	  if (st == ACS_EXPR_SYNTAX_ERROR) {
		log_msg((LOG_ERROR_LEVEL, "Syntax error in module predicate:"));
		log_msg((LOG_ERROR_LEVEL, "\"%s\"", m->predicate));
		init_common_status(&common_status, NULL, NULL,
						  "Syntax error in module predicate");
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  else if (st == ACS_EXPR_EVAL_ERROR) {
		log_msg((LOG_ERROR_LEVEL, "Evaluation error in module predicate:"));
		log_msg((LOG_ERROR_LEVEL, "\"%s\"", m->predicate));
		init_common_status(&common_status, NULL, NULL,
						  "Evaluation error in module predicate");
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  else if (st == ACS_EXPR_LEXICAL_ERROR) {
		log_msg((LOG_ERROR_LEVEL, "Lexical error in module predicate:"));
		log_msg((LOG_ERROR_LEVEL, "\"%s\"", m->predicate));
		init_common_status(&common_status, NULL, NULL,
						  "Lexical error in module predicate");
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  else if (st == 0) {
		log_msg((LOG_TRACE_LEVEL, "Module predicate is false:"));
		log_msg((LOG_TRACE_LEVEL, "\"%s\"", m->predicate));
		init_common_status(&common_status, NULL, NULL,
						  "Module predicate is false");
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  log_msg((LOG_TRACE_LEVEL, "Module predicate is true: \"%s\"",
			   m->predicate));
	}

	if ((p = kwv_lookup_value(kwv_auth, "ABORT")) != NULL
		&& strcaseeq(p, "yes")) {
	  rc = -1;
	  failure_reason = AUTH_FAILURE_REASON_ABORT;
	  goto fail;
	}

	if ((p = kwv_lookup_value(kwv_auth, "MODULE_SKIP")) != NULL
		&& strcaseeq(p, "yes")) {
	  kwv_delete(kwv_auth, "MODULE_SKIP");
	  log_msg((LOG_DEBUG_LEVEL, "Config requests module skip"));
	  init_common_status(&common_status, NULL, NULL,
						"Config requests module skip");
	  rc = 0;
	  goto bad_auth;
	}

	if (m->style & AUTH_STYLE_CERT) {
	  if (!dacs_is_https_request()
		  || (client_cert = getenv("SSL_CLIENT_CERT")) == NULL) {
		log_msg((LOG_WARN_LEVEL, "Certificate is not available..."));
		init_common_status(&common_status, NULL, NULL,
						  "Certificate is not available");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  using_cert = 1;
	  auth_user = args->username;
	  auth_password = args->password;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_CAS) {
	  Ds x;

	  /*
	   * local_cas_authenticate needs to construct a callback URL
	   * (aimed at dacs_authenticate) and various arguments
	   * have to be forwarded to it to retain the behaviour requested by
	   * the user.
	   */
	  ds_init(&x);
	  if (browser)
		ds_asprintf(&x, "DACS_BROWSER=1");
	  if (args->format != NULL)
		ds_asprintf(&x, "%sFORMAT=%s", ds_len(&x) ? "&" : "", args->format);
	  if (auth_handlers_enabled)
		ds_asprintf(&x, "%sENABLE_AUTH_HANDLERS=1", ds_len(&x) ? "&" : "");
	  if (args->dacs_error_url != NULL)
		ds_asprintf(&x, "%DACS_ERROR_URL=%s",
					ds_len(&x) ? "&" : "", args->dacs_error_url);
	  cas_auth.redirect_args = ds_buf(&x);

	  auth_user = args->username;
	  auth_password = args->password;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_PASSWORD) {
	  if (args->username == NULL || args->username[0] == '\0') {
		log_msg((LOG_WARN_LEVEL, "No USERNAME provided for password auth..."));
		init_common_status(&common_status, NULL, NULL,
						  "No USERNAME provided for password auth");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  auth_user = args->username;
	  auth_password = args->password;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_SIMPLE) {
	  if (args->username == NULL || args->username[0] == '\0') {
		log_msg((LOG_WARN_LEVEL, "No USERNAME provided for simple auth..."));
		init_common_status(&common_status, NULL, NULL,
						  "No USERNAME provided for simple auth");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  auth_user = args->username;
	  auth_password = args->password;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_INFOCARD
		|| m->style & AUTH_STYLE_MINFOCARD
		|| m->style & AUTH_STYLE_SINFOCARD) {
	  if (args->xmlToken == NULL)
		args->xmlToken = args->auxiliary;
	  if (args->xmlToken == NULL || *args->xmlToken == '\0') {
		log_msg((LOG_WARN_LEVEL, "No token provided for InfoCard auth..."));
		init_common_status(&common_status, NULL, NULL,
						  "No token provided for InfoCard auth");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  /*
	   * A given username is ignored (though InfoCard authentication requires
	   * a given username to match the identity assigned to the card).
	   */
	  auth_user = NULL;
	  auth_password = NULL;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_PROMPTED) {
	  /* These may be optional */
	  auth_user = args->username;
	  auth_password = args->password;
	  auth_aux = args->auxiliary;
	}

	if (m->style & AUTH_STYLE_NATIVE) {
	  if ((auth_type = getenv("AUTH_TYPE")) == NULL) {
		log_msg((LOG_WARN_LEVEL,
				 "No AUTH_TYPE environment variable for native auth..."));
		init_common_status(&common_status, NULL, NULL,
						  "No AUTH_TYPE environment variable for native auth");
		failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
		rc = 0;
		goto bad_auth;
	  }
	  if (!strcaseeq(auth_type, "Basic")
		  && !strcaseeq(auth_type, "Digest")) {
		log_msg((LOG_WARN_LEVEL,
				 "Unsupported native auth (AUTH_TYPE): %s", auth_type));
		init_common_status(&common_status, NULL, NULL,
						  "Unsupported native auth (AUTH_TYPE)");
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  if (m->kwv_options == NULL)
		m->kwv_options = kwv_init(10);
	  if ((p = getenv("REMOTE_USER")) != NULL) {
		kwv_add(m->kwv_options, "REMOTE_USER", p);
		log_msg((LOG_INFO_LEVEL, "REMOTE_USER=%s", p));
	  }
	  if ((p = getenv("AUTH_TYPE")) != NULL) {
		kwv_add(m->kwv_options, "AUTH_TYPE", p);
		log_msg((LOG_INFO_LEVEL, "AUTH_TYPE=%s", p));
	  }
	}

	if (m->init_eval != NULL) {
	  kwv_replace(kwv_auth, "CURRENT_USERNAME", args->username);
	  st = auth_eval(m->init_eval, kwv, kwv_auth, NULL, NULL);
	  if (st == ACS_EXPR_TRUE) {
		if ((p = kwv_lookup_value(kwv_auth, "CURRENT_USERNAME")) != NULL
			&& !streq(p, auth_user)) {
		  args->username = auth_user = p;
		  log_msg((LOG_DEBUG_LEVEL, "Username changed to \"%s\"", args->username));
		}
	  }
	  else if (st != ACS_EXPR_FALSE) {
		log_msg((LOG_ERROR_LEVEL, "Invalid INIT* directive: %s",
				 m->init_eval));
		init_common_status(&common_status, NULL, NULL,
						  "INIT* evaluation failed");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		goto fail;
	  }
	}

	if (m->url_eval != NULL) {
	  st = auth_eval(m->url_eval, kwv, kwv_auth, NULL, &m->url);
	  if (st != ACS_EXPR_TRUE || m->url == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid URL* directive: %s", m->url_eval));
		init_common_status(&common_status, NULL, NULL,
						   "URL* evaluation failed");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		goto fail;
	  }
	}

	if (m->style & AUTH_STYLE_DIGEST && got_authorization && aa != NULL) {
	  if (args->username == NULL || args->username[0] == '\0') {
		log_msg((LOG_WARN_LEVEL, "No USERNAME provided for digest auth..."));
		init_common_status(&common_status, NULL, NULL,
						   "No USERNAME provided for digest auth");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		rc = 0;
		goto bad_auth;
	  }
	  log_msg((LOG_TRACE_LEVEL, "Use Digest for \"%s\"", args->username));
	  if ((aa->http_method = args->dacs_request_method) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "DACS_REQUEST_METHOD unavailable?"));
		return(-1);
	  }
	  auth_user = args->username;
	  auth_password = NULL;
	  auth_aux = NULL;
	}

	if (m->style & AUTH_STYLE_EXPR) {
	  char *expr_result;

	  if (m->expr == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Missing EXPR?"));
		goto fail;
	  }

	  if ((p = kwv_lookup_value(kwv_auth, "ABORT")) != NULL
		  && strcaseeq(p, "yes")) {
		rc = -1;
		failure_reason = AUTH_FAILURE_REASON_ABORT;
		goto fail;
	  }

	  if ((p = kwv_lookup_value(kwv_auth, "MODULE_SKIP")) != NULL
		  && strcaseeq(p, "yes")) {
		kwv_delete(kwv_auth, "MODULE_SKIP");
		log_msg((LOG_DEBUG_LEVEL, "Config requests module skip"));
		init_common_status(&common_status, NULL, NULL,
						   "Config requests module skip");
		rc = 0;
		goto bad_auth;
	  }

	  kwv_delete(kwv_auth, "ROLES");
	  expr_result = NULL;
	  st = auth_eval(m->expr, kwv, kwv_auth, NULL, &expr_result);
	  if (acs_expr_error_occurred(st)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid EXPR directive: %s", m->expr));
		init_common_status(&common_status, NULL, NULL,
						  "EXPR evaluation failed");
		failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		goto fail;
	  }

	  if (st == ACS_EXPR_FALSE) {
		rc = 0;
		goto bad_auth;
	  }
	  if (!is_valid_auth_username(expr_result)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid username returned by EXPR"));
		goto fail;
	  }
	  mapped_username = expr_result;
	  kwv_replace(kwv_auth, "CURRENT_USERNAME", mapped_username);
	  log_msg((LOG_DEBUG_LEVEL,
			   "CURRENT_USERNAME=\"%s\" (from mapped_username)",
			   mapped_username));

	  role_rc = 0;
	  if ((p = kwv_lookup_value(kwv_auth, "ROLES")) != NULL) {
		if (is_valid_role_str(p)) {
		  role_str = p;
		  role_rc = 1;
		  log_msg((LOG_TRACE_LEVEL, "Found valid roles: \"%s\"", role_str));
		}
	  }

	  goto good_auth;
	}

	if ((p = kwv_lookup_value(kwv_auth, "ABORT")) != NULL
		&& strcaseeq(p, "yes")) {
	  rc = -1;
	  failure_reason = AUTH_FAILURE_REASON_ABORT;
	  goto fail;
	}

	if ((p = kwv_lookup_value(kwv_auth, "MODULE_SKIP")) != NULL
		&& strcaseeq(p, "yes")) {
	  kwv_delete(kwv_auth, "MODULE_SKIP");
	  log_msg((LOG_DEBUG_LEVEL, "Config requests module skip"));
	  init_common_status(&common_status, NULL, NULL,
						 "Config requests module skip");
	  rc = 0;
	  goto bad_auth;
	}

	log_msg((LOG_DEBUG_LEVEL, "Invoking module %d: %s",
			 auth_state.mindex, m->url));

	role_str = "";
	role_rc = 0;

	if (m->dsv_audits != NULL) {
	  char *constraints, *default_constraints, *s;
	  Auth_password_audit *pa;

	  default_constraints = conf_val(CONF_PASSWORD_CONSTRAINTS);
	  for (i = 0; i < dsvec_len(m->dsv_audits); i++) {
		pa = dsvec_ptr(m->dsv_audits, i, Auth_password_audit *);
		if ((constraints = pa->constraints) == NULL
			&& (constraints = default_constraints) == NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "A constraint or PASSWORD_CONSTRAINTS must be given with PASSWORD_AUDIT"));
		  goto fail;
		}

		if (streq(pa->varname, "PASSWORD") && auth_password != NULL
			&& *auth_password != '\0')
		  st = pw_is_passwd_acceptable(auth_password, constraints);
		else if (streq(pa->varname, "AUXILIARY") && auth_aux != NULL
				 && *auth_aux != '\0')
		  st = pw_is_passwd_acceptable(auth_aux, constraints);
		else if ((s = kwv_lookup_value(m->kwv_options, pa->varname)) != NULL
				 && *s != '\0')
		  st = pw_is_passwd_acceptable(s, constraints);
		else
		  st = 1;

		if (st == 0)
		  log_msg(((Log_level)
				   (LOG_WARN_LEVEL | LOG_AUDIT_FLAG),
				   "Module %s: argument \"%s\" does not satisfy audit constraints: \"%s\"",
				   m->id, pa->varname, constraints));
	  }
	}

	rc = call_auth_module(m, auth_user, auth_password, auth_aux,
						  args->jurisdiction, kwv, aa, args->xmlToken,
						  &cas_auth, &ic_auth, client_cert, kwv_trans,
						  &mapped_username, &lifetime, &role_str,
						  &role_rc, &prompts, &common_status);
  bad_auth:
	if (rc <= 0) {
	  if ((m->style & AUTH_STYLE_CAS) && cas_auth.redirect_url != NULL) {
		char *url;

		url = cas_auth.redirect_url;
		log_msg((LOG_DEBUG_LEVEL,
				 "CAS authentication requires redirect to \"%s\"", url));
		errmsg = "CAS authentication requires redirect";

		emit_html_header_redirect(stdout, NULL, url,
								  "This will continue CAS authentication.");
		emit_html_trailer(stdout);

		log_msg((LOG_NOTICE_LEVEL, "Authentication incomplete - exiting"));
		return(-1);
	  }

	  if (m->style & AUTH_STYLE_PROMPTED) {
		if (prompts != NULL) {
		  Auth_prompt *pr;

		  for (pr = prompts->head; pr != NULL; pr = pr->next) {
			if (streq(pr->type, "error"))
			  log_msg((LOG_ERROR_LEVEL, "PAM error: %s", pr->label));
		  }
		}
	  }

	  log_msg((LOG_WARN_LEVEL | LOG_AUDIT_FLAG,
			   "Auth module %d failed: username='%s',jurisdiction='%s',ip=%s",
			   auth_state.mindex, non_null(args->username),
			   non_null(args->jurisdiction), non_null(remote_addr)));
	  log_msg((LOG_WARN_LEVEL | LOG_AUDIT_FLAG,
			   "Reason: %s", common_status.message));

	  if (rc == -1) {
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		goto fail;
	  }

	  /* No error, but authentication failed. */
	  if (m->control_flag == AUTH_REQUISITE) {
		saved_common_status = common_status;
		if (failure_reason == AUTH_FAILURE_REASON_UNKNOWN)
		  failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
		goto fail;
	  }
	  else if (m->control_flag == AUTH_REQUIRED) {
		auth_state.state = AUTH_FAILING;
		if (saved_common_status.message == NULL)
		  saved_common_status = common_status;
	  }
	  else if (m->control_flag == AUTH_OPTIONAL
			   || m->control_flag == AUTH_SUFFICIENT
			   || m->control_flag == AUTH_USER_SUFFICIENT) {
		if (!ami->have_req_auth && saved_common_status.message == NULL)
		  saved_common_status = common_status;
		continue;
	  }
	  else {
		log_msg((LOG_ERROR_LEVEL, "Internal auth module processing error!"));
		goto fail;
	  }
	}
	else {
	  /*
	   * The local authentication module reported success.
	   */
	good_auth:

	  log_msg((LOG_INFO_LEVEL, "Auth module %d (\"%s\") succeeded",
			   auth_state.mindex, m->id));
	  auth_state.success_count++;
	  if (ami->mcount != 1) {
		for (i = 0; m->flags != NULL && m->flags[i] != NULL; i++) {
		  if (strcaseeq(m->flags[i], "ident")) {
			auth_state.mapped_username = mapped_username;
			break;
		  }
		}
	  }
	  else
		auth_state.mapped_username = mapped_username;

	  if ((m->style & AUTH_STYLE_INFOCARD
		   || m->style & AUTH_STYLE_MINFOCARD
		   || m->style & AUTH_STYLE_SINFOCARD)
		  && kwv_count(ic_auth.kwv_claims, NULL) > 0) {
		int c;
		Kwv_iter *iter;
		Kwv_pair *v;

		log_msg((LOG_TRACE_LEVEL, "Add %d claim variables...",
				 kwv_count(ic_auth.kwv_claims, NULL)));

		iter = kwv_iter_begin(ic_auth.kwv_claims, NULL);
		c = 0;
		for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter)) {
		  /*
		   * XXX here is where claims might be made visible to subsequent
		   * authentication module processing as new variables so that InfoCard
		   * contents might be used for other things...
		   */
		  log_msg((LOG_TRACE_LEVEL, "Claim %d: %s=\"%s\"",
				   c++, v->name, v->val));
		}
		kwv_iter_end(iter);
	  }

	  if (m->exit_eval != NULL) {
		if (auth_state.mapped_username != NULL) {
		  kwv_replace(kwv_auth, "CURRENT_USERNAME",
					  auth_state.mapped_username);
		  log_msg((LOG_DEBUG_LEVEL, "Pre EXIT*, CURRENT_USERNAME=%s",
				   auth_state.mapped_username));
		}

		st = auth_eval(m->exit_eval, kwv, kwv_auth, NULL, NULL);
		if (acs_expr_error_occurred(st)) {
		  log_msg((LOG_ERROR_LEVEL, "Invalid EXIT* directive: %s",
				   m->exit_eval));
		  init_common_status(&common_status, NULL, NULL,
							"EXIT* evaluation failed");
		  failure_reason = AUTH_FAILURE_REASON_USER_ERROR;
		  goto fail;
		}

		if ((p = kwv_lookup_value(kwv_auth, "ABORT")) != NULL
			&& strcaseeq(p, "yes")) {
		  rc = -1;
		  failure_reason = AUTH_FAILURE_REASON_ABORT;
		  goto fail;
		}

		if ((p = kwv_lookup_value(kwv_auth, "MODULE_SKIP")) != NULL
			&& strcaseeq(p, "yes")) {
		  kwv_delete(kwv_auth, "MODULE_SKIP");
		  log_msg((LOG_DEBUG_LEVEL, "Config requests module skip"));
		  init_common_status(&common_status, NULL, NULL,
							 "Config requests module skip");
		  rc = 0;
		  goto bad_auth;
		}

		if ((p = kwv_lookup_value(kwv_auth, "CURRENT_USERNAME")) != NULL) {
		  auth_state.mapped_username = p;
		  log_msg((LOG_DEBUG_LEVEL, "Post EXIT*, CURRENT_USERNAME=%s",
				   auth_state.mapped_username));
		}
	  }

	  if (((p = kwv_lookup_value(kwv_auth, "CURRENT_USERNAME")) == NULL
		   || *p == '\0') && auth_state.mapped_username != NULL) {
		kwv_replace(kwv_auth, "CURRENT_USERNAME", auth_state.mapped_username);
		log_msg((LOG_DEBUG_LEVEL,
				 "CURRENT_USERNAME=\"%s\" (from mapped_username)",
				 auth_state.mapped_username));
	  }

	  if (prompts != NULL) {
		if ((m->style & AUTH_STYLE_PROMPTED) == 0) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Prompt unexpectedly returned from %s", m->url));
		  init_common_status(&common_status, NULL, NULL,
							 "Configuration error");
		  goto fail;
		}

		/*
		 * Determine how to tell the caller what authentication information
		 * to prompt for.
		 * If the Auth clause has an OPTION directive that configures
		 * PAM_HANDLER_URL, use that, otherwise emit XML or crufty HTML.
		 */
		if ((p = kwv_lookup_value(m->kwv_options, "PAM_HANDLER_URL")) != NULL) {
		  char *handler, *url;
		  Auth_prompt *pr;
		  Ds *redir;
		  Jurisdiction *jp;

		  log_msg((LOG_TRACE_LEVEL, "Preparing redirect URL..."));
		  if (get_jurisdiction_meta(jurisdiction_name, &jp) == -1)
			goto fail;

		  if (*p == '/')
			handler = ds_xprintf("%s%s", jp->dacs_url, p);
		  else
			handler = p;
		  log_msg((LOG_TRACE_LEVEL, "Handler=\"%s\"", handler));

		  redir = ds_init(NULL);
		  ds_asprintf(redir, "%s%sservice=%s/dacs_authenticate",
					  handler,
					  (strchr(handler, (int) '?') == NULL) ? "?" : "&",
					  url_encode(jp->dacs_url, 0));
		  ds_asprintf(redir, "&auth_prompt_var_prefix=%s",
					  AUTH_PROMPT_VAR_PREFIX);
		  ds_asprintf(redir, "&DACS_VERSION=%s", DACS_VERSION_NUMBER);
		  ds_asprintf(redir, "&DACS_JURISDICTION=%s",
					  conf_val(CONF_JURISDICTION_NAME));
		  ds_asprintf(redir, "&DACS_BROWSER=1");
		  ds_asprintf(redir, "&ENABLE_AUTH_HANDLERS=%d",
					  auth_handlers_enabled);
		  if (conf_val(CONF_CSS_PATH) != NULL)
			ds_asprintf(redir, "&CSS_PATH=%s", conf_val(CONF_CSS_PATH));
		  else
			ds_asprintf(redir, "&CSS_PATH=%s", CSS_DIR);
		  ds_asprintf(redir, "&AUTH_TRANSID=%s", prompts->transid);
		  i = 1;
		  for (pr = prompts->head; pr != NULL; pr = pr->next) {
			ds_asprintf(redir, "&TYPE%d=%s", i, pr->type);
			if (pr->label != NULL)
			  ds_asprintf(redir, "&LABEL%d=%s", i, url_encode(pr->label, 0));
			if (pr->varname != NULL)
			  ds_asprintf(redir, "&NAME%d=%s", i, pr->varname);
			i++;
		  }
		  emit_http_header_redirect(stdout, ds_buf(redir));
		  emit_html_trailer(stdout);
		}
		else if (emit_xml) {
		  emit_xml_header(stdout, "dacs_auth_reply");
		  make_xml_prompts(prompts, 0, &p);
		  printf("<%s>", make_xml_root_element("dacs_auth_reply"));
		  printf("%s\n</dacs_auth_reply>\n", p);
		  emit_xml_trailer(stdout);
		}
		else if (test_emit_format(EMIT_FORMAT_JSON)) {
		  emit_json_header(stdout, "dacs_auth_reply");
		  make_json_prompts(prompts, 0, &p);
		  printf("%s", p);
		  emit_json_trailer(stdout);
		}
		else if (test_emit_format(EMIT_FORMAT_HTML)) {
		  char *inputs, *url;
		  Ds query;
		  Jurisdiction *jp;

		  /* For HTML, just create a simple form for now. */
		  if (get_jurisdiction_meta(jurisdiction_name, &jp) == -1)
			goto fail;

		  if (load_pam_auth_item("header", &p) != -1)
			printf("%s", p);
		  else {
			Html_header_conf *hc;

			hc = emit_html_header_conf(NULL);
			hc->no_cache = 1;
			hc->title = "DACS Authentication";
			if (conf_val(CONF_CSS_PATH) != NULL)
			  hc->css = ds_xprintf("%s/local_pam_authenticate.css",
								   conf_val(CONF_CSS_PATH));
			else
			  hc->css = CSS_DIR/**/"/local_pam_authenticate.css";
			emit_html_header(stdout, hc);
		  }

		  if (mapped_username != NULL)
			printf("Username <b>%s</b> is being authenticated.\n",
				   mapped_username);
		  if (load_pam_auth_item("prologue", &p) != -1)
			printf("%s", p);

		  if (load_pam_auth_item("instructions", &p) != -1)
			printf("%s", p);

		  url = ds_xprintf("%s/dacs_authenticate", jp->dacs_url);
		  ds_init(&query);
		  ds_asprintf(&query, "DACS_VERSION=%s", DACS_VERSION_NUMBER);
		  ds_asprintf(&query, "&DACS_BROWSER=1");
		  ds_asprintf(&query, "&ENABLE_AUTH_HANDLERS=%d",
					  auth_handlers_enabled);

		  if (load_pam_auth_item("form", &inputs) == -1)
			inputs = NULL;

		  p = make_pam_prompt_form(prompts, args->jurisdiction, url,
								   ds_buf(&query), inputs);
		  printf("%s", p);

		  if (load_pam_auth_item("epilogue", &p) != -1)
			printf("%s", p);

		  if (load_pam_auth_item("trailer", &p) != -1)
			printf("%s", p);
		  else
			emit_html_trailer(stdout);
		}
		else {
		  int show_tid;
		  Auth_prompt *pr;

		  show_tid = 0;
		  for (pr = prompts->head; pr != NULL; pr = pr->next) {
			if (streq(pr->type, "error"))
			  printf("Error: %s\n", pr->label);
			else if (pr->varname != NULL && pr->label != NULL) {
			  if (streq(pr->type, "text") || streq(pr->type, "password")) {
				printf("%s=\"%s\"\n", pr->varname, pr->label);
				show_tid = 1;
			  }
			}
		  }
		  if (show_tid && prompts->transid != NULL)
			printf("AUTH_TRANSID=\"%s\"\n", prompts->transid);
		}

		return(0);
	  }
	  else {
		if (role_rc == 1 && role_str != NULL && *role_str != '\0'
			&& (ami->saw_set_roles || ami->saw_add_roles)) {
		  if (ds_roles == NULL)
			ds_roles = ds_init(NULL);
		  if (ami->saw_set_roles)
			ds_asprintf(ds_roles, "%s", role_str);
		  else {
			/* XXX This assumes the format of a role string... */
			if (ds_len(ds_roles)) {
			  if (*role_str != '\0')
				ds_asprintf(ds_roles, ",%s", role_str);
			}
			else
			  ds_asprintf(ds_roles, "%s", role_str);

			kwv_replace(kwv_auth, "LAST_ROLES", ds_buf(ds_roles));
			kwv_replace(kwv_auth, "CURRENT_ROLES", ds_buf(ds_roles));
			log_msg((LOG_DEBUG_LEVEL, "Set CURRENT_ROLES to \"%s\"",
					 ds_buf(ds_roles)));
		  }
		}

		if (auth_style == AUTH_STYLE_UNKNOWN)
		  auth_style = m->style;
		else
		  auth_style |= m->style;

		if (m->control_flag == AUTH_SUFFICIENT
			|| m->control_flag == AUTH_USER_SUFFICIENT) {
		  if (auth_state.state != AUTH_FAILING)
			break;
		  failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
		  goto fail;
		}
	  }
	}
  }

  /* Erase potentially sensitive information! */
  if (args->password != NULL)
	strzap(args->password);
  envzap("SSL_CLIENT_CERT");
  if (client_cert != NULL)
	strzap(client_cert);
  if (args->xmlToken != NULL)
	strzap(args->xmlToken);

  /*
   * If a mapped username was not set by default (only one module) and not set
   * explicitly (through a flag), then use the last one provided.
   */
  if (auth_state.mapped_username == NULL) {
	if (mapped_username == NULL) {
	  failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
	  log_msg((LOG_WARN_LEVEL, "No username determined?"));
	  goto fail;
	}
	auth_state.mapped_username = mapped_username;
	kwv_replace(kwv_auth, "CURRENT_USERNAME", auth_state.mapped_username);
	log_msg((LOG_DEBUG_LEVEL, "CURRENT_USERNAME=%s (from mapped_username)",
			 auth_state.mapped_username));
  }

  if (ami->have_req_auth && auth_state.state == AUTH_FAILING) {
	failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
	goto fail;
  }
  if (!ami->have_req_auth && auth_state.success_count == 0) {
	failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
	goto fail;
  }

  if ((p = kwv_lookup_value(kwv_auth, "CURRENT_USERNAME")) != NULL) {
	auth_state.mapped_username = p;
	log_msg((LOG_DEBUG_LEVEL, "CURRENT_USERNAME=\"%s\"",
			 auth_state.mapped_username));
  }

  /* One last check... */
  if (!is_valid_auth_username(auth_state.mapped_username)) {
	log_msg((LOG_ERROR_LEVEL, "Final username is invalid"));
	failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
	goto fail;
  }

 do_roles:
  if (rmi != NULL && rmi->modules != NULL) {
	char *errmsg, *role_str, *u;
	Roles_module *rm;

	if (auth_state.mapped_username != NULL)
	  u = auth_state.mapped_username;
	else if (args->username != NULL)
	  u = args->username;
	else {
	  log_msg((LOG_ERROR_LEVEL, "No username for roles"));
	  goto fail;
	}

	role_str = NULL;
	errmsg = NULL;
	for (rm =  rmi->modules; rm != NULL; rm = rm->next) {
	  rc = roles_module_invoke(rm, u, args->password, args->jurisdiction,
							   kwv, kwv_auth, &role_str, &common_status,
							   &errmsg);
	  if (rc == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "Roles \"%s\" failed for username \"%s\"", rm->id, u));
		log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
		init_common_status(&common_status, NULL, NULL, errmsg);
		failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
		rc = 0;
		goto fail;
	  }

	  if (rc != 0) {
		if (ds_roles == NULL)
		  ds_roles = ds_init(NULL);

		/* XXX This assumes the syntax of a role string... */
		if (ds_len(ds_roles)) {
		  if (*role_str != '\0') {
			ds_asprintf(ds_roles, ",%s", role_str);
			log_msg((LOG_TRACE_LEVEL, "Appended to role string"));
		  }
		}
		else
		  ds_asprintf(ds_roles, "%s", role_str);

		kwv_replace(kwv_auth, "CURRENT_ROLES", ds_buf(ds_roles));
		log_msg((LOG_DEBUG_LEVEL,
				 "Set CURRENT_ROLES to \"%s\"", ds_buf(ds_roles)));
	  }
	}
  }

  if (ami->modules == NULL) {
	/* Did not perform authentication but there may be roles. */
	if (app_type == DACS_STANDALONE_NOARGS && dacsauth_out == NULL) {
	  if (ds_roles != NULL)
		printf("%s\n", ds_buf(ds_roles));
	}

	if (dacsauth_out != NULL) {
	  dacsauth_out->result = 0;
	  dacsauth_out->role_string = ds_buf(ds_roles);
	}

	log_msg((LOG_TRACE_LEVEL, "Leaving dacsauth_main()"));
	return(0);
  }

  /*
   * Authentication is successful.
   */

  identity = auth_identity(conf_val(CONF_FEDERATION_NAME),
						   conf_val(CONF_JURISDICTION_NAME),
						   auth_state.mapped_username, NULL);
  log_msg((LOG_DEBUG_LEVEL, "Identity is \"%s\"", identity));

  if (dacsauth_out != NULL) {
	dacsauth_out->result = 1;
	dacsauth_out->username = auth_state.mapped_username;
	dacsauth_out->identity = identity;
	if (ds_roles != NULL)
	  dacsauth_out->role_string = ds_buf(ds_roles);
  }

  if (app_type == DACS_STANDALONE_NOARGS) {
	if (dacsauth_out == NULL) {
	  if (show_identity)
		printf("%s\n", identity);

	  if (ds_roles != NULL)
		printf("%s\n", ds_buf(ds_roles));
	}

	log_msg((LOG_TRACE_LEVEL, "Leaving dacsauth_main()"));
	return(0);
  }

  p = kwv_lookup_value(kwv_auth, "CREDENTIALS_LIFETIME_SECS");
  if (p != NULL) {
	if (lifetime != NULL)
	  log_msg((LOG_DEBUG_LEVEL,
			   "Variable value for lifetime '%s' overrides module value '%s'",
			   p, lifetime));
	else
	  log_msg((LOG_DEBUG_LEVEL,
			   "Using variable value for lifetime '%s'", p));
	lifetime = p;
  }
  else {
	if (lifetime != NULL)
	  log_msg((LOG_DEBUG_LEVEL, "Using module value for lifetime '%s'",
			   lifetime));
	else
	  log_msg((LOG_DEBUG_LEVEL, "Using default value for lifetime '%s'",
			   default_lifetime));
  }

  if ((ua_str = getenv("HTTP_USER_AGENT")) == NULL)
	ua_str = "";

  /*
   * Issue new credentials, but check for revocation first.
   * If not revoked, get role information, and build and return the cookie.
   */
  credentials = make_credentials(NULL, NULL, auth_state.mapped_username,
								 remote_addr, "",
								 (lifetime != NULL)
								 ? lifetime : default_lifetime,
								 auth_style, AUTH_VALID_FOR_ACS, NULL, ua_str);

  if (vfs_lookup_item_type(ITEM_TYPE_REVOCATIONS) != NULL)
	it = "revocations";
  else
	it = NULL;

  if (it != NULL)
	rc = check_revocation(credentials, kwv, it, 0);
  if (rc == 1
	  || (rc == 0 && streq(credentials->valid_for, AUTH_VALID_FOR_NOTHING))) {
	log_msg((LOG_ALERT_LEVEL | LOG_AUDIT_FLAG,
			 "Access has been revoked for '%s'",
			 auth_identity(NULL, jurisdiction_name,
						   auth_state.mapped_username, NULL)));
	init_common_status(&common_status, NULL, NULL, "Authentication failed");
	/* Don't give any extra detail. */
	failure_reason = AUTH_FAILURE_REASON_INVALID_INFO;
	goto fail;
  }
  else if (rc == -1) {
	log_msg((LOG_ALERT_LEVEL, "Couldn't process revocation list"));
	init_common_status(&common_status, NULL, NULL, "Revocation testing error");
	goto fail;
  }

  if (ami->saw_set_roles)
	goto done_roles;

  if (collect_roles(auth_state.mapped_username, args->jurisdiction,
					kwv, kwv_auth, &ds_roles, &common_status, &failure_reason)
	  == -1) {
	rc = 0;
	goto fail;
  }

 done_roles:
  if (ds_roles != NULL) {
	if ((p = kwv_lookup_value(kwv_auth, "CURRENT_ROLES")) != NULL) {
	  credentials->role_str = p;
	  log_msg((LOG_DEBUG_LEVEL, "CURRENT_ROLES=%s", p));
	}
	else
	  credentials->role_str = ds_buf(ds_roles);
  }
  else
	credentials->role_str = "";

  /* Erase any remaining potentially sensitive information! */
  if (args->auxiliary != NULL)
	strzap(args->auxiliary);

  /* One last check */
  if (*credentials->role_str != '\0') {
	if (!is_valid_role_str(credentials->role_str)) {
	  log_msg((LOG_ERROR_LEVEL, "Final role string is invalid: \"%s\"",
			   credentials->role_str == NULL ? "" : credentials->role_str));
	  log_msg((LOG_ERROR_LEVEL,
			   "Final role string is invalid, ignoring roles"));
	  credentials->role_str = "";
	  kwv_replace(kwv_auth, "CURRENT_ROLES", "");
	}
  }
  else
	log_msg((LOG_DEBUG_LEVEL, "No roles were provided"));

  if (make_set_auth_cookie_header(credentials, NULL, 0, &bp) == -1) {
	init_common_status(&common_status, NULL, NULL, "Couldn't create cookie");
	goto fail;
  }

  if (selected) {
	reset_scredentials(credentials);
	credentials->selected = 1;
	make_set_scredentials_cookie_header(credentials, &sp);
  }
  else
	sp = NULL;

  if (browser)
	printf("%s%s", bp, (sp != NULL) ? sp : "");

  log_msg((LOG_DEBUG_LEVEL | LOG_AUDIT_FLAG,
		   "Authentication succeeded for %s",
		   auth_identity(NULL, credentials->home_jurisdiction,
						 credentials->username, auth_tracker(credentials))));
  log_msg((LOG_TRACE_LEVEL, "Identity: %s",
		   make_ident_from_credentials(credentials)));

  /* Evaluate 'success expressions', if provided. */
  auth_success(kwv, kwv_auth);

  /*
   * Since authentication succeeded, remove the lockfile if it exists.
   * This can fail, either because there is no lockfile or as a result of
   * file or directory permissions.
   */
  if (args->username != NULL) {
	if (auth_lockfile == NULL)
	  auth_lockfile = create_temp_filename(args->username);
	remove_lock(auth_lockfile);
  }

  /* Figure out what needs to be returned to the user. */
  if ((success_handler = lookup_success_handler()) == NULL) {
	log_msg((LOG_ALERT_LEVEL, "lookup_success_handler() failed?!"));
	return(-1);
  }

  /*
   * If the caller provides a URL, use it regardless.
   * Otherwise, the caller must explicitly enable handlers.
   */
  if ((p = args->dacs_auth_success_handler) != NULL && *p != '\0') {
	if (streq(p, "DACS_ERROR_URL")) {
	  if ((p = kwv_lookup_value(kwv, "DACS_ERROR_URL")) == NULL
		  || *p == '\0')
		p = "/DACS_ERROR_URL-was-not-passed-to-dacs_authenticate";
	}

	emit_http_header_redirect(stdout, p);
	emit_html_trailer(stdout);
  }
  else if (got_authorization && args->dacs_error_url != NULL) {
	emit_http_header_redirect(stdout, args->dacs_error_url);
	emit_html_trailer(stdout);
  }
  else if (!auth_handlers_enabled || handler_error
		   || success_handler->type == SUCCESS_HANDLER_CREDENTIALS) {
	if (emit_xml) {
	  emit_xml_header(stdout, "dacs_auth_reply");
	  printf("<%s>\n", make_xml_root_element("dacs_auth_reply"));
	  printf("%s", make_xml_dacs_current_credentials(credentials, NULL, 0));
	  printf("</dacs_auth_reply>\n");
	  emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_JSON)) {
	  emit_json_header(stdout, "dacs_auth_reply");
	  printf("%s", make_json_dacs_current_credentials(credentials, NULL, 0));
	  printf(" }\n");
	  emit_json_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_HTML)) {
	  Credentials *cr;

	  cr = credentials;
	  emit_html_header(stdout, NULL);
	  printf("New credentials were issued for <B>%s</B>\n",
			 auth_identity(NULL, cr->home_jurisdiction, cr->username, NULL));
	  if (cr->role_str != NULL && *cr->role_str != '\0')
		printf(" with roles <TT><B>%s</B></TT>\n", cr->role_str);
	  if (trace_level > 1) {
		printf("\n<P>");
		printf("expires: %lu<BR>\n",
			   (unsigned long) credentials->expires_secs);
		printf("lifetime: %s secs<BR>\n",
			   lifetime != NULL ? lifetime : default_lifetime);
		if (args->password != NULL && args->password[0] != '\0')
		  printf("password='%s'<BR>\n", "");
		if (args->auxiliary != NULL && args->auxiliary[0] != '\0')
		  printf("aux='%s'<BR>\n", "");
		if (credentials->role_str)
		  printf("roles='%s'<BR>\n", credentials->role_str);
		printf("auth_style='%s'<BR>\n",
			   auth_style_to_string(credentials->auth_style));
		printf("unique='%s'<BR>\n", credentials->unique);
	  }
	  emit_html_trailer(stdout);
	}
	else {
	  Credentials *cr;

	  cr = credentials;
	  emit_plain_header(stdout);
	  printf("New credentials were issued for \"%s\"\n",
			 auth_identity(NULL, cr->home_jurisdiction, cr->username, NULL));
	  if (cr->role_str != NULL && *cr->role_str != '\0')
		printf("With roles \"%s\"\n", cr->role_str);
	  emit_plain_trailer(stdout);
	}
  }
  else if (success_handler->type == SUCCESS_HANDLER_URL) {
	Ds ds;

	ds_init(&ds);
	ds_asprintf(&ds, "%s%s", success_handler->success_string,
				(strchr(success_handler->success_string, '?') != NULL)
				? "&" : "?");
	ds_asprintf(&ds, "DACS_VERSION=%s", DACS_VERSION_NUMBER);
	if ((p = federation_name) != NULL)
	  ds_asprintf(&ds, "&DACS_FEDERATION=%s", p);
	if ((p = jurisdiction_name) != NULL)
	  ds_asprintf(&ds, "&DACS_JURISDICTION=%s", p);
	ds_asprintf(&ds, "&DACS_USERNAME=%s", credentials->username);
	ds_asprintf(&ds, "&FORMAT=%s", get_emit_format());
	emit_http_header_redirect(stdout, ds_buf(&ds));
	emit_html_trailer(stdout);
  }
  else if (success_handler->type == SUCCESS_HANDLER_FILE) {
	char *ptr;
	size_t file_size;

	if (load_file(success_handler->success_string, &ptr, &file_size) == -1) {
	  log_msg((LOG_ALERT_LEVEL,
			   "Could not load file '%s' for AUTH_SUCCESS_HANDLER",
			   success_handler->success_string));
	  handler_error = 1;
	}
	else {
	  fflush(stdout);
	  /* XXX Errors ignored... */
	  write_buffer(fileno(stdout), ptr, file_size);
	}
  }
  else if (success_handler->type == SUCCESS_HANDLER_MESSAGE) {
	if (!emit_xml) {
	  emit_html_header(stdout, NULL);
	  printf("%s\n", success_handler->success_string);
	  emit_html_trailer(stdout);
	}
  }
  else {
	/* Error - emit nothing */
	log_msg((LOG_ALERT_LEVEL, "Broken AUTH_SUCCESS_HANDLER handling?"));
	return(-1);
  }

  return(0);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsauth_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
