/*
 * 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.
 ***************************************************************************/

/*
 * Access control rule processing
 */

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

#include "dacs.h"

#include <sys/param.h>

static char *log_module_name = "dacs_acs";

/*************************************************************************/

#ifdef NOTDEF
static void
acs_xml_html(FILE *fp, Acl_rule *acl)
{

}
#endif

/*
 * Cookies and authorizations must only be passed to trusted programs.
 * The DACS proxy_exec program is always trusted.
 */
static int proxy_pass_authorization_flag = 0;
static int pass_env_flag = 1;

static int proxy_static_flag = 0;
static int proxy_exec_flag = 0;

typedef struct DACS_acs_args {
  int check_only;
  int check_fail;
  int status_line;
  int visible;
  char *format;
  char *rname;
} DACS_acs_args;

static DACS_acs_args *
init_dacs_acs_args(DACS_acs_args *a)
{
  DACS_acs_args *args;

  if (a == NULL)
	args = ALLOC(DACS_acs_args);
  else
	args = a;
  
  args->check_only = 0;
  args->check_fail = 0;
  args->visible = 0;
  args->status_line = 0;
  args->format = NULL;
  args->rname = NULL;

  return(args);
}

static int
parse_dacs_acs_args(char *value, DACS_acs_args *a)
{
  int i, n, nargs;
  char **argv;
  DACS_acs_args *args;
  Mkargv conf = { 0, 0, " ", NULL, NULL };

  if ((n = mkargv(value, &conf, &argv)) == -1)
	return(-1);

  args = init_dacs_acs_args(a);

  /*
   * We allow flags to be repeated, which can make it easier for the
   * caller.
   */
  nargs = 0;
  for (i = 0; i < n; i++) {
	if (strcaseeq(argv[i], "-check_only"))
	  args->check_only++;
	else if (strcaseeq(argv[i], "-check_fail"))
	  args->check_fail++;
	else if (strcaseeq(argv[i], "-status_line"))
	  args->status_line = 1;
	else if (strcaseeq(argv[i], "-no_status_line"))
	  args->status_line = -1;
	else if (strcaseeq(argv[i], "-visible"))
	  args->visible = 1;
	else if (strcaseeq(argv[i], "-invisible"))
	  args->visible = 0;
	else if (strcaseeq(argv[i], "-format")) {
	  if (++i == n)
		return(-1);
	  args->format = argv[i];

	  /* Set the requested format now, as early as possible. */
	  if (set_emit_format(args->format) == -1)
		log_msg((LOG_ERROR_LEVEL, "Ignoring invalid format: \"%s\"",
				 args->format));
	  else
		log_msg((LOG_TRACE_LEVEL, "DACS_ACS sets format to \"%s\"",
				 args->format));
	}
	else if (strcaseeq(argv[i], "-rname"))
	  args->rname = argv[++i];
	else
	  return(-1);

	nargs++;
  }

  if ((args->check_only + args->check_fail) > 1)
	return(-1);

  return(nargs);
}

/*
 * If DACS_ACS appears as an argument in the query component of a URI
 * (VALUE), remove it.  NAME is variable name/key associated with VALUE.
 * This assumes that the query string has been properly formatted/escaped.
 */
static void
clean_query(char *name, char *value)
{
  char *p, *s;

  if ((s = strstr(value, "DACS_ACS=")) != NULL
	  && (s == value || *(s - 1) == '&')) {
	p = s + 9;
	while (*p != '\0' && *p != '&')
	  p++;
	if (*p == '&')
	  p++;
	while (*p != '\0')
	  *s++ = *p++;
	*s = *p;
	log_msg((LOG_TRACE_LEVEL, "Set %s=%s", name, value));
  }
}

typedef struct Acs_activity {
  char *jurisdiction;	/* Name of jurisdiction where activity occurred */
  time_t req_time;		/* Absolute date/time of activity */
} Acs_activity;

static Acs_activity *
parse_activity_cookie(char *value)
{
  char *p;
  Acs_activity *a;

  if ((p = strchr(value, (int) '@')) == NULL)
	return(NULL);
  a = ALLOC(Acs_activity);
  a->jurisdiction = strndup(value, p - value);
  if (strnum(p + 1, STRNUM_TIME_T, &a->req_time) == -1)
	return(NULL);

  return(a);
}

static char *
make_activity_cookie_name(void)
{
  char *cn;

  cn = ds_xprintf("DACS:%s::::ACTIVITY", conf_val(CONF_FEDERATION_NAME));

  return(cn);
}

static int
is_activity_cookie_name(char *name)
{
  char *cn;

  cn = make_activity_cookie_name();
  if (streq(name, cn))
	return(1);

  return(0);
}

/*
 * An activity cookie should only be emitted if there are valid selected
 * credentials and either ACS_TRACK_ACTIVITY or ACS_INACTIVITY_LIMIT_SECS
 * have been configured.
 * XXX a non-XML kludge, intended to be temporary
 */
static char *
make_activity_cookie(void)
{
  char *cn, *p, *v;
  time_t now;

  time(&now);
  v = ds_xprintf("%s@%lu",
				 conf_val(CONF_JURISDICTION_NAME), (unsigned long) now);
  cn = make_activity_cookie_name();
  p = ds_xprintf("%s=%s; domain=%s; path=/",
				 cn, v, conf_val(CONF_FEDERATION_DOMAIN));

  return(p);
}

/*
 * A user is deemed to have become inactive iff the inactivity feature has been
 * enabled at this jurisdiction and:
 * 1) an activity record has been provided and its req_time (which is relative
 * to the clock at the jurisdiction that produced the record) is older than
 * the configured inactivity limit (relative to the current jurisdiction's
 * clock) OR
 * 2) no activity record has been provided but at least one set of valid
 * selected credentials were provided and the newest of them is not older
 * than the configured inactivity limit
 * If a user is inactive, the current request is rejected.
 * An unauthenticated user cannot become inactive.
 */
static int
acs_is_inactive(Credentials *selected, Acs_activity *a)
{
  char *p;
  time_t inactivity_limit_secs, now;
  Credentials *cr, *cr_newest;

  if (selected == NULL)
	return(0);

  if ((p = conf_val(CONF_ACS_INACTIVITY_LIMIT_SECS)) == NULL) {
	log_msg((LOG_TRACE_LEVEL, "No inactivity limit"));
	return(0);
  }

  if (strnum(p, STRNUM_TIME_T, &inactivity_limit_secs) == -1
	  || inactivity_limit_secs == 0) {
	log_msg((LOG_ERROR_LEVEL, "Invalid ACS_INACTIVITY_LIMIT_SECS value"));
	log_msg((LOG_ERROR_LEVEL, "Inactivity feature is disabled"));
	return(0);
  }
  log_msg((LOG_TRACE_LEVEL, "Inactivity limit is %lu secs",
		   (unsigned long) inactivity_limit_secs));

  time(&now);
  if (a == NULL) {
	cr_newest = selected;
	for (cr = selected->next; cr != NULL; cr = cr->next) {
	  if (cr->auth_time > cr_newest->auth_time)
		cr_newest = cr;
	}
	if (cr_newest->auth_time > now) {
	  /* If the clocks are out of whack, it's pointless to continue. */
	  log_msg((LOG_ERROR_LEVEL, "Improper clock synchronization, inactivity detection is disabled"));
	  return(0);
	}

	if ((now - cr_newest->auth_time) <= inactivity_limit_secs)
	  return(0);

	log_msg((LOG_DEBUG_LEVEL,
			 "No activity record, but credentials are too old"));
	return(1);
  }

  if (a->req_time > now) {
	/* If the clocks are out of whack, it's pointless to continue. */
	log_msg((LOG_ERROR_LEVEL, "Improper clock synchronization, inactivity detection is disabled"));
	return(0);
  }

  if ((now - a->req_time) <= inactivity_limit_secs)
	return(0);

  log_msg((LOG_DEBUG_LEVEL, "Activity record indicates inactivity"));
  return(1);
}

int
main(int argc, char **argv)
{
  int error_occurred, i, ncred, st, xargc;
  int emitted_activity_cookie, in_error_handler, emit_activity_cookie;
  unsigned int alimit, ncookies;
  char **xargv, *p, *proxy_auth, *proxied_uri, *request_uri;
  char *service_authorization, *service_method, *service_uri;
  char *content_type, *errmsg, *remote_addr, *remote_host, *truncated, *uri;
  char *dacs_acs_str, *htype, *web_server_env;
  Acs_activity *activity;
  Acs_denial_reason denial_reason;
  Acs_post_exception_mode post_exception_mode;
  Acs_result result;
  Credentials *credentials, *selected;
  Cookie *cookies;
  DACS_acs_args dacs_acs_args;
  Ds ds;
  Kwv *args, *kwv, *proxy_kwv;
  Kwv_pair *v;
  Kwv_conf kwv_conf = {
	"=", NULL, " ", KWV_CONF_DEFAULT, NULL, INIT_CGI_KWV, NULL, NULL
  };
  Rlink *rlink;
  Scredentials *scredentials;

  errmsg = "Invalid environment or internal error";
  denial_reason = ACS_DENIAL_REASON_UNKNOWN;
  in_error_handler = 0;
  args = NULL;
  request_uri = service_uri = NULL;
  dacs_acs_args.check_only = 0;
  dacs_acs_args.check_fail = 0;
  dacs_acs_args.format = NULL;
  error_occurred = 0;
  ncred = 0;
  credentials = NULL;
  service_authorization = NULL;
  emit_activity_cookie = 0;
  emitted_activity_cookie = 0;
  activity = NULL;

  xargc = argc;
  xargv = argv;
  if (dacs_init(DACS_ACS, &argc, &argv, &kwv, &errmsg) == -1) {
	char *denial_msg, *uri_path;
	Acs_error_handler *handler;
	Http_auth *auth;

	error_occurred = 1;

  fail:
	log_msg((LOG_DEBUG_LEVEL,
			 "Command line: %d arg%s", xargc, xargc != 1 ? "s" : ""));
	for (i = 0; i < xargc; i++)
	  log_msg((LOG_DEBUG_LEVEL, "  Arg%d: \"%s\"", i, xargv[i]));

	if (errmsg != NULL)
	  log_msg((LOG_NOTICE_LEVEL, "%s", errmsg));

	if (dacs_service_uri != NULL)
	  p = ds_xprintf(" for %s", dacs_service_uri);
	else
	  p = "";
	log_msg((LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG,
			 "*** Access denied to %s%s",
			 auth_identities_from_credentials_track(credentials), p));

	if (emit_activity_cookie && !emitted_activity_cookie) {
	  acs_emit_set_header("Set-Cookie: %s", make_activity_cookie());
	  log_msg((LOG_TRACE_LEVEL, "Emitting activity cookie"));
	  emitted_activity_cookie = 1;
	}

	switch (denial_reason) {
	case ACS_DENIAL_REASON_NO_RULE:
	  denial_msg = "Access denied, no applicable rule";
	  break;
	case ACS_DENIAL_REASON_BY_RULE:
	  denial_msg = "Access denied, forbidden by rule";
	  break;
	case ACS_DENIAL_REASON_NO_AUTH:
	  denial_msg = "Access denied, user not authenticated";
	  break;
	case ACS_DENIAL_REASON_REVOKED:
	  denial_msg = "Access denied, user access revoked";
	  break;
	case ACS_DENIAL_REASON_BY_REDIRECT:
	  denial_msg = "Access denied, redirect";
	  break;
	case ACS_DENIAL_REASON_BY_SIMPLE_REDIRECT:
	  denial_msg = "Access denied, simple redirect";
	  break;
	case ACS_DENIAL_REASON_ACK_NEEDED:
	  denial_msg = "Access denied, acknowledgement needed";
	  break;
	case ACS_DENIAL_REASON_LOW_AUTH:
	  denial_msg = "Access denied, low authentication level";
	  break;
	case ACS_DENIAL_REASON_CREDENTIALS_LIMIT:
	  denial_msg = "Access denied, too many credentials were submitted";
	  break;
	case ACS_DENIAL_REASON_INACTIVITY:
	  denial_msg = "Access denied, inactivity";
	  break;
	default:
	case ACS_DENIAL_REASON_UNKNOWN:
	  denial_msg = "Access denied, internal error";
	  break;
	}

	if ((dacs_acs_args.status_line != -1
		 && (conf_val_eq(CONF_STATUS_LINE, "on")
			 || dacs_acs_args.check_only
			 || dacs_acs_args.check_fail))
		|| dacs_acs_args.status_line == 1) {
	  if (error_occurred || denial_reason == ACS_DENIAL_REASON_UNKNOWN)
		acs_emit_set_header("DACS-Status-Line: DACS-%s 799 Access error",
							dacs_version_release);
	  else
		acs_emit_set_header("DACS-Status-Line: DACS-%s 797 Access denied, %s",
							dacs_version_release,
							auth_identity_from_credentials_track(credentials));
	}

	if (dacs_acs_args.check_only || dacs_acs_args.check_fail) {
	  log_msg((LOG_TRACE_LEVEL, "Check only - prevent Apache from executing"));
	  acs_emit_header("check_only", "yes");
	  acs_emit_set_header("Cache-Control: no-cache");
	  acs_emit_set_header("Pragma: no-cache");
	  if (error_occurred || denial_reason == ACS_DENIAL_REASON_UNKNOWN)
		acs_emit_access_error(stdout, denial_reason, errmsg);
	  else
		acs_emit_access_denial(stdout, denial_reason, dacs_service_uri,
							   denial_msg, &result);
	  exit(1);
	}

	current_uri_pqf(&uri_path, NULL, NULL);

	if ((p = conf_val(CONF_ACS_FAIL)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Evaluating ACS_FAIL expr: \"%s\"", p));
	  st = acs_eval(p, kwv, args, result.cr, NULL);
	  if (acs_expr_error_occurred(st))
		log_msg((LOG_ERROR_LEVEL, "Invalid ACS_FAIL directive: \"%s\"", p));
	}

	/*
	 * If an error handler has been configured as a full URL, then pass
	 * back to Apache an error document string for it to use as a custom 403
	 * response.  The configured URL may contain a properly escaped query
	 * string.  Additional information about the failed request is appended
	 * to the query string.
	 *
	 * If an error handler has been specified as the word "reason", then
	 * DACS will emit an error string for Apache to use.
	 * Any other error handler will be passed as-is to Apache.
	 *
	 * The value returned by DACS is terminated by a newline.
	 *
	 * If no error handler has been configured for DACS, then Apache will
	 * use whatever ErrorDocument or default behaviour has been configured
	 * for it.
	 *
	 * If an error occurs while in an error handler, take care not
	 * to recurse.
	 */
	if (result.redirect_action != NULL)
	  handler = acs_init_error_handler(ds_xprintf("%d", result.denial_reason),
									   NULL, result.redirect_action,
									   ACS_HANDLER_NONE);
	else if (denial_reason == ACS_DENIAL_REASON_NO_AUTH
			 && (conf_val_eq(CONF_HTTP_AUTH_ENABLE, "post_acs_only")
				 || conf_val_eq(CONF_HTTP_AUTH_ENABLE, "yes")
				 || conf_val_eq(CONF_HTTP_AUTH_ENABLE, "both"))
			 && (auth = http_auth_match_directive(uri_path)) != NULL
			 && !auth->pre_flag
			 && auth->scheme != HTTP_AUTH_EXCEPT) {
	  char *auth_message, *www_authenticate;
	  Kwv *akwv;

	  /*
	   * Access is being denied due to lack of credentials, but HTTP
	   * authentication applies to this request.
	   * Construct a WWW-Authenticate header; we'll either return it to
	   * the user or forward it to dacs_authenticate.
	   * By default, dacs_authenticate will be invoked at the current
	   * jurisdiction; the config variable http_auth_jurisdiction can
	   * specify an alternative jurisdiction name (from which we'll look up
	   * the absolute URL for its dacs_authenticate).
	   *
	   * If an Authorization header was received, then the authentication
	   * request being processed is the response to the WWW-Authenticate
	   * request sent previously by DACS.
	   * Deny access and redirect the user to dacs_authenticate with all of
	   * the arguments it needs.
	   */
	  akwv = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");

	  if (service_authorization != NULL) {
		char *buf, *auth_jurisdiction, *auth_url, *error_url, *q;
		unsigned char *encrypted_authorization, *encrypted_opaque;
		unsigned int len;
		Crypt_keys *ck;
		Http_auth_authorization *aa;
		Jurisdiction *jp;

		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "Not authenticated, saw Authorization header: \"%s\"",
				 service_authorization));

		if ((ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Cannot get federation_keys"));
		  exit(1);
		}
		if ((q = strcaseprefix(service_authorization, "Digest ")) != NULL) {
		  aa = http_auth_authorization_parse(service_authorization, &errmsg);
		  if (aa == NULL) {
			log_msg((LOG_ERROR_LEVEL | LOG_SENSITIVE_FLAG,
					 "Error parsing Authorization header: \"%s\"",
					 service_authorization));
			exit(1);
		  }
		  if (stra64b(aa->opaque, &encrypted_opaque, &len) == NULL) {
			log_msg((LOG_ERROR_LEVEL, "Base64 decoding error"));
			exit(1);
		  }
		  if (crypto_decrypt_string(ck, encrypted_opaque, len,
									(unsigned char **) &www_authenticate,
									NULL) == -1) {
			log_msg((LOG_ERROR_LEVEL, "Decryption error"));
			exit(1);
		  }
		}
		else if ((q = strcaseprefix(service_authorization, "Basic "))
				 != NULL) {
		  www_authenticate = http_auth_basic_auth(auth);
		}
		else {
		  log_msg((LOG_ERROR_LEVEL, "Unrecognized scheme"));
		  exit(1);
		}
		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "Got WWW-Authenticate: \"%s\"", www_authenticate));

		if ((auth_jurisdiction
			 = kwv_lookup_value(akwv, "http_auth_jurisdiction")) == NULL)
		  auth_jurisdiction = conf_val(CONF_JURISDICTION_NAME);
		if (get_jurisdiction_meta(auth_jurisdiction, &jp) == -1
			|| jp->dacs_url == NULL || *jp->dacs_url == '\0') {
		  log_msg((LOG_ERROR_LEVEL,
				   "Cannot get jurisdiction meta info for \"%s\"",
				   auth_jurisdiction));
		  exit(1);
		}

		len = crypto_encrypt_string(ck,
									(unsigned char *) service_authorization,
									strlen(service_authorization) + 1,
									&encrypted_authorization);
		crypt_keys_free(ck);
		strba64(encrypted_authorization, len, &buf);
		if ((p = make_error_url(kwv)) != NULL)
		  error_url = ds_xprintf("&DACS_ERROR_URL=%s", url_encode(p, 0));
		else
		  error_url = "";
		auth_url = ds_xprintf("%s/dacs_authenticate", jp->dacs_url);
		printf("%s?", auth_url);
		printf("DACS_JURISDICTION=%s", auth_jurisdiction);
		if ((p = kwv_lookup_value(kwv, "SERVICE_METHOD")) == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Can't find SERVICE_METHOD?"));
		  exit(1);
		}
		printf("&DACS_REQUEST_METHOD=%s", p);
		printf("&DACS_BROWSER=1");
		printf("%s", error_url);
		printf("&AUTHORIZATION=%s", buf);
		printf("&WWW_AUTHENTICATE=%s\n", url_encode(www_authenticate, 0));

		exit(1);
	  }

	  /*
	   * There is no Authorization header, so just deny access and emit
	   * the WWW-Authenticate header.
	   */
	  if (auth->scheme == HTTP_AUTH_BASIC
		  || auth->scheme == HTTP_AUTH_UNKNOWN)
		www_authenticate = http_auth_basic_auth(auth);
	  else if (auth->scheme == HTTP_AUTH_DIGEST)
		www_authenticate = http_auth_digest_auth(auth, uri_path);
	  else {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized HTTP auth scheme: \"%s\"",
				 auth->scheme_name));
		exit(1);
	  }

	  if ((auth_message = kwv_lookup_value(akwv, "http_auth_message")) == NULL)
		auth_message = ds_xprintf("%d Authentication by DACS is required",
								  ACS_DENIAL_REASON_NO_AUTH);
	  acs_emit_set_header("WWW-Authenticate: %s", www_authenticate);
	  acs_emit_set_header("Status: 401");
	  if (auth_message != NULL && *auth_message != '\0')
		fprintf(stdout, "%s\n", auth_message);
	  fflush(stdout);
	  log_msg((LOG_DEBUG_LEVEL, "Set-Header WWW-Authenticate: \"%s\"",
			   www_authenticate));

	  exit(1);
	}
	else {
	  log_msg((LOG_DEBUG_LEVEL, "Looking up handler for reason code %d",
			   denial_reason));
	  handler = acs_lookup_error_handler(denial_reason, dacs_service_uri);	
	  if (handler == NULL) {
		log_msg((LOG_INFO_LEVEL, "No handler is configured... exiting"));
		exit(1);
	  }
	}

	if (in_error_handler) {
	  log_msg((LOG_WARN_LEVEL, "Handler will not execute another handler"));
	  exit(1);
	}

	if (handler == NULL) {
	  /*
	   * This can happen if a fatal error occurs before configuration processing
	   * completes.
	   */
	  log_msg((LOG_ERROR_LEVEL,
			   "An error has occurred before a handler could be configured"));
	  log_msg((LOG_ERROR_LEVEL, "Exiting"));
	  exit(1);
	}

	if (handler->type == ACS_HANDLER_DEFAULT)
	  htype = "Default";
	else if (handler->type == ACS_HANDLER_URL)
	  htype = "URL";
	else if (handler->type == ACS_HANDLER_LOCAL_URL)
	  htype = "Local URL";
	else if (handler->type == ACS_HANDLER_REASON)
	  htype = "Reason";
	else if (handler->type == ACS_HANDLER_MESSAGE)
	  htype = "Message";
	else if (handler->type == ACS_HANDLER_EXPR)
	  htype = "Expr";
	else
	  htype = "???";
	log_msg((LOG_DEBUG_LEVEL,
			 "Invoking handler type '%s' for reason code %d",
			 htype, denial_reason));

	ds_init(&ds);
	if (handler->type == ACS_HANDLER_URL
		|| handler->type == ACS_HANDLER_LOCAL_URL) {
	  if (denial_reason == ACS_DENIAL_REASON_BY_SIMPLE_REDIRECT)
		ds_asprintf(&ds, "%s", handler->handler_action);
	  else {
		ds_asprintf(&ds, "%s%s", handler->handler_action,
					(strchr(handler->handler_action, '?') != NULL)
					? "&" : "?");
		ds_asprintf(&ds, "DACS_ERROR_CODE=%d", denial_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);
		if ((p = kwv_lookup_value(kwv, "SERVICE_HOSTNAME")) != NULL)
		  ds_asprintf(&ds, "&DACS_HOSTNAME=%s", url_encode(p, 0));
		if ((p = kwv_lookup_value(kwv, "SERVICE_USER_AGENT")) != NULL)
		  ds_asprintf(&ds, "&DACS_USER_AGENT=%s", url_encode(p, 0));
		if ((p = kwv_lookup_value(kwv, "SERVICE_METHOD")) != NULL)
		  ds_asprintf(&ds, "&DACS_REQUEST_METHOD=%s", url_encode(p, 0));

		if (denial_reason == ACS_DENIAL_REASON_ACK_NEEDED)
		  ds_asprintf(&ds, "%s",
					  acs_make_notice_presentation_args(&result, NULL, NULL,
														NULL, NULL));

		/*
		 * This must be last; because it may contain problematic characters,
		 * such as a '?' or '&' character, it must be encoded.
		 */
		if ((p = make_error_url(kwv)) != NULL)
		  ds_asprintf(&ds, "&DACS_ERROR_URL=%s", url_encode(p, 0));
	  }

	  log_msg((LOG_DEBUG_LEVEL, "Redirecting to: \"%s\"", ds_buf(&ds)));
	  printf("%s\n", ds_buf(&ds));
	}
	else if (handler->type == ACS_HANDLER_REASON) {
	  /* This must not begin like a URL or with a '/' */
	  printf("%d \"%s\"", denial_reason, denial_msg);
	  if (is_internet_explorer(kwv_lookup_value(kwv, "SERVICE_USER_AGENT"))) {
		int i;

		for (i = 0; i < 200; i++)
		  printf("  ");
	  }
	  printf("\n");
	}
	else if (handler->type == ACS_HANDLER_DEFAULT) {
	  /* Emit nothing. */
	}
	else if (handler->type == ACS_HANDLER_EXPR) {
	  char *result_str;

	  /*
	   * The message must not begin like a URL or with a '/', but an initial
	   * double quote should be stripped off by Apache.
	   */
	  log_msg((LOG_TRACE_LEVEL, "Evaluating handler expr: \"%s\"",
			   handler->handler_action));
	  st = acs_eval(handler->handler_action, kwv, args, result.cr,
					&result_str);
	  if (acs_expr_error_occurred(st)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid handler expr: \"%s\"",
				 handler->handler_action));
		/* Error - emit nothing */
	  }
	  else {
		printf("\"%s\n", result_str);
		if (is_internet_explorer(kwv_lookup_value(kwv, "SERVICE_USER_AGENT"))) {
		  int i;

		  for (i = 0; i < 200; i++)
			printf("  ");
		}
	  }
	}
	else if (handler->type == ACS_HANDLER_MESSAGE) {
	  /*
	   * The message must not begin like a URL or with a '/', but an initial
	   * double quote should be stripped off by Apache.
	   *
	   * The Content-Type is always set to text/html by Apache
	   * http://issues.apache.org/bugzilla/show_bug.cgi?id=36411
	   */

	  /*
	   * server/core.c:set_error_document() prepends a double quote to its
	   * <document> argument if it does not begin with a slash or look like
	   * a URL.
	   * If core.c:ap_custom_response() is used instead of the ErrorDocument
	   * directive to set the error document, there seems to be a bug
	   * wrt dealing with a message - see
	   * http://issues.apache.org/bugzilla/show_bug.cgi?id=42430
	   * Also see modules/http/http_protocol.c:ap_send_error_response().
	   */
	  printf("\"%s", strdequote(handler->handler_action));
	  if (is_internet_explorer(kwv_lookup_value(kwv, "SERVICE_USER_AGENT"))) {
		int i;

		for (i = 0; i < 200; i++)
		  printf("  ");
	  }
	  printf("\n");
	}
	else {
	  /* Error - emit nothing */
	}

	exit(1);
  }

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-proxy-static")) {
#ifdef NOTDEF
	  proxy_static_flag = 1;
#else
	  /* Not currently supported */
	  proxy_static_flag = 0;
#endif
	}
	else if (streq(argv[i], "-proxy-exec")) {
#ifdef NOTDEF
	  proxy_exec_flag = 1;
#else
	  /* Not currently supported */
	  proxy_exec_flag = 0;
#endif
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Unrecognized argument: %s", argv[i]));
	  errmsg = "Bad command line argument";
	  error_occurred = 1;
	  goto fail;
	}
  }

  log_msg((LOG_TRACE_LEVEL,
		   "Command line: %d arg%s", xargc, xargc != 1 ? "s" : ""));
  for (i = 0; i < xargc; i++)
	log_msg((LOG_TRACE_LEVEL, "  Arg%d: \"%s\"", i, xargv[i]));
  log_msg((LOG_TRACE_LEVEL,
		   "proxy_static=%d, proxy_exec=%d, proxy_pass_authorization=%d",
		   proxy_static_flag, proxy_exec_flag,
		   proxy_pass_authorization_flag));

  if (proxy_exec_flag && proxy_static_flag) {
	errmsg = "Both -proxy-exec and -proxy-static specified!";
	error_occurred = 1;
	goto fail;
  }

  /*
   * Our standard input comes from mod_auth_dacs.
   * It is a series of "name=value\n" strings.
   * The maximum length of the input written to us is determined by the
   * configuration of mod_auth_dacs.
   */
  ds_init(&ds);
  if ((p = conf_val(CONF_ACS_POST_BUFFER_LIMIT)) != NULL) {
	if (strnum(p, STRNUM_UI, &ds.len_limit) == -1) {
	  ds.len_limit = DEFAULT_ACS_POST_BUFFER_LIMIT;
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid ACS_POST_BUFFER_LIMIT, using default"));
	}
  }
  else
	ds.len_limit = DEFAULT_ACS_POST_BUFFER_LIMIT;

  while (ds_agets(&ds, stdin) != NULL)
	;
  if (ferror(stdin) || !feof(stdin)) {
	errmsg = ds_errmsg;
	error_occurred = 1;
	goto fail;
  }
  log_msg((LOG_INFO_LEVEL,
		   "Read %d bytes from mod_auth_dacs (limit is %d)",
		   ds_len(&ds), ds.len_limit));
  web_server_env = ds_buf(&ds);
  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG, "Read:\n%s", web_server_env));

#ifdef NOTDEF
  if ((kwv = kwv_make_new(web_server_env, &kwv_conf)) == NULL) {
	errmsg = "kwv_make error";
	error_occurred = 1;
	goto fail;
  }
#else
  {
	Dsvec *ws_env_vec;

	if ((ws_env_vec = dsvec_lines(NULL, web_server_env)) == NULL) {
	  errmsg = "dsvec_lines error";
	  error_occurred = 1;
	  goto fail;
	}

	kwv = NULL;
	for (i = 0; i < dsvec_len(ws_env_vec); i++) {
	  char *str;
	  Kwv_parse_conf conf;

	  str = (char *) dsvec_ptr_index(ws_env_vec, i);
	  if (*str == '\0')
		continue;
	  kwv_parse_init(&conf, KWV_PARSE_INTERNAL_QUOTES, 0, 0);
	  if ((kwv = kwv_add_qstr(kwv, str, NULL, &conf)) == NULL) {
		if (conf.errmsg != NULL)
		  errmsg = ds_xprintf("kwv_add_str error, str=\"%s\": %s",
							  str, conf.errmsg);
		else
		  errmsg = ds_xprintf("kwv_add_str error, str=\"%s\"", str);
		error_occurred = 1;
		goto fail;
	  }
	}
  }
#endif

  /*
   * It is the SERVICE_URI, r->uri as passed here by mod_auth_dacs, that
   * we will use when matching against access control rules.
   * It is just the path component, without any query string etc.
   * Note that SERVICE_URI is not always the same as the path component of
   * REQUEST_URI; after an internal redirect, for instance, the former will
   * be from the "new" URI while the latter is from the user's originally
   * requested URI.  So after a redirect, the original URI will have been
   * authorized by DACS (if its DACS-wrapped) but when checking the target URI
   * of the redirect, we need to look at SERVICE_URI, not REQUEST_URI.
   */
  if ((service_uri = kwv_lookup_value(kwv, "SERVICE_URI")) == NULL) {
	errmsg = "No SERVICE_URI found";
	error_occurred = 1;
	goto fail;
  }
  log_msg((LOG_INFO_LEVEL, "SERVICE_URI=%s", service_uri));
  dacs_service_uri = service_uri;
  if ((p = current_uri_no_query(NULL)) != NULL)
	log_msg((LOG_INFO_LEVEL, "Current URI (sans query)=%s", p));

  service_method = kwv_lookup_value(kwv, "SERVICE_METHOD");
  log_msg((LOG_INFO_LEVEL, "SERVICE_METHOD=%s",
		   (service_method == NULL) ? "???" : service_method));

  /*
   * We need to use the REQUEST_URI because it is properly URL encoded.
   * We will use it when matching against access control rules.
   * Strip off the query string, if present.
   */
  service_authorization = kwv_lookup_value(kwv, "SERVICE_AUTHORIZATION");

  /*
   * The mod_auth_dacs module passes arguments (regardless of the HTTP method)
   * as SERVICE_ARGS, base64 encoded.  If the POST/PUT/OPTIONS method is used
   * with an application/x-www-form-urlencoded content type, then mod_auth_dacs
   * appends them to SERVICE_ARGS.  The message body, if any, is always passed
   * as SERVICE_POSTDATA.
   *
   * If mod_auth_dacs indicates that the arguments have been truncated
   * (not when passed only in the query string, but when they are passed in
   * the message body), then we can't be sure (and consistent) about how to
   * proceed.
   * The safest course is to give up and fail, but it may be ok to just throw
   * them all away, flag the problem, and continue.
   * ACS_POST_EXCEPTION_MODE tells us what to do.
   * XXX there should probably also be a fine-grained, rule-dependent way to
   * do this - only rules that depend on arguments are affected by this issue.
   */
  if (conf_val_eq(CONF_ACS_POST_EXCEPTION_MODE, "abort"))
	post_exception_mode = ACS_POST_EXCEPTION_ABORT;
  else if (conf_val_eq(CONF_ACS_POST_EXCEPTION_MODE, "discard"))
	post_exception_mode = ACS_POST_EXCEPTION_DISCARD;
  else if (conf_val_eq(CONF_ACS_POST_EXCEPTION_MODE, "query"))
	post_exception_mode = ACS_POST_EXCEPTION_QUERY;
  else if (conf_val_eq(CONF_ACS_POST_EXCEPTION_MODE, "proceed"))
	post_exception_mode = ACS_POST_EXCEPTION_PROCEED;
  else if (conf_val_eq(CONF_ACS_POST_EXCEPTION_MODE, "default")
		   || conf_val(CONF_ACS_POST_EXCEPTION_MODE) == NULL)
	post_exception_mode = ACS_POST_EXCEPTION_DISCARD;
  else {
	errmsg = ds_xprintf("Unrecognized ACS_POST_EXCEPTION_MODE: \"%s\"",
						conf_val(CONF_ACS_POST_EXCEPTION_MODE));
	error_occurred = 1;
	goto fail;
  }

  args = kwv_init(INIT_CGI_KWV);
  truncated = kwv_lookup_value(kwv, "SERVICE_ARGS_TRUNCATED");
  if (truncated != NULL) {
	/* The arguments have been truncated... */
	switch (post_exception_mode) {
	case ACS_POST_EXCEPTION_DISCARD:
	case ACS_POST_EXCEPTION_DEFAULT:
	  log_msg((LOG_WARN_LEVEL, "Entity-body truncated... unavailable"));
	  log_msg((LOG_WARN_LEVEL,
			   "POST parameters will not be parsed into variables"));
	  goto post_data_processed;
	  /*NOTREACHED*/
	  break;

	case ACS_POST_EXCEPTION_ABORT:
	  log_msg((LOG_WARN_LEVEL, "Entity-body truncated... unavailable"));
	  errmsg = "ACS_POST_EXCEPTION_MODE specifies abort";
	  error_occurred = 1;
	  goto fail;
	  /*NOTREACHED*/
	  break;

	case ACS_POST_EXCEPTION_QUERY:
	  log_msg((LOG_WARN_LEVEL, "Entity-body truncated... unavailable"));
	  log_msg((LOG_WARN_LEVEL, "Only query arguments will be available"));
	  if ((p = kwv_lookup_value(kwv, "SERVICE_QUERY")) != NULL) {
		CGI_input *input;
		
		if ((input = cgiparse_set_input(HTTP_GET_METHOD, NULL, p, strlen(p), 1,
										NULL)) == NULL) {
		  errmsg = "Invalid input selection";
		  error_occurred = 1;
		  goto fail;
		}

		if (mime_parse_urlencoded(input, args) == -1) {
		  log_msg((LOG_ERROR_LEVEL, "decoded SERVICE_QUERY=\"%s\"", p));
		  errmsg = "Mime parse of SERVICE_QUERY failed";
		  error_occurred = 1;
		  goto fail;
		}
	  }
	  goto post_data_processed;
	  /*NOTREACHED*/
	  break;

	case ACS_POST_EXCEPTION_PROCEED:
	  log_msg((LOG_WARN_LEVEL, "Entity-body truncated... proceeding anyway"));
	  log_msg((LOG_WARN_LEVEL, "Arguments may be missing or corrupted"));
	  /* Proceed to process the SERVICE_ARGS... */
	  break;
	}
  }

  if ((p = kwv_lookup_value(kwv, "SERVICE_ARGS")) != NULL) {
	char *sa;
	CGI_input *input;

	if (mime_decode_base64(p, (unsigned char **) &sa) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "SERVICE_ARGS=\"%s\"", p));
	  errmsg = "Decode of SERVICE_ARGS failed!";
	  error_occurred = 1;
	  goto fail;
	}

	if ((input = cgiparse_set_input(HTTP_GET_METHOD, NULL, sa, strlen(sa), 1,
									NULL)) == NULL) {
	  errmsg = "Invalid input selection";
	  error_occurred = 1;
	  goto fail;
	}

	if (mime_parse_urlencoded(input, args) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "decoded SERVICE_ARGS=\"%s\"", sa));
	  errmsg = "Mime parse of SERVICE_ARGS failed";
	  error_occurred = 1;
	  goto fail;
	}
  }

  if (kwv_lookup_value(args, "DACS_ERROR_CODE")) {
	in_error_handler = 1;
	log_msg((LOG_INFO_LEVEL,
			 "This service request is for a DACS error handler"));
  }

  if ((content_type = kwv_lookup_value(kwv, "SERVICE_CONTENT_TYPE")) != NULL) {
	char *enc_postdata;
	unsigned char *postdata;
	Mime *mime;
	Mime_content_type *ct;

	/* If there's a Content-type, there should be content... */
	log_msg((LOG_DEBUG_LEVEL, "CONTENT_TYPE=\"%s\"", content_type));
	enc_postdata = kwv_lookup_value(kwv, "SERVICE_POSTDATA");

	/*
	 * Check if there was a problem with the message body; i.e., it was
	 * not passed to us.
	 */
	if (enc_postdata == NULL) {
	  unsigned long clen;

	  if (cgiparse_get_content_length(&clen) != 1 || clen == 0) {
		log_msg((LOG_WARN_LEVEL, "No Content-Length, entity-body unavailable"));
		goto post_data_processed;
	  }
	  log_msg((LOG_ERROR_LEVEL, "Entity-body expected but not received!"));
	  error_occurred = 1;
	  goto fail;
	}

	mime = ALLOC(Mime);
	mime_init(mime, NULL, NULL);
	ct = &mime->content_type;
	/* XXX We blindly assume that the charset is acceptable... */
	if (mime_parse_content_type(content_type, ct) == MIME_TYPE_ERROR) {
	  log_msg((LOG_ERROR_LEVEL, "MIME parse of Content-Type header failed"));
	  log_msg((LOG_WARN_LEVEL,
			   "POST parameters will not be parsed into variables"));
	  goto post_data_processed;
	} 
	mime_dump_content_type(ct);

	/*
	 * Matching of media type and subtype is ALWAYS case-insensitive
	 * (RFC 2045, 5.1)
	 */
	if (ct->content_type == MIME_TYPE_APPLICATION
		&& strcaseeq(ct->subtype, "x-www-form-urlencoded")) {
	  /* The body was already processed with the query args - do nothing. */
	  log_msg((LOG_DEBUG_LEVEL, "urlencoded arguments were already handled"));
	}
	else if (ct->content_type == MIME_TYPE_MULTIPART
			 && strcaseeq(ct->subtype, "form-data")) {
	  unsigned long nbytes;
	  CGI_input *input;

	  if ((nbytes = mime_decode_base64(enc_postdata, &postdata)) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Decode of multipart/form-data failed!"));
		error_occurred = 1;
		goto fail;
	  }
	  if ((input = cgiparse_set_input(HTTP_POST_METHOD, NULL, (char *) postdata,
									  nbytes, 1, NULL)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid input selection"));
		error_occurred = 1;
		goto fail;
	  }

	  if (mime_parse(mime, input, args) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Mime parse of multipart/form-data failed"));
		error_occurred = 1;
		goto fail;
	  }
	}
	else {
	  /* We don't do anything else here... the entity-body could be binary. */
	  log_msg((LOG_WARN_LEVEL, "Unrecognized Content-Type: \"%s\"",
			   content_type));
	  log_msg((LOG_WARN_LEVEL,
			   "POST parameters will not be parsed into variables"));
	}
  }

 post_data_processed:

  {
	int argnum;
	Kwv_iter *iter;
	Kwv_pair *v;

	log_msg((LOG_DEBUG_LEVEL, "Arg count: %d", args->npairs));
	/* WARNING! THIS MAY LOG PASSWORDS! */
	iter = kwv_iter_begin(args, NULL);
	argnum = 0;
	for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter)) {
	  log_msg(((Log_level) (LOG_DEBUG_LEVEL | LOG_SENSITIVE_FLAG),
			   "arg%d: %s=\"%s\"", argnum++, v->name, v->val));
	}
	kwv_iter_end(iter);
  }

  if ((dacs_acs_str = kwv_lookup_value(args, "DACS_ACS")) != NULL) {
	emit_format_default = EMIT_FORMAT_TEXT;
	if (parse_dacs_acs_args(dacs_acs_str, &dacs_acs_args) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Error parsing DACS_ACS argument: \"%s\"",
			   dacs_acs_str));
	  errmsg = "Error parsing DACS_ACS argument";
	  error_occurred = 1;
	  goto fail;
	}

	log_msg((LOG_TRACE_LEVEL, "Found DACS_ACS=%s", dacs_acs_str));
	if (dacs_acs_args.check_only)
	  log_msg((LOG_TRACE_LEVEL, "DACS_ACS enables check_only"));
	if (dacs_acs_args.check_fail)
	  log_msg((LOG_TRACE_LEVEL, "DACS_ACS enables check_fail"));
	if (dacs_acs_args.rname != NULL)
	  log_msg((LOG_TRACE_LEVEL, "DACS_ACS includes rname"));

	/* Make this available as a variable to rules. */
	kwv_add(kwv, "DACS_ACS", dacs_acs_str);

	/* Erase this argument so as not to affect access control processing. */
	kwv_delete(args, "DACS_ACS");
	if ((p = kwv_lookup_value(kwv, "SERVICE_QUERY")) != NULL)
	  clean_query("SERVICE_QUERY", p);
	if ((p = getenv("QUERY_STRING")) != NULL)
	  clean_query("QUERY_STRING", p);
  }
  else
	init_dacs_acs_args(&dacs_acs_args);

  if (get_cookies(kwv, &cookies, &ncookies) == -1) {
	errmsg = "Cookie parse error";
	error_occurred = 1;
	goto fail;
  }

  log_msg((LOG_DEBUG_LEVEL, "%d credential%s found",
		   ncookies, ncookies != 1 ? "s" : ""));

  if (ncookies == 0 && conf_val_eq(CONF_ACS_AUTHENTICATED_ONLY, "yes")) {
	errmsg = "User must be authenticated";
	denial_reason = ACS_DENIAL_REASON_NO_AUTH;
	goto fail;
  }

  /*
   * We need to use the REQUEST_URI because it is properly URL encoded.
   * We will use it when matching against access control rules.
   * Strip off the query string, if present.
   */
  request_uri = getenv("REQUEST_URI");
  if (uri_pqf(request_uri, &uri, NULL, NULL) == -1) {
	errmsg = "No REQUEST_URI found";
	error_occurred = 1;
	goto fail;
  }
  log_msg((LOG_INFO_LEVEL, "REQUEST_URI path=%s", uri));

  if ((remote_addr = kwv_lookup_value(kwv, "SERVICE_REMOTE_ADDR")) == NULL) {
	errmsg = "No REMOTE_ADDR found";
	error_occurred = 1;
	goto fail;
  }
  log_msg((LOG_INFO_LEVEL, "REMOTE_ADDR=%s", remote_addr));

  if ((remote_host = kwv_lookup_value(kwv, "SERVICE_REMOTE_HOST")) != NULL)
	log_msg((LOG_INFO_LEVEL, "REMOTE_HOST=%s", remote_host));

  if ((v = kwv_lookup(kwv, "SERVICE_HOSTNAME")) != NULL)
	log_msg((LOG_INFO_LEVEL, "HOSTNAME=%s", v->val));

  if (service_authorization != NULL) {
	log_msg((LOG_INFO_LEVEL | LOG_SENSITIVE_FLAG,
			 "SERVICE_AUTHORIZATION=\"%s\"", service_authorization));
  }

  ncred = get_valid_scredentials(cookies, remote_addr, 1, &credentials,
								 &selected, &scredentials);
  if (ncred != -1)
	log_msg((LOG_DEBUG_LEVEL, "%d valid selected credential%s found",
			 ncred, ncred != 1 ? "s" : ""));

  if (ncred == 0 && conf_val_eq(CONF_ACS_AUTHENTICATED_ONLY, "yes")) {
	errmsg = "There are no valid credentials";
	denial_reason = ACS_DENIAL_REASON_NO_AUTH;
	goto fail;
  }

  if (ncred > 0) {
	if (acs_is_inactive(selected, activity)) {
	  log_msg((LOG_INFO_LEVEL, "An inactivity timeout has occurred"));
	  errmsg = "Inactivity timeout";
	  denial_reason = ACS_DENIAL_REASON_INACTIVITY;
	  /* Do not emit an activity cookie. */
	  goto fail;
	}

	if (conf_val_eq(CONF_ACS_TRACK_ACTIVITY, "yes")) {
	  emit_activity_cookie = 1;
	  log_msg((LOG_TRACE_LEVEL, "Will emit activity cookie"));
	}
  }

  if ((p = conf_val(CONF_ACS_CREDENTIALS_LIMIT)) != NULL
	  && !strcaseeq(p, "none")) {
	if (strnum(p, STRNUM_UI, &alimit) == -1 || alimit == 0)
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid ACS_CREDENTIALS_LIMIT value, ignored"));
	else if (ncred > alimit) {
	  /* XXX Perhaps this should exclude admin identities... */
	  errmsg = "There are too many selected credentials";
	  denial_reason = ACS_DENIAL_REASON_CREDENTIALS_LIMIT;
	  goto fail;
	}
  }

  proxy_kwv = NULL;
  proxied_uri = NULL;
  if (proxy_static_flag || proxy_exec_flag) {
	Crypt_keys *ck;
	unsigned char *authbuf;
	unsigned char *cryptbuf;
	unsigned int crypt_len, auth_len;

	if (conf_val(CONF_PROXY_EXEC_MAPPER_RULES_FILE) == NULL
		|| conf_val(CONF_PROXY_EXEC_PROG_URI) == NULL) {
	  errmsg = "Not configured for proxying";
	  error_occurred = 1;
	  goto fail;
	}
	if ((proxy_auth = kwv_lookup_value(kwv, "SERVICE_PROXY_AUTH")) != NULL) {
	  log_msg((LOG_INFO_LEVEL, "SERVICE_PROXY_AUTH=%s", proxy_auth));
	  if (stra64b(proxy_auth, &cryptbuf, &crypt_len) == NULL) {
		errmsg = "Error unpacking proxy authorization";
		error_occurred = 1;
		goto fail;
	  }

	  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
	  if (crypto_decrypt_string(ck, cryptbuf, crypt_len, &authbuf, &auth_len)
		  == -1) {
		crypt_keys_free(ck);
		errmsg = "SERVICE_PROXY_AUTH decryption failed";
		error_occurred = 1;
		goto fail;
	  }
	  crypt_keys_free(ck);

	  if ((proxy_kwv = kwv_make_new((char *) authbuf, &kwv_conf)) == NULL) {
		errmsg = "proxy_kwv kwv_make error";
		error_occurred = 1;
		goto fail;
	  }
	  for (i = 0; i < kwv->npairs; i++)
		log_msg((LOG_INFO_LEVEL, "proxy_kwv[%d]: %s=%s",
				 i, proxy_kwv->pairs[i]->name, proxy_kwv->pairs[i]->val));

	  if (proxy_static_flag)
		kwv_add(proxy_kwv, "PROXY_STATIC_FLAG", "1");
	  if (proxy_exec_flag)
		kwv_add(proxy_kwv, "PROXY_EXEC_FLAG", "1");
	  if (proxy_pass_authorization_flag)
		kwv_add(proxy_kwv, "PROXY_PASS_AUTHORIZATION_FLAG", "1");
	  if (proxy_pass_authorization_flag || proxy_exec_flag) {
		kwv_add(proxy_kwv, "REMOTE_ADDR", remote_addr);
		kwv_add(proxy_kwv, "PROXY_EXEC_PROG_URI",
				conf_val(CONF_PROXY_EXEC_PROG_URI));
	  }
	  if (pass_env_flag)
		kwv_add(proxy_kwv, "PASS_ENV_FLAG", "1");

	  proxied_uri = kwv_lookup_value(proxy_kwv, "DACS_PROXIED_URI");
	}
  }

  if (dacs_acs_args.rname != NULL) {
	/* Make it accessible as ${Args::RNAME} */
	kwv_replace(args, "RNAME", dacs_acs_args.rname);
  }
  if ((rlink = acs_rlink_lookup(kwv, args, selected)) != NULL)
	log_msg((LOG_DEBUG_LEVEL, "An Rlink has been enabled"));

  result.errmsg = errmsg;
  st = dacs_acs(conf_val(CONF_JURISDICTION_NAME), dacs_service_uri, kwv, args,
				credentials, selected, scredentials, cookies,
				proxy_kwv, rlink, &result);

  if (st == -1) {
	/* Access is denied. */
	denial_reason = result.denial_reason;
	errmsg = result.errmsg;
	goto fail;
  }

  /* Access is granted. */

  if (emit_activity_cookie) {
	acs_emit_set_header("Set-Cookie: %s", make_activity_cookie());
	log_msg((LOG_TRACE_LEVEL, "Emitting activity cookie"));
	emitted_activity_cookie = 1;
  }

  if ((dacs_acs_args.status_line != -1
	   && (conf_val_eq(CONF_STATUS_LINE, "on")
		   || dacs_acs_args.check_only
		   || dacs_acs_args.check_fail))
	  || dacs_acs_args.status_line == 1) {
#ifdef NOTDEF
	/*
	 * XXX If no caching, emit response header directives?
	 * These might be enabled by a new rule attribute or perhaps a
	 * config directive.
	 */
	acs_emit_set_header("Cache-Control: no-cache");
	acs_emit_set_header("Pragma: no-cache");
	/*
	 * XXX The Expires value should probably be the same as the Date header.
	 * IE likes -1 and Mozilla apparently likes 0
	 * RFC 2616 14.21 specifically says it must be in RFC 1123 date format, but
	 * it also says that HTTP/1.1 clients and caches MUST treat other invalid
	 * date formats, especially including the value "0", as in the past
	 * (i.e., "already expired").  So values such as 0 and -1 are
	 * intentionally invalid dates and trigger the error interpretation.
	 * http://www.mozilla.org/projects/netlib/http/http-caching-faq.html
	 * http://support.microsoft.com/kb/234067
	 */
	acs_emit_set_header("Expires: Thu, 01 Jan 1970 00:00:01 GMT");
#endif
	acs_emit_set_header("DACS-Status-Line: DACS-%s 798 Access granted, %s",
						dacs_version_release,
						auth_identity_from_credentials_track(result.cr));
  }

  if (dacs_acs_str != NULL && !dacs_acs_args.visible) {
	/* XXX This won't be effective if it wasn't a query argument... */
	log_msg((LOG_TRACE_LEVEL, "Removing DACS_ACS query argument"));
	p = kwv_lookup_value(kwv, "SERVICE_QUERY");
	acs_emit_header("Unset-DACS_ACS", "%s", (p == NULL) ? "" : p);
  }

  /*
   * Fetch this value again because dacs_acs() may have deleted it after
   * using it itself.
   */
  service_authorization = kwv_lookup_value(kwv, "SERVICE_AUTHORIZATION");

  if (service_authorization != NULL) {
	char *buf;
	unsigned char *encrypted_authorization;
	unsigned int len;
	Crypt_keys *ck;

	/*
	 * This is used to forward the Authorization request header to
	 * dacs_authenticate to handle RFC 2716 auth
	 * XXX This should be replay-proof
	 */
	ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
	len = crypto_encrypt_string(ck, (unsigned char *) service_authorization,
								strlen(service_authorization) + 1,
								&encrypted_authorization);
	crypt_keys_free(ck);
	strba64(encrypted_authorization, len, &buf);
	kwv_add(result.auth_kwv, "DACS_AUTHORIZATION", buf);
	log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			 "Setting DACS_AUTHORIZATION: %s", buf));
  }

  if (proxy_static_flag || proxy_exec_flag) {
	/*
	 * This is the authorization that is passed to the proxied jurisdiction
	 * through an HTTP header by mod_auth_dacs (over SSL).
	 */
	printf("%s", encrypt_kwv(result.auth_kwv));
  }
  else {
	if (dacs_acs_args.check_only) {
	  log_msg((LOG_DEBUG_LEVEL, "Check only - prevent Apache from executing"));
	  acs_emit_header("check_only", "yes");
	  acs_emit_set_header("Cache-Control: no-cache");
	  acs_emit_set_header("Pragma: no-cache");
	  acs_emit_access_granted(stdout);

	  exit(0);
	}

	/*
	 * Tell mod_auth_dacs not to zap cookies.  This is particularly necessary
	 * in the case of internal web server redirects; if cookies are deleted,
	 * then the identity of the requestor is lost.
	 */
	if (result.pass_http_cookie != NULL
		&& strcaseeq(result.pass_http_cookie, "yes")) {
	  acs_emit_header("pass_credentials", "yes");
	  log_msg((LOG_TRACE_LEVEL, "Allowing Apache to pass credentials"));
	}

	if (kwv_count(result.auth_kwv, NULL) == 0 || !pass_env_flag)
	  printf("\n");
	else if (pass_env_flag)
	  kwv_text(stdout, result.auth_kwv);
	else {
#ifdef NOTDEF
	  /*
	   * This must be done so that only the legitimate service
	   * can see it.
	   * We might have mod_cgi:cgi_child() create a pipe, assign it to
	   * a specific file descriptor in the child (say, fd 3), write the
	   * encoded (not encrypted) environment stuff to the pipe, then close
	   * the pipe.  As long as the entire write can be buffered, this might
	   * work.
	   * When the child starts up, it should be able to read the pipe.
	   */
	  printf("DACS_ENV=%s\n", encrypt_kwv(result.auth_kwv));
#endif
	}
  }

  /*
   * Process any post-authorization success expressions.
   */
  acs_success(kwv, args, &result);

  exit(0);
}
