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

/*
 * Notice acknowledgement processing
 * A notice is usually some text, identified by a URL (N).
 * A notice is associated with one or more resources (R) such that an attempt
 * to access any of these resources requires the user to first explicitly
 * acknowledge the textual material.  A DACS event handler (H) is responsible
 * for presenting the notice to the user.
 *
 * The basic flow of control is roughly like so:
 *
 *                USER         |  Apache/DACS
 *
 *  a)    --- Request for R --->|---> (DACS Access Control Service)
 *  b)    <--- Redirect to H ---|<--
 *
 *  c)    --- Request for H --->|---> (a notice presentation handler)
 *  d)        <--- Return N ----|<---
 *
 *  e)     --- Submit Ack N --->|-->  (a notice acknowledgement handler)
 *  f)    <--- Redirect to R ---|<--
 *
 *  g)    --- Request for R --->|---> (DACS access control service)
 *  h)                <--- R ---|<--
 *
 * Of course there's a bit more to it than that:
 * o more than one notice can be associated with a resource, in which
 *   case they are effectively concatenated for presentation purposes and
 *   collectively acknowledged (so N may be N1, N2, ..., Nn and Ack N
 *   acknowledges all of them).
 * o having already acknowledged N, the user should not need to do so again;
 *   when enabled by an administrator, relevant state must be maintained.
 *   An acknowledgement for N may be limited to the context of R, however,
 *   meaning that an administrator may also require an acknoledgement for N
 *   in the context of a different R.
 * o client-side state is maintained using cookies, but server-side state
 *   can be maintained, or a combination of methods.
 *
 * This program provides the functionality of a 1) generic notice presentation
 * handler and 2) a generic notice acknowledgement handler.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: notices.c 2610 2012-10-19 18:02:36Z brachman $";
#endif

#include "dacs.h"

#include <sys/time.h>

static char *log_module_name = "notices";

#ifndef NOTICES_WORKFLOW_LIFETIME_SECS
#define NOTICES_WORKFLOW_LIFETIME_SECS	120
#endif

#ifndef NAT_NAME_PREFIX
#define NAT_NAME_PREFIX	"NAT-DACS"
#endif

/* The value of the RESPONSE variable if the user accepts the notice. */
#define POSITIVE_ACK		"accepted"

/* The value of the RESPONSE variable if the user doesn't accept the notice. */
#define NEGATIVE_ACK		"declined"

typedef struct Nat {
  char *cookie_name;
  char *resource_uris;
  Dsvec *resources;
  char *notice_uris;
  Dsvec *notices;
#ifdef NOTDEF
  char *federation;
  char *jurisdiction;
#endif
} Nat;

static char *nat_name_prefix = NAT_NAME_PREFIX;

#ifdef NOTDEF
static int
make_set_void_nat_cookie_header(char *name, char **buf)
{

  return(make_set_void_cookie_header(name, 0, buf));
}

static int
is_nat_cookie_name(char *cookie_name)
{
  char *cn;

  cn = make_nat_cookie_name_prefix();
  if (strprefix(cookie_name, cn) != NULL)
	return(1);

  return(0);
}
#endif

static Nat *
nat_init(char *cookie_name)
{
  Nat *nat;

  nat = ALLOC(Nat);
  nat->cookie_name = strdup(cookie_name);
  nat->resource_uris = NULL;
  nat->resources = NULL;
  nat->notice_uris = NULL;
  nat->notices = NULL;
#ifdef NOTDEF
  nat->federation = NULL;
  nat->jurisdiction = NULL;
#endif

  return(nat);
}

static char *
nat_show(Nat *nat)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "%s", nat->cookie_name);
  if (nat->resource_uris != NULL)
	ds_asprintf(&ds, " ResourceURIs=\"%s\"", nat->resource_uris);
  if (nat->notice_uris != NULL)
	ds_asprintf(&ds, " NoticeURIs=\"%s\"", nat->notice_uris);

  return(ds_buf(&ds));
}

static Nat *
parse_nat_cookie(char *cookie)
{
  char *cname, *errmsg, *p, *prefix;
  unsigned char *outp;
  Kwv *kwv;
  Kwv_iter *iter;
  Kwv_pair *v;
  Nat *nat;
  static Kwv_conf conf = {
	"=", "\"'", NULL, KWV_CONF_DEFAULT, " \t", 10, NULL, NULL
  };

  errmsg = NULL;
  cname = strdup(cookie);

  if ((p = strchr(cname, (int) '=')) == NULL) {
	errmsg = "no '=' found";
	goto fail;
  }
  *p++ = '\0';

  prefix = ds_xprintf("%s:", nat_name_prefix);
  if (strprefix(cname, prefix) == NULL) {
	errmsg = ds_xprintf("cookie name doesn't match prefix: \"%s\"", prefix);
	goto fail;
  }

  if (mime_decode_base64(p, &outp) == -1) {
	errmsg = "base64 decoding failed";
	goto fail;
  }

  nat = nat_init(cname);

  if ((kwv = kwv_make_sep(NULL, (char *) outp, &conf)) == NULL) {
	errmsg = "kwv_make_sep failed";
	goto fail;
  }

  iter = kwv_iter_begin(kwv, NULL);
  for (v = kwv_iter_first(iter); v != NULL ; v = kwv_iter_next(iter)) {
	if (strcaseeq(v->name, "ResourceURIs")) {
	  nat->resource_uris = strdup(v->val);
	  if ((nat->resources = strsplit_re(v->val, " [ ]*", 0, 0, &errmsg))
		  == NULL)
		goto fail;
	}
	else if (strcaseeq(v->name, "NoticeURIs")) {
	  nat->notice_uris = strdup(v->val);
	  if ((nat->notices = strsplit_re(v->val, " [ ]*", 0, 0, &errmsg)) == NULL)
		goto fail;
	}
  }
  kwv_iter_end(iter);

  return(nat);

 fail:
  if (errmsg != NULL)
	log_msg((LOG_TRACE_LEVEL, "parse_nat_cookie: %s", errmsg));
  return(NULL);
}

/*
 * Examine HTTP cookies for Notice Acknowledgement Tokens (NATs).
 * Parse them and return the list (or NULL).
 */
Dsvec *
get_nats(void)
{
  char *http_cookie, *p, *r, *s;
  Dsvec *nats;
  Nat *nat;

  nats = NULL;
  if ((http_cookie = getenv("HTTP_COOKIE")) == NULL || *http_cookie == '\0') {
	log_msg((LOG_TRACE_LEVEL, "HTTP_COOKIE is NULL"));
	if ((http_cookie = non_auth_cookie_header) == NULL) {
	  log_msg((LOG_TRACE_LEVEL, "No NAT cookie found"));
	  return(NULL);
	}
	log_msg((LOG_TRACE_LEVEL, "Using non_auth_cookie_header..."));
  }
  else
	log_msg((LOG_TRACE_LEVEL, "Using HTTP_COOKIE..."));

  p = s = strdup(http_cookie);
  while (p != NULL) {
	/*
	 * The Netscape spec says that a semicolon is used to separate
	 * multiple name/value pairs in a Cookie header.
	 * RFC 2109 uses a comma for this purpose; such cookies ought
	 * to be identified by an initial field of '$Version="1";' in the
	 * Cookie header, but that doesn't seem be the case for all clients.
	 * DACS doesn't use a comma in Cookie headers, so treating a comma
	 * as a semicolon ought to be ok.
	 */
	if ((r = strchr(p, (int) ';')) != NULL) {
	  *r++ = '\0';
	  while (*r == ' ')
		r++;
	}
	else if ((r = strchr(p, (int) ',')) != NULL) {
	  *r++ = '\0';
	  while (*r == ' ')
		r++;
	}

	s = p;
	p = r;

	/* Break the cookie up into its fields. */
	if ((nat = parse_nat_cookie(s)) != NULL) {
	  if (nats == NULL)
		nats = dsvec_init(NULL, sizeof(Nat *));
	  dsvec_add_ptr(nats, nat);
	  log_msg((LOG_TRACE_LEVEL, "Accepting NAT: %s", nat_show(nat)));
	}
	else
	  log_msg((LOG_TRACE_LEVEL, "Ignoring non-NAT cookie: \"%s\"", s));
  }

  return(nats);
}

/*
 * Match URI against PATTERN_URI
 * From RFC 3986,
 *  URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
 *  hier-part   = "//" authority path-abempty
 *                / path-absolute
 *                / path-rootless
 *                / path-empty
 * The scheme and authority are case insensitive, the path is not.
 * XXX We ignore any query or fragment components.
 */
int
uri_path_match(Uri *uri, char *pattern, int *exact)
{
  int n;
  Dsvec *dsv_uri;
  Uri *pattern_uri;

  *exact = 0;

  if ((pattern_uri = uri_parse(pattern)) == NULL)
	return(-1);
  if ((dsv_uri = uri_path_parse(uri->path)) == NULL)
	return(-1);

  if (!strcaseeq(uri->scheme, pattern_uri->scheme))
	return(0);
  if (!strcaseeq(uri->host, pattern_uri->host))
	return(0);
  if (uri->port != pattern_uri->port)
	return(0);

  if ((n = acs_match_url_segs(pattern_uri->path, dsv_uri, exact)) == -1)
	return(-1);

  return(n);
}

/*
 * Examine all NATs to find one that best matches TARGET (a URI).
 * If TARGET is NULL, return all NATs.
 * Return it or NULL if nothing applicable is found.
 */
Nat *
get_matching_nat(char *target)
{
  int best, exact, i, j, n;
  char *r;
  Dsvec *nats, *resources;
  Nat *best_nat, *nat;
  Uri *uri;

  if ((nats = get_nats()) == NULL || target == NULL) {
	log_msg((LOG_TRACE_LEVEL, "No matching NAT"));
	return(NULL);
  }

  best = -1;
  best_nat = NULL;

  if ((uri = uri_parse(target)) == NULL) {
	log_msg((LOG_TRACE_LEVEL, "No matching NAT"));
	return(NULL);
  }

  for (i = 0; i < dsvec_len(nats); i++) {
	nat = (Nat *) dsvec_ptr_index(nats, i);

	resources = nat->resources;
	for (j = 0; j < dsvec_len(resources); j++) {
	  if (j == 0)
		log_msg((LOG_TRACE_LEVEL, "URI target: \"%s\"", uri->uri));
	  r = (char *) dsvec_ptr_index(resources, j);
	  /* XXX Canonicalize r */
	  n = uri_path_match(uri, r, &exact);
	  log_msg((LOG_TRACE_LEVEL, "Match against URI \"%s\" yields %d", r, n));
	  if (n > 0) {
		if (exact) {
		  log_msg((LOG_TRACE_LEVEL, "Found exact NAT match"));
		  return(nat);
		}

		if (n > best) {
		  best = n;
		  best_nat = nat;
		}
	  }
	}
  }

  if (best_nat == NULL)
	log_msg((LOG_TRACE_LEVEL, "No NAT match"));
  else
	log_msg((LOG_TRACE_LEVEL, "Found NAT match"));

  return(best_nat);
}

/*
 * Return a vector of notice URIs that must be acknowledged
 * for URI, or NULL if no acknowledgements are needed.
 */
Dsvec *
get_needed_notices(Dsvec *notices, char *mode)
{
  int i;
  char *uri;
  Dsvec *dsv;
  Nat *nat;

  if (mode == NULL || strcaseeq(mode, "EXACT_MATCH")) {
	/*
	 * Find a single NAT that acknowledges each element of NOTICES.
	 * If one doesn't exist, we need to present all notices.
	 */
	uri = current_uri_no_query(NULL);
	if (uri == NULL || (nat = get_matching_nat(uri)) == NULL) {
	  dsv = dsvec_init(NULL, sizeof(char *));
	  for (i = 0; i < dsvec_len(notices); i++)
		dsvec_add_ptr(dsv, dsvec_ptr_index(notices, i));
	  return(dsv);
	}
	return(NULL);
  }
#ifdef NOTDEF
  This is of dubious value...
  else if (strcaseeq(mode, "ANY_MATCH")) {
	/*
	 * Find a single NAT that acknowledges each element of NOTICES, or
	 * failing that, one that acknowledges the greatest number.
	 * We only need to present notices that are not acknowledged by the
	 * best NAT.
	 */
	return(notices);
  }
#endif
  else if (strcaseeq(mode, "ALL_MATCH")) {
	/*
	 * Look for each element of NOTICES amongst the union of all NATs;
	 * we don't care which resources are being acknowledged.
	 * We only need to present notices that are not acknowledged anywhere.
	 */
	int exact, found, j, k;
	char *notice, *req_notice;
	Dsvec *nats;
	Uri *req_uri;

	if ((nats = get_nats()) == NULL)
	  return(notices);

	dsv = dsvec_init(NULL, sizeof(char *));
	for (i = 0; i < dsvec_len(notices); i++) {
	  req_notice = (char *) dsvec_ptr_index(notices, i);

	  if ((req_uri = uri_parse(req_notice)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid notice URI: %s", req_notice));
		/* XXX Should return an error indication... */
		dsvec_add_ptr(dsv, req_notice);
		continue;
	  }

	  found = 0;
	  for (j = 0; !found && j < dsvec_len(nats); j++) {
		nat = (Nat *) dsvec_ptr_index(nats, j);

		for (k = 0; k < dsvec_len(nat->notices); k++) {
		  notice = (char *) dsvec_ptr_index(nat->notices, k);
		  if (uri_path_match(req_uri, notice, &exact) > 0 && exact) {
			found = 1;
			break;
		  }
		}
	  }
	  if (!found)
		dsvec_add_ptr(dsv, req_notice);
	}

	if (dsvec_len(dsv) == 0)
	  return(NULL);
	return(dsv);
  }

  return(notices);
}

#ifdef PROG

static int secure = 1;
static char *default_notices_accept_label = "I Accept<br>";
static char *default_notices_decline_label = "I Decline<br>";
static char *default_notices_submit_label = "Send";
static char *default_prompt_text = "Your response to the preceding notices is requested:<p>";

static char *accept_label;
static char *decline_label;

static char *notices_accept_handler = NULL;
static char *notices_decline_handler = NULL;

/*
 * Emit an HTML FORM to prompt for a response to the notice(s).
 * The FORM includes the URIs of the notices that are being acknowledged,
 * the time of the original access control decision, and a keyed hash.
 * The purpose of the keyed hash is to make it difficult for a trouble maker
 * to simply call this service without having actually have seen the notice(s)
 * or even having been prompted.  The time protects against reuse
 * of a keyed hash by a trouble maker.
 * The value of the RESPONSE field is the user's response to the prompt:
 * POSITIVE_ACK is a positive acknowledgement and NEGATIVE_ACK is a negative
 * acknowlegement.
 */
static void
emit_form(FILE *fp, char *notice_uris, char *ack_handler, char *error_url,
		  char *request_method, char *resource_uris, char *time_str,
		  char *hmac_str)
{
  char *p, *submit_label;

  p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf", "notices_prompt_text");
  if (p != NULL)
	fprintf(fp, "%s", p);
  else
	fprintf(fp, "%s", default_prompt_text);

  fprintf(fp, "<form method=\"post\" action=\"%s\">\n", ack_handler);

  fprintf(fp, "<input type=\"radio\" name=\"RESPONSE\" value=\"%s\">",
		  POSITIVE_ACK);
  if (accept_label == NULL) {
	if ((p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
							  "notices_accept_label")) != NULL)
	  accept_label = p;
	else
	  accept_label = default_notices_accept_label;
  }
  fprintf(fp, "%s\n", accept_label);

  fprintf(fp, "<input type=\"radio\" name=\"RESPONSE\" value=\"%s\" checked>",
		  NEGATIVE_ACK);
  if (decline_label == NULL) {
	if ((p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
							  "notices_decline_label")) != NULL)
	  decline_label = p;
	else
	  decline_label = default_notices_decline_label;
  }
  fprintf(fp, "%s\n", decline_label);

  fprintf(fp, "<input type=\"hidden\" name=\"NOTICE_URIS\" value=\"%s\">\n",
		  notice_uris);

  if (error_url != NULL)
	fprintf(fp,
			"<input type=\"hidden\" name=\"DACS_ERROR_URL\" value=\"%s\">\n",
			error_url);

  if (request_method != NULL)
	fprintf(fp,
			"<input type=\"hidden\" name=\"DACS_REQUEST_METHOD\" value=\"%s\">\n",
			request_method);

  if (resource_uris != NULL)
	fprintf(fp,
			"<input type=\"hidden\" name=\"RESOURCE_URIS\" value=\"%s\">\n",
			resource_uris);

  if (secure) {
	fprintf(fp, "<input type=\"hidden\" name=\"TIME\" value=\"%s\">\n",
			time_str);
	fprintf(fp, "<input type=\"hidden\" name=\"HMAC\" value=\"%s\">\n",
			hmac_str);

	log_msg((LOG_TRACE_LEVEL, "  ack_handler=\"%s\"", ack_handler));
	log_msg((LOG_TRACE_LEVEL, "  NOTICE_URIs=\"%s\"", notice_uris));
	if (error_url != NULL)
	  log_msg((LOG_TRACE_LEVEL, "  ERROR_URL=\"%s\"", error_url));
	if (request_method != NULL)
	  log_msg((LOG_TRACE_LEVEL, "  REQUEST_METHOD=\"%s\"", request_method));
	log_msg((LOG_TRACE_LEVEL, "  RESOURCE_URIs=\"%s\"", resource_uris));
	log_msg((LOG_TRACE_LEVEL, "  TIME=\"%s\"", time_str));
  }

  if ((p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
							"notices_submit_label")) != NULL)
	submit_label = p;
  else
	submit_label = default_notices_submit_label;
  fprintf(fp, "<p><input type=\"submit\" value=\"%s\">\n", submit_label);

  fprintf(fp, "</form>\n");
}

/*
 * Retrieve URL using GET and append it to DS.
 */
static int
get_notice(Ds *ds, char *url)
{
  int n, reply_len, st, status_code;
  char *reply;
  Dsvec *v;

  reply_len = -1;
  n = 0;
  v = NULL;
  st = http_invoke(url, HTTP_GET_METHOD, HTTP_SSL_URL_SCHEME,
				   n, (Http_params *) dsvec_base(v), NULL, NULL,
				   &reply, &reply_len, &status_code, NULL);

  if (st == -1 || status_code != 200) {
	if (reply != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Could not obtain notice \"%s\": %s",
			   url, reply));
	else
	  log_msg((LOG_ERROR_LEVEL, "Could not obtain notice \"%s\"", url));
	return(-1);
  }

  ds_asprintf(ds, "%s", reply);

  return(0);
}

static int
load_notice_item(char *name, char **buf)
{
  Vfs_handle *handle;

  *buf = NULL;
  if ((handle = vfs_open_item_type(ITEM_TYPE_NOTICES)) == NULL)
	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);
}

static char *
make_nat_cookie_name_prefix(void)
{
  char *name;

  name = ds_xprintf("%s:%s::%s::",
					nat_name_prefix,
					conf_val(CONF_FEDERATION_NAME),
					conf_val(CONF_JURISDICTION_NAME));
  return(name);
}

/*
 *
 */
static char *
make_nat_cookie_name(void)
{
  char *name;

  name = crypto_make_random_string(make_nat_cookie_name_prefix(), 8);

  return(name);
}

static int
make_nat_cookie(char *resource_uris, char *notices, char **buf)
{
  char *outp_str;
  Ds ds, val;

  ds_init(&val);
  ds_asprintf(&val, "ResourceURIs=\"%s\"", resource_uris);
  ds_asprintf(&val, " NoticeURIs=\"%s\"", notices);
  mime_encode_base64(ds_ucbuf(&val), ds_len(&val), &outp_str);

  ds_init(&ds);
  ds_asprintf(&ds, "%s=%s", make_nat_cookie_name(), outp_str);
  *buf = ds_buf(&ds);

  return(0);
}

static int
make_set_nat_cookie_header(char *resource_uris, char *notices,
						   char **cookie_buf)
{
  char *buf;

  if (make_nat_cookie(resource_uris, notices, &buf) == -1)
	return(-1);
  if (*buf == '\0')
	return(0);

  make_set_cookie_header(buf, NULL, 0, 0, cookie_buf);

  return(1);
}

/*
 * If a RESPONSE parameter is present, act as a notice acknowledgement
 * handler, otherwise if a NOTICES parameter is present, act as a notice
 * presentation handler.
 */
int
main(int argc, char **argv)
{
  int emit_xml, i;
  char *ack_handler, *error_url, *errmsg, *p, *response;
  char *enc, *hmac, *hmac_str, *notice_uris;
  char *redirect_url, *request_method, *resource_uris, *time_str;
  time_t now, then, workflow_lifetime_secs;
  Common_status status;
  Ds notice_page;
  Dsvec *notices;
  Kwv *kwv;

  errmsg = "Internal error";

  dacs_init_allow_dups_default = 1;
  emit_format_default = EMIT_FORMAT_HTML;

  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	log_msg((LOG_ERROR_LEVEL, "Failed: %s", errmsg));

	if (test_emit_xml_format()) {
	  emit_xml_header(stdout, "dacs_notices");
	  printf("<%s>\n", make_xml_root_element("dacs_notices"));
	  init_common_status(&status, NULL, NULL, errmsg);
	  fprintf(stdout, "%s", make_xml_common_status(&status));
	  printf("</dacs_notices>\n");
	  emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_HTML)) {
	  emit_html_header(stdout, NULL);
	  fprintf(stdout, "%s\n", errmsg);
	  emit_html_trailer(stdout);
	}
	else {
	  emit_plain_header(stdout);
	  fprintf(stderr, "%s\n", errmsg);
	  emit_plain_trailer(stdout);
	}

	exit(1);
  }

  /* Should always default to be secure. */
  if (conf_val_eq(CONF_NOTICES_SECURE_HANDLER, "no"))
	secure = 0;
  else
	secure = 1;

  /* Note: no sanity check */
  if ((p = conf_val(CONF_NOTICES_WORKFLOW_LIFETIME_SECS)) != NULL) {
	if (strnum(p, STRNUM_TIME_T, &workflow_lifetime_secs) == -1) {
	  errmsg = "Invalid NOTICES_WORKFLOW_LIFETIME_SECS";
	  goto fail;
	}
  }
  else
	workflow_lifetime_secs = NOTICES_WORKFLOW_LIFETIME_SECS;

  if ((p = conf_val(CONF_NOTICES_NAT_NAME_PREFIX)) != NULL && *p != '\0')
	nat_name_prefix = p;

  /*
   * We may need to set a cookie, redirect, or emit a user-specified
   * HTML header, so don't emit anything just yet.
   */
  if ((emit_xml = test_emit_xml_format()) != 0)
	;
  else if (test_emit_format(EMIT_FORMAT_HTML))
	;
  else {
	errmsg = "Unsupported FORMAT argument value";
	goto fail;
  }

  notices_accept_handler = conf_val(CONF_NOTICES_ACCEPT_HANDLER);
  notices_decline_handler = conf_val(CONF_NOTICES_DECLINE_HANDLER);

  if ((ack_handler = conf_val(CONF_NOTICES_ACK_HANDLER)) == NULL
	  || *ack_handler == '\0')
	ack_handler = current_uri_no_query(NULL);
  error_url = kwv_lookup_value(kwv, "DACS_ERROR_URL");
  request_method = kwv_lookup_value(kwv, "DACS_REQUEST_METHOD");
  resource_uris = kwv_lookup_value(kwv, "RESOURCE_URIS");

  hmac_str = kwv_lookup_value(kwv, "HMAC");
  time_str = kwv_lookup_value(kwv, "TIME");
  if (secure) {
	if (time_str == NULL || hmac_str == NULL) {
	  errmsg = "Security parameters are missing";
	  goto fail;
	}
	if (strnum(time_str, STRNUM_TIME_T, &then) == -1) {
	  errmsg = "Invalid time";
	  goto fail;
	}
  }
  else
	then = 0;

  if ((response = kwv_lookup_value(kwv, "RESPONSE")) != NULL) {
	char *outp_str;
	unsigned char *outp;
	Crypt_keys *ck;
	Hmac_handle *h;

	/*
	 * We are invoked as a notice acknowledgement handler.
	 * If a positive acknowledgement is received (through RESPONSE), then
	 * we may cache that fact; the user is then redirected to his original
	 * request (if possible), or a specified generic URL.
	 * If a negative acknowledgement is received, then the user is redirected
	 * to a specified generic URL.
	 */
	log_msg((LOG_DEBUG_LEVEL, "Acting as a notice acknowledgement handler"));

	notice_uris = kwv_lookup_value(kwv, "NOTICE_URIS");
	log_msg((LOG_TRACE_LEVEL, "ack_handler URL=%s", ack_handler));
	if (notice_uris == NULL) {
	  errmsg = "No NOTICE_URIS argument?";
	  goto fail;
	}

	if (secure) {
	  Ds ds;

	  /*
	   * Confirm that this request came from the presentation handler
	   * and has not expired.
	   */
	  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
	  h = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, ck->hmac_key,
						   CRYPTO_HMAC_KEY_LENGTH);
	  crypt_keys_free(ck);

	  ds_init(&ds);
	  ds_asprintf(&ds, "h=\"%s\" n=\"%s\" e=\"%s\" m=\"%s\" r=\"%s\" t=\"%s\"",
				  ack_handler,
				  notice_uris,
				  error_url != NULL ? error_url : "",
				  request_method != NULL ? request_method : "",
				  resource_uris != NULL ? resource_uris : "",
				  time_str);

	  crypto_hmac_hash(h, ds_ucbuf(&ds), ds_len(&ds));
	  outp = crypto_hmac_close(h, NULL, NULL);
	  strba64(outp, CRYPTO_HMAC_BYTE_LENGTH, &outp_str);
	  log_msg((LOG_TRACE_LEVEL, "Hashing: %s (%d bytes) ==> \"%s\"",
			   ds_buf(&ds), ds_len(&ds), outp_str));

	  if (!streq(outp_str, hmac_str)) {
		log_msg((LOG_TRACE_LEVEL, "HMAC was \"%s\"", hmac_str));
		errmsg = "HMAC comparison failed";
		goto fail;
	  }

	  now = time(NULL);
	  if ((now >= then && (now - then) > workflow_lifetime_secs)
		  || (then >= now && (then - now) > workflow_lifetime_secs)) {
		errmsg = "Invalid request: expired time";
		goto fail;
	  }
	}

	if (secure)
	  log_msg((LOG_DEBUG_LEVEL, "Response has been validated"));
	else
	  log_msg((LOG_DEBUG_LEVEL, "Response validation disabled"));
	log_msg((LOG_DEBUG_LEVEL, "Response is: \"%s\"", response));

	if (strcaseeq(response, POSITIVE_ACK)) {
	  char *cookie_buf;

	  /* Issue an acknowledgement token */
		
	  /*
	   * If the original request used a method that had no message body,
	   * we can automatically reissue it; if it did have a message body,
	   * such as in the case of the POST method, the client must repeat
	   * the original request "manually".
	   * XXX This should probably not be based on the method.
	   */
	  if (error_url != NULL
		  && (request_method == NULL
			  || strcaseeq(request_method, "GET")
			  || strcaseeq(request_method, "HEAD")
			  || strcaseeq(request_method, "DELETE")
			  || strcaseeq(request_method, "OPTIONS")))
		  redirect_url = error_url;
	  else if (notices_accept_handler != NULL)
		redirect_url = notices_accept_handler;
	  else
		redirect_url = NULL;

	  if (redirect_url != NULL)
		log_msg((LOG_TRACE_LEVEL, "Redirect to \"%s\"", redirect_url));

	  make_set_nat_cookie_header(resource_uris, notice_uris, &cookie_buf);
	  printf("%s", cookie_buf);

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		/* Redirect to original request or an "accepted" page */
		if (redirect_url != NULL)
		  emit_http_header_redirect(stdout, redirect_url);
		else {
		  emit_html_header(stdout, NULL);
		  printf("Please repeat your service request");
		}
		emit_html_trailer(stdout);
	  }
	  else {
		emit_xml_header(stdout, "dacs_notices");
		printf("<%s>\n", make_xml_root_element("dacs_notices"));
		printf("<ack_reply response=\"accepted\"");
		if (redirect_url != NULL)
		  printf(" redirect=\"%s\"", redirect_url);
		printf("/>\n");
		printf("</dacs_notices>\n");
		emit_xml_trailer(stdout);
	  }
	}
	else if (strcaseeq(response, NEGATIVE_ACK)) {
	  /*
	   * Do not issue an acknowledgement token.
	   * Redirect to an "access denied" page.
	   */
	  if (notices_decline_handler != NULL)
		redirect_url = notices_decline_handler;
	  else
		redirect_url = NULL;

	  if (redirect_url != NULL)
		log_msg((LOG_TRACE_LEVEL, "Redirect to \"%s\"", redirect_url));

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		if (redirect_url != NULL)
		  emit_http_header_redirect(stdout, redirect_url);
		else {
		  emit_html_header(stdout, NULL);
		  printf("An acknowlegement was not received, access is denied");
		}
		emit_html_trailer(stdout);
	  }
	  else {
		emit_xml_header(stdout, "dacs_notices");
		printf("<%s>\n", make_xml_root_element("dacs_notices"));
		printf("<ack_reply response=\"declined\"");
		if (redirect_url != NULL)
		  printf(" redirect=\"%s\"", redirect_url);
		printf("/>\n");
		printf("</dacs_notices>\n");
		emit_xml_trailer(stdout);
	  }
	}
	else {
	  errmsg = ds_xprintf("Invalid RESPONSE: \"%s\"", response);
	  goto fail;
	}

  }
  else if ((notice_uris = kwv_lookup_value(kwv, "NOTICE_URIS")) != NULL) {
	/*
	 * We are invoked as a notice presentation handler.
	 * Emit information that a client will require to obtain acknowledgement
	 * for NOTICE_URIS (a comma-separated list of URIs).
	 */

	log_msg((LOG_DEBUG_LEVEL, "Acting as a notice presentation handler"));

	if (secure) {
	  unsigned char *outp;
	  Crypt_keys *ck;
	  Hmac_handle *h;

	  /*
	   * Confirm that this request comes from DACS ACS and has not
	   * expired.
	   */
	  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
	  h = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, ck->hmac_key,
						   CRYPTO_HMAC_KEY_LENGTH);
	  crypt_keys_free(ck);

	  crypto_hmac_hash(h, (unsigned char *) notice_uris, strlen(notice_uris));
	  crypto_hmac_hash(h, (unsigned char *) resource_uris,
					   strlen(resource_uris));
	  crypto_hmac_hash(h, (unsigned char *) time_str, strlen(time_str));
	  outp = crypto_hmac_close(h, NULL, NULL);
	  strba64(outp, CRYPTO_HMAC_BYTE_LENGTH, &hmac);
	  if (!streq(hmac, hmac_str)) {
		errmsg = "HMAC is invalid";
		goto fail;
	  }

	  now = time(NULL);
	  if ((now >= then && (now - then) > workflow_lifetime_secs)
		  || (then >= now && (then - now) > workflow_lifetime_secs)) {
		errmsg = "Invalid request: expired time";
		goto fail;
	  }
	}

	if (*notice_uris == '\0'
		|| (notices = strsplit_re(notice_uris, " [ ]*", 0, 0, &errmsg))
		== NULL) {
	  errmsg = "Invalid NOTICE_URIS argument";
	  goto fail;
	}

	accept_label = decline_label = NULL;
	if ((p = kwv_lookup_value(kwv, "ACCEPT_LABEL")) != NULL)
	  accept_label = p;
	if ((p = kwv_lookup_value(kwv, "DECLINE_LABEL")) != NULL)
	  decline_label = p;

	ds_init(&notice_page);
	if (load_notice_item("instructions", &p) != -1)
	  ds_set(&notice_page, p);
	else {
	  ds_asprintf(&notice_page,
				  "Please read and acknowledge the following notice%s.\n",
				  (dsvec_len(notices) > 1) ? "s" : "");
	  ds_asprintf(&notice_page,
				  "You cannot access the requested document or service ");
	  ds_asprintf(&notice_page, "until an acknowledgement is received.<p>\n");
	  ds_asprintf(&notice_page, "<hr width=\"66%%\">\n");
	}

	for (i = 0; i < dsvec_len(notices); i++) {
	  char *notice_uri;

	  notice_uri = (char *) dsvec_ptr_index(notices, i);
	  if (get_notice(&notice_page, notice_uri) == -1) {
		errmsg = ds_xprintf("Could not obtain notice: %s", notice_uri);
		goto fail;
	  }
	  ds_asprintf(&notice_page, "<hr width=\"66%%\">\n");
	}
	ds_appendc(&notice_page, (int) '\0');

	if (secure) {
	  unsigned char *outp;
	  Crypt_keys *ck;
	  Ds ds;
	  Hmac_handle *h;

	  /*
	   * This could be a jurisdictional key, but it might be nice to allow
	   * any jurisdiction in the federation to validate the submitted form.
	   */
	  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
	  h = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, ck->hmac_key,
						   CRYPTO_HMAC_KEY_LENGTH);
	  crypt_keys_free(ck);

	  ds_init(&ds);
	  ds_asprintf(&ds, "h=\"%s\" n=\"%s\" e=\"%s\" m=\"%s\" r=\"%s\" t=\"%s\"",
				  ack_handler,
				  notice_uris,
				  error_url != NULL ? error_url : "",
				  request_method != NULL ? request_method : "",
				  resource_uris != NULL ? resource_uris : "",
				  time_str);

	  crypto_hmac_hash(h, ds_ucbuf(&ds), ds_len(&ds));
	  outp = crypto_hmac_close(h, NULL, NULL);
	  strba64(outp, CRYPTO_HMAC_BYTE_LENGTH, &hmac_str);
	  log_msg((LOG_TRACE_LEVEL, "Hashing: %s (%d bytes) ==> \"%s\"",
			   ds_buf(&ds), ds_len(&ds), hmac_str));
	}

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

		hc = emit_html_header_conf(NULL);
		hc->no_cache = 1;
		emit_html_header(stdout, hc);
	  }

	  if (load_notice_item("prologue", &p) != -1)
		printf("%s", p);

	  printf("%s", ds_buf(&notice_page));

	  emit_form(stdout, notice_uris, ack_handler, error_url, request_method,
				resource_uris, time_str, hmac_str);

	  if (load_notice_item("epilogue", &p) != -1)
		printf("%s", p);
	  if (load_notice_item("trailer", &p) != -1)
		printf("%s", p);
	  else
		emit_html_trailer(stdout);
	}
	else {
	  emit_xml_header(stdout, "dacs_notices");
	  printf("<%s>\n", make_xml_root_element("dacs_notices"));
	  printf("<presentation_reply");
	  printf(" notice_uris=\"%s\"", notice_uris);
	  printf(" resource_uris=\"%s\"", resource_uris);
	  printf(" ack_handler=\"%s\"", ack_handler);
	  if (secure) {
		printf(" hmac=\"%s\"", hmac_str);
		printf(" time=\"%s\"", time_str);
	  }
	  printf(">\n");

	  ds_init(&notice_page);
	  for (i = 0; i < dsvec_len(notices); i++) {
		char *notice_uri;

		notice_uri = (char *) dsvec_ptr_index(notices, i);
		ds_reset(&notice_page);
		if (get_notice(&notice_page, notice_uri) == -1) {
		  errmsg = ds_xprintf("Could not obtain notice: %s", notice_uri);
		  goto fail;
		}
		printf("<notice uri=\"%s\">\n", notice_uri);
		mime_encode_base64(ds_ucbuf(&notice_page), ds_len(&notice_page), &enc);
		printf("%s\n", enc);
		printf("</notice>\n");
	  }
	  printf("</presentation_reply>\n");
	  printf("</dacs_notices>\n");
	  emit_xml_trailer(stdout);
	}
  }
  else {
	errmsg = "A NOTICE_URIS or RESPONSE parameter is required";
	goto fail;
  }

  exit(0);
}

#endif
