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

/*
 * Manage local jurisdictional authentication InfoCard accounts for DACS.
 *
 * As the command dacsinfocard, which should be runnable only by a DACS
 * administrator, this provides account management for InfoCard-based accounts.
 * These accounts can be deleted/listed/enabled/disabled/etc., as
 * dacspasswd(1) does for its username/password accounts.  Additionally, the
 * utility can be given an xmlToken to decrypt, parse, validate,
 * or extract information.
 *
 * Creation of managed InfoCards is done by a different web service/program,
 * however: dacs_managed_infocard
 *
 * A secure hash of a self-issued InfoCard's PPID (private personal identifier)
 * and public key is used to uniquely identify a self-issued card without
 * the possibility of revealing the PPID - this hash is the key used to look
 * up the account:
 *   hash(PPID + Pubkey) --> username, status, info, roles
 * Both values are obtained at account enrollment time.
 * A unique username maps one-to-one with the (PPID, Pubkey) pair.
 *
 * As the web service dacs_infocard, this associates the current
 * identity, which must have been authenticated locally, with the self-issued
 * InfoCard that is submitted as the parameter "xmlToken" (with MIME type
 * application/x-informationCard), replacing any existing mapping.
 * Alternatively, by configuration, InfoCard contents (e.g., the email address
 * field) can be selected as the account identifer/DACS identity.
 *
 * The authentication module local_infocard_authenticate accepts
 * an InfoCard (self-issued or managed) and tests if there is a valid mapping
 * to a DACS identity.  Note that no USERNAME parameter to dacs_authenticate is
 * actually used, but dacs_authenticate may insist on one...
 *
 * For storage of InfoCard-based accounts, the "infocards" item type is used.
 *
 * The code uses the convention that names associated with self-issued
 * InfoCards begin with "sic_" ("Sic_" and "SIC_"), those associated with
 * managed InfoCards begin with "mic_" ("Mic_" and "MIC_"), and those that
 * are generic (common to both types) begin with "ic_" ("Ic_" and "IC_").
 * When the distinction isn't important, "INFOCARD_" is often used.
 * Naming inconsistencies etc. are the result of managed InfoCard support being
 * added after self-issued InfoCard support was implemented - these are being
 * fixed gradually.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: dacsinfocard.c 2925 2016-11-09 17:01:21Z brachman $";
#endif

#include "icx.h"

#include "dacs_api.h"

static MAYBE_UNUSED char *log_module_name = "dacsinfocard";

#ifndef PROG

static int debug_flag = 0;
#ifdef NOTDEF
static int is_admin = 0;
#endif
static char *item_type = ITEM_TYPE_INFOCARDS;

/*
 * If DIGEST_NAME is given, it is the name of a digest algorithm to use
 * instead of the one specified by the INFOCARD_DIGEST directive or the
 * default algorithm.
 * Verify that the name is acceptable and map it into its internal identifier.
 * If DIGEST_NAME was not given, set it to the name that was selected.
 *
 * Note that changing the algorithm will cause lookups of existing entries
 * by hash value to fail.  This will not cause a problem when looking up
 * an account by username, since that entry will point to the old hash value.
 * But if a user tries to associate his card with a different username and
 * the hash algorithm has changed, the old account entries will be left
 * dangling.
 */
int
ic_get_digest_algorithm(char **digest_name, Digest_desc *desc)
{
  char *name;
  Digest_desc *dd;

  if (*digest_name != NULL)
	name = *digest_name;
  else if ((name = conf_val(CONF_INFOCARD_DIGEST)) == NULL)
	name = INFOCARD_DIGEST_DEFAULT_NAME;

  if ((dd = passwd_get_digest_algorithm(name)) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "An unrecognized digest method is configured: \"%s\"", name));
	return(-1);
  }

  *desc = *dd;
  if (*digest_name == NULL)
	*digest_name = strdup(name);

  return(0);
}

Ic_sts_authtype
ic_lookup_sts_authtype(char *authtype_str)
{

  if (strcaseeq(authtype_str, "passwd")
	  || strcaseeq(authtype_str, "password")
	  || strcaseeq(authtype_str, "UsernamePasswordCredential"))
	return(INFOCARD_AUTHTYPE_PASSWD);

  if (strcaseeq(authtype_str, "card")
	  || strcaseeq(authtype_str, "SelfIssuedCredential"))
	return(INFOCARD_AUTHTYPE_CARD);

  if (strcaseeq(authtype_str, "cert")
	  || strcaseeq(authtype_str, "X509V3Credential"))
	return(INFOCARD_AUTHTYPE_CERT);

  if (strcaseeq(authtype_str, "kerberos")
	  || strcaseeq(authtype_str, "KerberosV5Credential"))
	return(INFOCARD_AUTHTYPE_KERBEROS);

  return(INFOCARD_AUTHTYPE_NONE);
}

int
ic_lookup_sts_authtype_str(Ic_sts_authtype authtype, char **short_str,
						   char **long_str)
{

  switch (authtype) {
  case INFOCARD_AUTHTYPE_PASSWD:
	if (short_str != NULL)
	  *short_str = "passwd";
	if (long_str != NULL)
	  *long_str = "UsernamePasswordCredential";
	break;

  case INFOCARD_AUTHTYPE_CARD:
	if (short_str != NULL)
	  *short_str = "card";
	if (long_str != NULL)
	  *long_str = "SelfIssuedCredential";
	break;

  case INFOCARD_AUTHTYPE_CERT:
	if (short_str != NULL)
	  *short_str = "cert";
	if (long_str != NULL)
	  *long_str = "X509V3Credential";
	break;

  case INFOCARD_AUTHTYPE_KERBEROS:
	if (short_str != NULL)
	  *short_str = "kerberos";
	if (long_str != NULL)
	  *long_str = "KerberosV5Credential";
	break;

  default:
	return(-1);
  }

  return(0);
}

static MAYBE_UNUSED int
is_mic_entry(char *key)
{

  if (strprefix(key, MIC_IDENTITY_PREFIX) != NULL
	  || strprefix(key, MIC_CARD_ID_PREFIX) != NULL)
	return(1);

  return(0);
}

static int
is_sic_entry(char *key)
{

  if (strprefix(key, SIC_USERNAME_PREFIX) != NULL
	  || strprefix(key, SIC_HASHSTR_PREFIX) != NULL)
	return(1);

  return(0);
}

typedef struct Mic_use_mode_table {
  Ic_use_mode use_mode;
  char *name;
} Mic_use_mode_table;

static Mic_use_mode_table mic_use_modes[] = {
  { IC_USE_MODE_DACS,    "dacs" },
  { IC_USE_MODE_STATIC,  "static" },
  { IC_USE_MODE_ISTATIC, "istatic" },
  { IC_USE_MODE_DYNAMIC, "dynamic" },
  { IC_USE_MODE_UNKNOWN, NULL }
};

static char *
mic_lookup_use_mode_str(Ic_use_mode use_mode)
{
  int i;

  for (i = 0; mic_use_modes[i].name != NULL; i++) {
	if (use_mode == mic_use_modes[i].use_mode)
	  return(mic_use_modes[i].name);
  }

  return(NULL);
}

static Ic_use_mode
mic_lookup_use_mode(char *name)
{
  int i;

  for (i = 0; mic_use_modes[i].name != NULL; i++) {
	if (strcaseeq(mic_use_modes[i].name, name))
	  return(mic_use_modes[i].use_mode);
  }

  return(IC_USE_MODE_UNKNOWN);
}

static char *
mic_make_key_from_identity(char *identity)
{
  char *enc_identity, *p;

  mime_encode_base64((unsigned char *) identity, strlen(identity),
					 &enc_identity);
  p = ds_xprintf("%s%s", MIC_IDENTITY_PREFIX, enc_identity);

  return(p);
}

static char *
mic_make_key_from_card_id(char *card_id)
{
  char *enc_card_id, *p;

  mime_encode_base64((unsigned char *) card_id, strlen(card_id), &enc_card_id);
  p = ds_xprintf("%s%s", MIC_CARD_ID_PREFIX, enc_card_id);

  return(p);
}

/*
 * Generate an XML representation for a managed InfoCard account entry.
 *
 * <!ELEMENT mic_account (identity, card_id, sts_auth, (claim)+) >
 * <!ATTLIST mic_account
 *     state  (enabled | disabled)        #REQUIRED
 *     use_mode (dacs | static | dynamic) #REQUIRED
 * >
 * <!ELEMENT identity EMPTY>
 * <!ELEMENT card_id EMPTY>
 *
 * <!ELEMENT sts_auth (digest | thumbprint | self_issued_ppid)>
 * <!ATTLIST sts_auth
 *     authtype (passwd | cert | card) #REQUIRED
 * >
 * <!ELEMENT digest EMPTY>
 * <!ATTLIST digest
 *     hash_alg CDATA #REQUIRED
 * >
 * <!ELEMENT thumbprint EMPTY>
 * <!ELEMENT self_issued_ppid EMPTY>
 *
 * <!ELEMENT claim (uri, label, description, (value)?) >
 * <!ELEMENT uri EMPTY>
 * <!ELEMENT label EMPTY>
 * <!ELEMENT description EMPTY>
 * <!ELEMENT value EMPTY>
 */
static Ds *
mic_format_xml_entry(Mic_entry *mic)
{
  int i;
  char *authtype_str, *sts_auth_el;
  Ds *ds;
  Icx_claim *claim;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<mic_account");
  ds_asprintf(ds, " state=\"%s\"",
			  (mic->state == IC_DISABLED) ? "disabled" : "enabled");
  ds_asprintf(ds, " use_mode=\"%s\">", mic_lookup_use_mode_str(mic->use_mode));

  ds_asprintf(ds, "<identity>%s</identity>", mic->identity);
  ds_asprintf(ds, "<card_id>%s</card_id>", mic->card_id);

  switch (mic->sts_auth->authtype) {
  case INFOCARD_AUTHTYPE_PASSWD:
	authtype_str = "passwd";
	sts_auth_el = ds_xprintf("<digest hash_alg=\"%s\">%s</digest>",
							 mic->sts_auth->desc->dt->name,
							 (mic->sts_auth->digest == NULL)
							 ? "" : mic->sts_auth->digest);
	break;

  case INFOCARD_AUTHTYPE_CERT:
	authtype_str = "cert";
	sts_auth_el = ds_xprintf("<thumbprint>%s</thumbprint>",
							 ds_buf(mic->sts_auth->thumbprint));
	break;

  case INFOCARD_AUTHTYPE_CARD:
	authtype_str = "card";
	sts_auth_el = ds_xprintf("<self_issued_ppid>%s</self_issued_ppid>",
							 mic->sts_auth->self_issued_ppid);
	break;

  case INFOCARD_AUTHTYPE_KERBEROS:
  default:
	return(NULL);
  }

  ds_asprintf(ds, "<sts_auth authtype=\"%s\">%s</sts_auth>",
			  authtype_str, sts_auth_el);

  for (i = 0; i < dsvec_len(mic->claims); i++) {
	claim = (Icx_claim *) dsvec_ptr_index(mic->claims, i);
	ds_asprintf(ds, "<claim>");
	ds_asprintf(ds, "<uri>%s/%s</uri>", claim->uri, claim->name);
	ds_asprintf(ds, "<label>%s</label>",
				xml_escape_cdata(claim->label));
	ds_asprintf(ds, "<description>%s</description>",
				xml_escape_cdata(claim->description));
	if (claim->value != NULL) {
	  ds_asprintf(ds, "<value>%s</value>", xml_escape_cdata(claim->value));
	  log_msg((LOG_TRACE_LEVEL, "Account claim %d: %s/%s=\"%s\"",
			   i, claim->uri, claim->name, claim->value));
	}
	else
	  log_msg((LOG_TRACE_LEVEL, "Account claim %d: %s/%s (no stored value)",
			   i, claim->uri, claim->name));

	ds_asprintf(ds, "</claim>");
  }

  ds_asprintf(ds, "</mic_account>");

  return(ds);
}

/*
 * Parse an (base-64 encoded) XML representation of a managed InfoCard
 * account entry.
 */
static Mic_entry *
mic_parse_entry(char *stored_entry)
{
  char *authtype_str, *p;
  unsigned char *entry_xml;
  long dec_len;
  xmlDocPtr doc;
  xmlNodePtr root, claim_head, node, sts_auth_el;
  xmlParserCtxtPtr parser_ctx;
  Icx_claim *claim;
  Mic_entry *mic;
  Mic_sts_auth *sts_auth;

  if ((dec_len = mime_decode_base64(stored_entry, &entry_xml)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Decoding error"));
	return(NULL);
  }

  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	return(NULL);
  }

  doc = xmlCtxtReadMemory(parser_ctx, (const char *) entry_xml, dec_len,
						  NULL, NULL, 0);
  if (doc == NULL) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	log_msg((LOG_ERROR_LEVEL, "Parse failed, non-well-formed XML: %s",
			 (err != NULL) ? err->message : "null"));
	return(NULL);
  }

  root = xmlDocGetRootElement(doc);
  if (root->type != XML_ELEMENT_NODE
	  || !xmlStreq(root->name, (xmlChar *) "mic_account")
	  || root->next != NULL)
	  return(NULL);

  mic = ALLOC(Mic_entry);

  if ((p = xmlGetAttrValue(root, (xmlChar *) "state")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"state\" attribute"));
	return(NULL);
  }
  if (strcaseeq(p, "enabled"))
	mic->state = IC_ENABLED;
  else if (strcaseeq(p, "disabled"))
	mic->state = IC_DISABLED;
  else {
	log_msg((LOG_ERROR_LEVEL, "Parse error: invalid state attribute"));
	return(NULL);
  }

  if ((p = xmlGetAttrValue(root, (xmlChar *) "use_mode")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"use_mode\" attribute"));
	return(NULL);
  }
  if ((mic->use_mode = mic_lookup_use_mode(p)) == IC_USE_MODE_UNKNOWN) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: invalid use_mode attribute"));
	return(NULL);
  }

  if ((mic->identity = xmlGetChildText(root, (xmlChar *) "identity")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no identity element"));
	return(NULL);
  }

  if ((mic->card_id = xmlGetChildText(root, (xmlChar *) "card_id")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no card_id element"));
	return(NULL);
  }

  mic->sts_auth = sts_auth = ALLOC(Mic_sts_auth);
  sts_auth->authtype = INFOCARD_AUTHTYPE_NONE;
  sts_auth->desc = NULL;
  sts_auth->digest = NULL;
  sts_auth->self_issued_ppid = NULL;
  sts_auth->thumbprint = NULL;

  if ((sts_auth_el = xmlGetChild(root, (xmlChar *) "sts_auth")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no sts_auth element"));
	return(NULL);
  }
  if ((authtype_str = xmlGetAttrValue(sts_auth_el, (xmlChar *) "authtype"))
	  == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no authtype attribute"));
	return(NULL);
  }
  if (streq(authtype_str, "passwd")) {
	sts_auth->authtype = INFOCARD_AUTHTYPE_PASSWD;
	sts_auth->digest = xmlGetChildText(sts_auth_el, (xmlChar *) "digest");
  }
  else if (streq(authtype_str, "cert")) {
	sts_auth->authtype = INFOCARD_AUTHTYPE_CERT;
	if ((p = xmlGetChildText(sts_auth_el, (xmlChar *) "thumbprint")) == NULL)
	  return(NULL);
	sts_auth->thumbprint = ds_set(NULL, p);
  }
  else if (streq(authtype_str, "card")) {
	sts_auth->authtype = INFOCARD_AUTHTYPE_CARD;
	sts_auth->self_issued_ppid
	  = xmlGetChildText(sts_auth_el, (xmlChar *) "self_issued_ppid");
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "Unrecognized authtype attribute value: %s",
			 authtype_str));
	return(NULL);
  }

  if ((claim_head = xmlGetChild(root, (xmlChar *) "claim")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no claim element"));
	return(NULL);
  }

  mic->claims = dsvec_init(NULL, sizeof(Icx_claim *));
  for (node = claim_head; node != NULL; node = node->next) {
	char *uri;

	if (!xmlStreq(node->name, (xmlChar *) "claim")) {
	  log_msg((LOG_ERROR_LEVEL, "Parse error: claim format"));
	  return(NULL);
	}

	claim = ALLOC(Icx_claim);
	if ((uri = xmlGetChildText(node, (xmlChar *) "uri")) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Parse error: uri element"));
	  return(NULL);
	}
	claim->uri = strdirname(uri);
	claim->type = icx_lookup_claim_type(claim->uri);
	claim->name = strbasename(uri, NULL);

	if ((p = xmlGetChildText(node, (xmlChar *) "label")) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Parse error: label element"));
	  return(NULL);
	}
	claim->label = xml_unescape_cdata(p);

	if ((p = xmlGetChildText(node, (xmlChar *) "description")) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Parse error: description element"));
	  return(NULL);
	}
	claim->description = xml_unescape_cdata(p);

	if ((p = xmlGetChildText(node, (xmlChar *) "value")) != NULL)
	  claim->value = xml_unescape_cdata(p);
	else
	  claim->value = NULL;

	dsvec_add_ptr(mic->claims, claim);
  }

  xmlFreeDoc(doc);
  xmlFreeParserCtxt(parser_ctx);

  return(mic);
}

static Mic_entry *
mic_read_entry(Vfs_handle *h, char *key)
{
  char *stored_entry;
  Mic_entry *mic;

  log_msg((LOG_TRACE_LEVEL, "Fetch key=\"%s\"", key));
  if (vfs_get(h, key, (void **) &stored_entry, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot read entry for key \"%s\"", key));
	return(NULL);
  }

  if ((mic = mic_parse_entry(stored_entry)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot decode record, key \"%s\"", key));
	return(NULL);
  }

  return(mic);
}

Mic_entry *
mic_read(Vfs_handle *h, char *identity, char *card_id)
{
  char *key, *kstr;
  Mic_entry *mic;

  if ((kstr = identity) != NULL)
	key = mic_make_key_from_identity(identity);
  else if ((kstr = card_id) != NULL)
	key = mic_make_key_from_card_id(card_id);
  else
	return(NULL);

  if ((mic = mic_read_entry(h, key)) == NULL)
	return(NULL);

  return(mic);
}

/*
 * Register a managed InfoCard so that it can be used for DACS authentication
 * or by a relying party.
 * The account record will be indexed by both an identity and a card identifier.
 *
 * IDENTITY is the DACS identity that is being associated with the InfoCard
 * CARD_ID is a unique identifier (as a URI) associated with the InfoCard.
 * An identity maps to exactly one InfoCard (CardId), and vice versa.
 * If the identity already maps to an InfoCard, that mapping is deleted
 * (so unless an InfoCard is being re-registered, the previously registered
 * InfoCard cannot be used for authentication).
 * CLAIM is zero or more claim definitions (Icx_claim *), which may or may not
 * have values.
 * STATE is the initial status of the account.
 */
int
mic_register_entry(Vfs_handle *h, char *identity, char *card_id,
				   Dsvec *claims, Ic_use_mode use_mode, Mic_sts_auth *sts_auth,
				   Ic_state state)
{
  int st;
  char *card_id_key, *identity_key, *new_entry, *stored_entry;
  long new_entry_len;
  Ds *ds;
  Mic_entry *mic;

  identity_key = mic_make_key_from_identity(identity);
  card_id_key = mic_make_key_from_card_id(card_id);

  /*
   * If this identity already has a card, invalidate the card by deleting
   * the mappings.
   */
  if ((st = vfs_exists(h, identity_key)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot check for identity \"%s\"", identity));
	return(-1);
  }
  log_msg((LOG_DEBUG_LEVEL, "Entry %s for identity \"%s\"",
		   (st == 0) ? "does not exist" : "exists", identity));

  if (st) {
	char *old_card_id_key;
	Mic_entry *old_mic;

	/* The identity already has a stored entry, so it needs to be replaced. */
	if (vfs_get(h, identity_key, (void **) &stored_entry, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot read entry for \"%s\"", identity));
	  return(-1);
	}

	/* Extract the card_id and delete that record too. */
	if ((old_mic = mic_parse_entry(stored_entry)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot decode existing card_id record"));
	  return(-1);
	}

	old_card_id_key = mic_make_key_from_card_id(old_mic->card_id);
	if (vfs_delete(h, old_card_id_key) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot delete entry for \"%s\"",
			   old_mic->card_id));
	  return(-1);
	}

	if (vfs_delete(h, identity_key) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot delete entry for \"%s\"", identity));
	  return(-1);
	}
  }

  mic = ALLOC(Mic_entry);
  mic->identity = identity;
  mic->state = state;
  mic->use_mode = use_mode;
  mic->card_id = card_id;
  mic->sts_auth = sts_auth;
  mic->claims = claims;

  ds = mic_format_xml_entry(mic);
  log_msg((LOG_TRACE_LEVEL, "Entry: %s", ds_buf(ds)));
  new_entry_len = mime_encode_base64(ds_ucbuf(ds), ds_len(ds), &new_entry);

  log_msg((LOG_TRACE_LEVEL, "Storing identity key=\"%s\"", identity_key));
  if (vfs_put(h, identity_key, new_entry, new_entry_len + 1) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot write entry for identity \"%s\"",
			 identity));
	return(-1);
  }

  log_msg((LOG_TRACE_LEVEL, "Storing card_id key=\"%s\"", card_id_key));
  if (vfs_put(h, card_id_key, new_entry, new_entry_len + 1) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot write entry for card_id \"%s\"",
			 mic->card_id));
	return(-1);
  }

  return(0);
}

/*
 * Create a key for a self-issued InfoCard based on the username.
 */
static char *
sic_make_key_from_username(char *username)
{
  char *p;

  p = ds_xprintf("%s%s", SIC_USERNAME_PREFIX, username);

  return(p);
}

/*
 * Create a key for a self-issued InfoCard based on a hash.
 */
static char *
sic_make_key_from_hashstr(char *hashstr)
{
  char *p;

  p = ds_xprintf("%s%s", SIC_HASHSTR_PREFIX, hashstr);

  return(p);
}

typedef struct Ic_claim_parse {
  char *str;
  Icx_claim_type type;
  char *ns_uri;
  char *ns_abbrev;
  char *name;
} Ic_claim_parse;

static char *
expand_dacs_claim_name(char *claim_str, Ic_claim_parse *cp)
{
  char *abbrev_namespace, *claim, *claim_name, *claim_uri, *p;
  Icx_claim_type type;

  if ((p = strprefix(claim_str, "standard:")) != NULL) {
	if (*p == '\0' || !strprintable(p, 0, 0))
	  return(NULL);
	claim_uri = ICX_STANDARD_CLAIM_URI;
	claim_name = p;
	abbrev_namespace = "standard";
	type = ICX_STANDARD_CLAIM;
  }
  else if ((p = strprefix(claim_str, "dacs:")) != NULL) {
	if (*p == '\0' || !strprintable(p, 0, 0))
	  return(NULL);
	claim_uri = ICX_DACS_CLAIM_URI;
	claim_name = p;
	abbrev_namespace = "dacs";
	type = ICX_DACS_CLAIM;
  }
  else {
	Uri *uri;

	/* This might be some other URI or just a word, like "ppid". */
	if ((uri = uri_parse(claim_str)) != NULL) {
	  if (uri->path == NULL || strlen(uri->path) == 0)
		return(NULL);
	  if ((p = strrchr(uri->uri, (int) '/')) == NULL
		  || *(p + 1) == '\0')
		return(NULL);
	  claim_uri = strdup(uri->uri);
	  claim_uri[p - uri->uri] = '\0';
	  claim_name = &claim_uri[p - uri->uri + 1];
	  if (streq(claim_uri, ICX_STANDARD_CLAIM_URI)) {
		type = ICX_STANDARD_CLAIM;
		abbrev_namespace = "standard";
	  }
	  else if (streq(claim_uri, ICX_DACS_CLAIM_URI)) {
		type = ICX_DACS_CLAIM;
		abbrev_namespace = "dacs";
	  }
	  else {
		type = ICX_USER_CLAIM;
		abbrev_namespace = "";
	  }
	}
	else {
	  claim_uri = NULL;
	  claim_name = claim_str;
	  abbrev_namespace = "";
	  type = ICX_PSEUDO_CLAIM;
	}
  }

  if (claim_uri != NULL)
	claim = ds_xprintf("%s/%s", claim_uri, claim_name);
  else
	claim = claim_name;

  if (cp != NULL) {
	cp->str = strdup(claim_str);
	cp->ns_uri = claim_uri;
	cp->name = claim_name;
	cp->ns_abbrev = abbrev_namespace;
	cp->type = type;
  }

  return(claim);
}

/*
 * The username associated with a self-issued InfoCard account depends on the
 * value of INFOCARD_USERNAME_SELECTOR; if it is "credentials", the username
 * is obtained from CREDENTIALS (which must have come from this
 * jurisdiction); if it is "email", the username is obtained from the email
 * address field of the InfoCard; if it is "webpage", the username is derived
 * from the web page URL in the InfoCard; in the future there may be other
 * alternatives.
 */
static char *
ic_select_username(Ic_token *token, Credentials *selected)
{
  char *selector, *username;

  if ((selector = conf_val(CONF_INFOCARD_USERNAME_SELECTOR)) == NULL
	  || strcaseeq(selector, "credentials")) {
	/* The default is to use the current username. */
	if (selected == NULL) {
	  log_msg((LOG_DEBUG_LEVEL, "No credentials, cannot select username"));
	  return(NULL);
	}

	if (selected->next != NULL) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Multiple credentials, cannot select username"));
	  return(NULL);
	}

	if (!is_local_user_identity(selected)) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Credentials not local, cannot select username"));
	  return(NULL);
	}

	username = selected->username;
  }
  else if (strcaseeq(selector, "email")) {
	char *claim, *email;

	claim = expand_dacs_claim_name("standard:emailaddress", NULL);
	email = kwv_lookup_value(token->kwv_claims, claim);
	if (email == NULL) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "InfoCard has no email claim, cannot select username"));
	  return(NULL);
	}

	if (!is_valid_username(email)) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Email address is not usable, cannot select username"));
	  return(NULL);
	}

	username = email;
  }
  else if (strcaseeq(selector, "webpage")) {
	char *claim, *webpage;

	claim = expand_dacs_claim_name("standard:webpage", NULL);
	webpage = kwv_lookup_value(token->kwv_claims, claim);
	if (webpage == NULL) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "InfoCard has no webpage claim, cannot select username"));
	  return(NULL);
	}

	/* XXX It is almost certainly invalid without canonicalization... */
	if (!is_valid_username(webpage)) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Webpage address is not usable, cannot select username"));
	  return(NULL);
	}

	username = webpage;
  }
  else {
	log_msg((LOG_ERROR_LEVEL,
			 "Unrecognized INFOCARD_USERNAME_SELECTOR value, cannot select username"));
	return(NULL);
  }

  /* XXX we will implement only the pre-authenticated case, initially. */
  if (selected == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Unauthenticated user, cannot select username"));
	return(NULL);
  }

  return(username);
}

/*
 * Given XMLTOKEN, a secure token received by a Relying Party, parse and
 * validate it, then extract all of the fields that might be needed to create
 * a self-issued InfoCard account, use an existing account, or provide claim
 * information.
 */
Ic_token *
ic_parse_token(Icx_ctx *ctx, char *xmlToken)
{

  if (xmlToken == NULL)
	return(NULL);

  log_msg((LOG_DEBUG_LEVEL, "xmlToken=\"%s\"", xmlToken));

  if (icx_process_token(ctx, (const xmlChar *) xmlToken, 0) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Parse or validation of xmlToken failed"));
	return(NULL);
  }

  log_msg((LOG_TRACE_LEVEL, "Parse and validation of xmlToken ok"));

  return(ctx->token);
}

/*
 * Hash various fields from an InfoCard.
 * The resulting string will become the unique identifier that we associate
 * with a particular InfoCard.
 *
 * Note that we do not use the PPID for this purpose, because doing
 * so would make the account file vulnerable to attack.  Simply using a hash
 * of the PPID for this purpose would be better, because it would make it
 * more difficult to steal PPIDs, but although supposedly secret, PPIDs are
 * transmitted... if captured by an attacker, the attacker
 * could create a new token containing the PPID and authenticate using it.
 * Also, a PPID is guaranteed to be unique per site only for
 * self-issued infocards.
 *
 * The public key, used to verify its token's signature, is a better
 * identifier; presumably it is unique, and the corresponding private key
 * is never transmitted, making it much harder for an attacker to create a
 * valid token (one with a valid signature).
 *
 * Therefore, we use a hash of the PPID *and* the public key as the
 * effective account identifier.  And we store the public key at registration
 * time and verify that it is used in future tokens (otherwise someone may
 * have crafted a token with a stolen PPID and a valid signature but using
 * his own public/private key pair.
 */
static char *
ic_hash(Ic_token *token, char *digest_name)
{
  unsigned int hashlen;
  unsigned char *hashval;
  char *hashstr;
  Ds *ds;

  ds = ds_dsappend(NULL, 1, 3, token->ppid, token->exponent, token->modulus);
  ds_appendc(ds, (int) '\0');
  hashval = crypto_digest(digest_name, ds_buf(ds), ds_len(ds), NULL, &hashlen);
  mime_encode_base64(hashval, hashlen, &hashstr);
  log_msg((LOG_DEBUG_LEVEL, "Hashed %s (%d) to get %s",
		   ds_buf(ds), ds_len(ds), hashstr));
  ds->clear_flag = 1;
  ds_free(ds);

  return(hashstr);
}

static char *
save_buffer_to_temp(char *buf, size_t buflen)
{
  int st;
  char *filename;
  size_t len;
  FILE *fp;

  if ((filename = create_temp_filename(NULL)) == NULL)
	return(NULL);

  if ((fp = create_temp_file(filename)) == NULL)
	return(NULL);

  if (buflen == 0)
	len = strlen(buf);
  else
	len = buflen;

  st = write_buffer(fileno(fp), buf, len);

  fclose(fp);

  return(st == -1 ? NULL : filename);
}

static Sic_entry *
sic_init_entry(Sic_entry *oic, char *digest, char *username, Ic_token *token)
{
  Sic_entry *sic;

  if (oic == NULL)
	sic = ALLOC(Sic_entry);
  else
	sic = oic;

  sic->state = IC_UNCHANGED;
  sic->desc = NULL;
  sic->username = username;
  sic->friendly_id = icx_friendly_identifier(token->ppid, 1);
  sic->pubkey_modulus = ds_buf(token->modulus);
  sic->pubkey_exponent = ds_buf(token->exponent);
  sic->digest = digest;
  sic->private = NULL;

  return(sic);
}

/*
 * <!ELEMENT ic_account (digest, username, pubkey_modulus, pubkey_exponent,
 *     (private)? >
 * <!ATTLIST ic_account
 *   state (enabled | disabled) #REQUIRED
 *   friendly_id          CDATA #REQUIRED
 *   alg                  CDATA #REQUIRED
 * >
 * <!ELEMENT digest EMPTY>
 * <!ELEMENT username EMPTY>
 * <!ELEMENT pubkey_modulus EMPTY>
 * <!ELEMENT pubkey_exponent EMPTY>
 * <!ELEMENT private EMPTY>
 * 
 * state: "enabled" | "disabled"
 * alg:    name of the digest algorithm used to create unique account identifier
 * digest: the hash value (unique account identifier)
 * username: DACS username associated with this account entry (relative to
 *         the current jurisdiction)
 * pubkey_modulus/pubkey_exponent: public key components from the cert
 *   held by the user
 * private:  optional private (opaque) data stored with this account entry
 *
 * This function's inverse is ic_parse_entry().
 * It is the <digest>, computed by ic_hash(), that is the unique identifier
 * for the account.
 */
static Ds *
sic_format_xml_entry(Sic_entry *sic)
{
  const char *digest_name;
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<ic_account");
  ds_asprintf(ds, " state=\"%s\"",
			  (sic->state == IC_DISABLED) ? "disabled" : "enabled");
  ds_asprintf(ds, " friendly_id=\"%s\"", ds_buf(sic->friendly_id));
  if ((digest_name = sic->desc->dt->name) == NULL)
	return(NULL);
  ds_asprintf(ds, " alg=\"%s\">", digest_name);

  ds_asprintf(ds, "<digest>%s</digest>", xml_escape_cdata(sic->digest));
  ds_asprintf(ds, "<username>%s</username>", xml_escape_cdata(sic->username));
  ds_asprintf(ds, "<pubkey_modulus>%s</pubkey_modulus>",
			  xml_escape_cdata(sic->pubkey_modulus));
  ds_asprintf(ds, "<pubkey_exponent>%s</pubkey_exponent>",
			  xml_escape_cdata(sic->pubkey_exponent));

  if (sic->private != NULL)
	ds_asprintf(ds, "<private>%s</private>",
				xml_escape_cdata(ds_buf(sic->private)));

  ds_asprintf(ds, "</ic_account>");

  return(ds);
}

/*
 * Parse and validate an account record for a self-issued InfoCard.
 * An account record is an XML document (sans the XML declaration).
 */
static Sic_entry *
sic_parse_entry(char *stored_entry)
{
  char *p;
  unsigned char *entry_xml;
  long dec_len;
  xmlDocPtr doc;
  xmlNodePtr root;
  xmlParserCtxtPtr parser_ctx;
  Sic_entry *sic;

  log_msg((LOG_TRACE_LEVEL, "Parsing stored entry: %s", stored_entry));

  if ((dec_len = mime_decode_base64(stored_entry, &entry_xml)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Decoding error"));
	return(NULL);
  }

  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	return(NULL);
  }

  doc = xmlCtxtReadMemory(parser_ctx, (const char *) entry_xml, dec_len,
						  NULL, NULL, 0);
  if (doc == NULL) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	log_msg((LOG_ERROR_LEVEL, "Parse failed, non-well-formed XML: %s",
			 (err != NULL) ? err->message : "null"));
	return(NULL);
  }

  root = xmlDocGetRootElement(doc);
  if (root->type != XML_ELEMENT_NODE
	  || !xmlStreq(root->name, (xmlChar *) "ic_account")
	  || root->next != NULL)
	  return(NULL);

  sic = ALLOC(Sic_entry);

  if ((p = xmlGetAttrValue(root, (xmlChar *) "state")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"state\" attribute"));
	return(NULL);
  }
  if (strcaseeq(p, "enabled"))
	sic->state = IC_ENABLED;
  else if (strcaseeq(p, "disabled"))
	sic->state = IC_DISABLED;
  else {
	log_msg((LOG_ERROR_LEVEL, "Parse error: invalid state attribute"));
	return(NULL);
  }

  if ((p = xmlGetAttrValue(root, (xmlChar *) "friendly_id")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"friendly_id\" attribute"));
	return(NULL);
  }
  sic->friendly_id = ds_set(NULL, p);

  if ((p = xmlGetAttrValue(root, (xmlChar *) "alg")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"alg\" attribute"));
	return(NULL);
  }
  if ((sic->desc = passwd_get_digest_algorithm(p)) == NULL) {
    log_msg((LOG_ERROR_LEVEL, "Parse error: Unrecognized \"alg\" field: %s",
			 p));
	return(NULL);
  }

  if ((p = xmlGetChildText(root, (xmlChar *) "digest")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"digest\" element"));
	return(NULL);
  }
  sic->digest = xml_unescape_cdata(p);

  if ((p = xmlGetChildText(root, (xmlChar *) "username")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"username\" element"));
	return(NULL);
  }
  sic->username = xml_unescape_cdata(p);

  if ((p = xmlGetChildText(root, (xmlChar *) "pubkey_modulus")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"pubkey_modulus\" element"));
	return(NULL);
  }
  sic->pubkey_modulus = xml_unescape_cdata(p);

  if ((p = xmlGetChildText(root, (xmlChar *) "pubkey_exponent")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: no \"pubkey_exponent\" element"));
	return(NULL);
  }
  sic->pubkey_exponent = xml_unescape_cdata(p);

  if ((p = xmlGetChildText(root, (xmlChar *) "private")) != NULL)
	sic->private = ds_set(NULL, xml_unescape_cdata(p));
  else
	sic->private = NULL;

  xmlFreeDoc(doc);
  xmlFreeParserCtxt(parser_ctx);

  return(sic);
}

/*
 * Return 1 if an entry for USERNAME exists, 0 if it does not, and
 * -1 if cannot tell because an error occurred.
 */
static int
sic_exists_username_entry(Vfs_handle *h, char *username)
{
  int st;

  st = vfs_exists(h, sic_make_key_from_username(username));
  if (st == -1)
	log_msg((LOG_DEBUG_LEVEL, "Cannot find entry for username \"%s\"",
			 username));
  else if (st)
	log_msg((LOG_DEBUG_LEVEL, "Entry exists for username=\"%s\"", username));
  else
	log_msg((LOG_DEBUG_LEVEL, "Entry does not exist for username=\"%s\"",
			 username));

  return(st);
}

/*
 * Return 1 if an entry for HASHSTR exists, 0 if it does not, and
 * -1 if cannot tell because an error occurred.
 */
static int
sic_exists_hashstr_entry(Vfs_handle *h, char *hashstr)
{
  int st;

  if ((st = vfs_exists(h, sic_make_key_from_hashstr(hashstr))) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot find entry for hashstr \"%s\"",
			 hashstr));
	return(-1);
  }

  log_msg((LOG_DEBUG_LEVEL, "Entry %s for hashstr=\"%s\"",
		   (st == 0) ? "exists" : "does not exist", hashstr));

  return(st);
}

/*
 * Read a self-issued InfoCard account entry for KEY.
 * If it does not exist or there is an error, return NULL.
 */
static Sic_entry *
sic_read_entry(Vfs_handle *h, char *key)
{
  char *stored_entry;
  Sic_entry *sic;

  if (vfs_get(h, key, (void **) &stored_entry, NULL) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of key \"%s\" failed", key));
	return(NULL);
  }

  if (stored_entry == NULL || stored_entry[0] == '\0') {
	log_msg((LOG_ALERT_LEVEL, "Invalid entry for key \"%s\"?", key));
	return(NULL);
  }

  if ((sic = sic_parse_entry(stored_entry)) == NULL)
	return(NULL);

  return(sic);
}

static Sic_entry *
sic_read(Vfs_handle *h, char *hashstr, char *username)
{
  int st;
  char *key;
  Sic_entry *sic;

  st = 0;
  key = NULL;
  if (hashstr != NULL) {
	if ((st = sic_exists_hashstr_entry(h, hashstr)) == -1) {
	  log_msg((LOG_DEBUG_LEVEL, "Cannot find entry for digest \"%s\"",
			   hashstr));
	  return(NULL);
	}
	if (st == 1)
	  key = sic_make_key_from_hashstr(hashstr);
  }

  if (st == 0 && username != NULL) {
	if ((st = sic_exists_username_entry(h, username)) == -1) {
	  log_msg((LOG_DEBUG_LEVEL, "Cannot read entry for username \"%s\"",
			   username));
	  return(NULL);
	}

	if (st == 1)
	  key = sic_make_key_from_username(username);
  }

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

  if ((sic = sic_read_entry(h, key)) == NULL)
	return(NULL);

  return(sic);
}

static MAYBE_UNUSED Sic_entry *
sic_getdigest_entry(Vfs_handle *h, char *username)
{
  Sic_entry *sic;

  if ((sic = sic_read(h, NULL, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(NULL);
  }

  return(sic);
}

static MAYBE_UNUSED Ds *
sic_getdata_entry(Vfs_handle *h, char *username)
{
  Sic_entry *sic;

  if ((sic = sic_read(h, NULL, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(NULL);
  }

  if (sic->private == NULL)
	return(ds_set(NULL, ""));

  return(sic->private);
}

Ic_config *
ic_config(void)
{
  Ic_config *conf;

  conf = ALLOC(Ic_config);
  conf->certfile = NULL;
  conf->keyfile = NULL;
  conf->audience = NULL;
  conf->issuer = NULL;
  conf->enable_replay_detection = 0;
  conf->enable_signature_validation = 1;
  conf->max_token_size = 0;
  conf->max_drift_secs = 0;

  return(conf);
}

/*
 * Return 1 if replay is detected, 0 otherwise.
 */
static int
detect_replay(Icx_ctx *ctx, const xmlChar *replay_value,
			  time_t not_on_or_after, void *detector_context)
{

  return(0);
}

Icx_ctx *
ic_init(Ic_config *conf)
{
  Icx_ctx *ctx;

  if (icx_init() == -1)
	return(NULL);

  ctx = icx_ctx_create();

  ctx->validate_self_issued = 0;
  if (conf->issuer != NULL) {
	if (strcaseeq(conf->issuer, "SELF"))
	  ctx->validate_self_issued = 1;
  }

  if (conf->certfile != NULL) {
	if (icx_load_certificate(ctx, conf->certfile) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not load certificate: \"%s\"",
			   conf->certfile));
	  return(NULL);
	}
  }

  if (conf->keyfile != NULL) {
	if (icx_load_key(ctx, conf->keyfile, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not load key: \"%s\"", conf->keyfile));
	  return(NULL);
	}
  }

  if (conf->audience != NULL)
	icx_set_audience(ctx, conf->audience);

  if (conf->enable_replay_detection)
	icx_set_replay_detection(ctx, detect_replay, NULL);
  else
	icx_set_replay_detection(ctx, NULL, NULL);

  if (conf->max_token_size)
	icx_set_max_token_size(ctx, conf->max_token_size);
  else
	icx_set_max_token_size(ctx, IC_MAX_TOKEN_SIZE);

  ctx->enable_signature_validation = conf->enable_signature_validation;

  if (debug_flag)
	icx_set_time_conditions(ctx, -1, -1);
  else {
	if (conf->max_drift_secs)
	  icx_set_time_conditions(ctx, conf->max_drift_secs, conf->max_drift_secs);
	else
	  icx_set_time_conditions(ctx, IC_MAX_DRIFT_SECS, IC_MAX_DRIFT_SECS);
  }

  log_msg((LOG_DEBUG_LEVEL, "now=%u", ctx->now));

  return(ctx);
}

Dsvec *
ic_get_config_audience(void)
{
  Dsvec *dsv;
  Kwv_pair *v;

  dsv = dsvec_init(NULL, sizeof(char *));
  for (v = conf_var(CONF_INFOCARD_AUDIENCE); v != NULL; v = v->next)
	dsvec_add_ptr(dsv, v->val);

  return(dsv);
}

/*
 * Find the account entry associated with XMLTOKEN.  If it exists,
 * it will be keyed by the token's hash value, the user's email address
 * (contained in the token), or both.
 * This is used by local_infocard_auth().
 */
Ic_auth *
ic_lookup_entry(Vfs_handle *h, char *xmlToken, Ic_config *conf)
{
  char *claim, *digest_name, *hashstr, *identity, *role_str, *username;
  DACS_name dacs_name;
  DACS_name_cmp cmp_mode;
  Ic_auth *ic_auth;
  Icx_ctx *ctx;
  Ic_token *token;
  Mic_entry *mic;
  Digest_desc dd;
  Sic_entry *sic;

  if (xmlToken == NULL)
	return(NULL);

  digest_name = NULL;
  if (ic_get_digest_algorithm(&digest_name, &dd) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Digest configuration error"));
	return(NULL);
  }

  if ((ctx = ic_init(conf)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot initialize ctx"));
	return(NULL);
  }

  if ((token = ic_parse_token(ctx, xmlToken)) == NULL) {
	char *filename;

	if ((filename = save_buffer_to_temp(xmlToken, 0)) != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Saved xmlToken to temp file: %s", filename));
	log_msg((LOG_ERROR_LEVEL, "Error parsing xmlToken argument"));
	return(NULL);
  }

  if (token->issuer == NULL) {
	char *email;

	/* A token from a self-issued InfoCard. */
	hashstr = ic_hash(token, digest_name);
	log_msg((LOG_DEBUG_LEVEL, "hashstr=\"%s\"", hashstr));

	claim = expand_dacs_claim_name("standard:emailaddress", NULL);
	if ((email = kwv_lookup_value(token->kwv_claims, claim)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot get emailaddress claim"));
	  return(NULL);
	}
	if ((sic = sic_read(h, hashstr, email)) == NULL)
	  return(NULL);
	ic_auth = ALLOC(Ic_auth);
	ic_auth->type = IC_SELF_ISSUED_TYPE;
	ic_auth->state = sic->state;
	ic_auth->use_mode = IC_USE_MODE_DACS;
	ic_auth->username = sic->username;
	ic_auth->roles = NULL;
	ic_auth->kwv_claims = token->kwv_claims;

	return(ic_auth);
  }

  /*
   * To be used for DACS authentication, the dacs_identity claim must
   * exist in our managed InfoCard.
   * The managed InfoCard will be indexed by the identity, if it has been
   * registered.
   * We'll consider mapping the identity to a username in this jurisdiction,
   * if necessary;
   */
  claim = expand_dacs_claim_name("dacs:dacs_identity", NULL);
  if ((identity = kwv_lookup_value(token->kwv_claims, claim)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No dacs_identity claim found"));
	return(NULL);
  }

  /* Check for syntactic validity. */
  if (parse_dacs_name(identity, &dacs_name) != DACS_USER_NAME) {
	log_msg((LOG_ERROR_LEVEL, "Error parsing DACS identity"));
	return(NULL);
  }

  cmp_mode = get_name_cmp_mode();
  if (!name_eq(dacs_name.jurisdiction, conf_val(CONF_JURISDICTION_NAME),
			   cmp_mode)
      || !name_eq(dacs_name.federation, conf_val(CONF_FEDERATION_NAME),
				  cmp_mode)) {
	log_msg((LOG_ERROR_LEVEL, "Cannot import identity: %s", identity));
	return(NULL);
  }

  username = dacs_name.username;

  log_msg((LOG_DEBUG_LEVEL, "Using managed token: identity=\"%s\"", identity));

  if ((mic = mic_read(h, identity, NULL)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot find managed identity \"%s\"", identity));
	return(NULL);
  }

  /*
   * A managed card may optionally have roles.
   */
  claim = expand_dacs_claim_name("dacs:dacs_roles", NULL);
  if ((role_str = kwv_lookup_value(token->kwv_claims, claim)) == NULL)
	log_msg((LOG_DEBUG_LEVEL, "No dacs_roles claim found"));
  else {
	/* Check for syntactic validity. */
	if (!is_valid_role_str(role_str)) {
	  log_msg((LOG_ERROR_LEVEL, "Ignoring invalid role string: \"%s\"",
			   role_str));
	  role_str = NULL;
	}
  }

  ic_auth = ALLOC(Ic_auth);
  ic_auth->type = IC_MANAGED_TYPE;
  ic_auth->state = mic->state;
  ic_auth->use_mode = mic->use_mode;
  ic_auth->username = username;
  ic_auth->roles = role_str;
  ic_auth->kwv_claims = token->kwv_claims;

  return(ic_auth);
}

int
ic_check_infocard(Vfs_handle *h, char *xmlToken)
{

  return(-1);
}

/*
 * If an entry exists with key HASHSTR, delete the entry, return a copy
 * of it if EXISTING_IC is non-NULL, and return 1.
 * If the entry does not exist, return 0.
 * If an error occurs, return -1.
 */
static int
sic_delete_hashstr_entry(Vfs_handle *h, char *hashstr, Sic_entry **existing_sic)
{
  int st;
  Sic_entry *sic;

  log_msg((LOG_TRACE_LEVEL, "Deleting entry, hashstr=\"%s\"", hashstr));

  if (existing_sic != NULL)
	*existing_sic = NULL;

  sic = NULL;
  if ((st = sic_exists_hashstr_entry(h, hashstr)) == 1) {
	if (existing_sic != NULL
		&& (sic = sic_read(h, hashstr, NULL)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Entry exists but cannot read it, hashstr=\"%s\"", hashstr));
	  return(-1);
	}

	if (vfs_delete(h, sic_make_key_from_hashstr(hashstr)) == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Cannot delete entry, hashstr=\"%s\"", hashstr));
	  return(-1);
	}

	log_msg((LOG_DEBUG_LEVEL, "Entry deleted, hashstr=\"%s\"", hashstr));
	if (existing_sic != NULL)
	  *existing_sic = sic;

	return(1);
  }
  else if (st == 0) {
	log_msg((LOG_DEBUG_LEVEL, "No entry exists for hashstr=\"%s\"", hashstr));
	return(0);
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "VFS error, cannot read entry, hashstr=\"%s\"",
			 hashstr));
	return(-1);
  }
}

static int
sic_delete_username_entry(Vfs_handle *h, char *username,
						  Sic_entry **existing_sic)
{
  int st;
  Sic_entry *sic;

  log_msg((LOG_TRACE_LEVEL, "Deleting entry, username=\"%s\"", username));

  if (existing_sic != NULL)
	*existing_sic = NULL;

  sic = NULL;
  if ((st = sic_exists_username_entry(h, username)) == 1) {
	if (existing_sic != NULL
		&& (sic = sic_read(h, NULL, username)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Entry exists but cannot read it, username=\"%s\"", username));
	  return(-1);
	}

	if (vfs_delete(h, sic_make_key_from_username(username)) == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Cannot delete entry, username=\"%s\"", username));
	  return(-1);
	}

	log_msg((LOG_DEBUG_LEVEL, "Entry deleted, username=\"%s\"", username));
	if (existing_sic != NULL)
	  *existing_sic = sic;

	return(1);
  }
  else if (st == 0) {
	log_msg((LOG_DEBUG_LEVEL, "No entry exists, username=\"%s\"", username));
	return(0);
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "VFS error, cannot read, username=\"%s\"",
			 username));
	return(-1);
  }
}

/*
 * Delete both records associated with the account described by the hash
 * string and/or the username.  It is possible for a different hash string to
 * be computed from an InfoCard (if the hash algorithm has been reconfigured,
 * for instance), in which case the account's username must be used to locate
 * the account entry, and from that the correct (previous) hash string can be
 * found.
 */
static int
sic_delete_entry(Vfs_handle *h, char *hashstr, char *username)
{
  Sic_entry *existing_hashstr_sic, *existing_username_sic;

  existing_username_sic = NULL;
  existing_hashstr_sic = NULL;

  /* Try to delete both records. */
  if (username != NULL)
	sic_delete_username_entry(h, username, &existing_username_sic);

  if (hashstr != NULL)
	sic_delete_hashstr_entry(h, hashstr, &existing_hashstr_sic);

  /*
   * If the digest associated with the username-keyed record just deleted
   * is different from HASHSTR, or HASHSTR is not known, try to delete
   * the hash-keyed record.
   */
  if (existing_username_sic != NULL
	  && (hashstr == NULL
		  || !streq(existing_username_sic->digest, hashstr)))
	sic_delete_hashstr_entry(h, existing_username_sic->digest, NULL);

  if (existing_hashstr_sic != NULL
	  && (username == NULL
		  || !streq(existing_hashstr_sic->username, username)))
	sic_delete_username_entry(h, existing_hashstr_sic->username, NULL);

  return(0);
}

/*
 * Create a new entry, representing a new self-issued InfoCard-based account,
 * or update an existing entry.  We need to be able to look up the account
 * entry using either an InfoCard (specifically, a hash of various fields
 * found in a SAML token) or the username that is associated with the InfoCard.
 * Associating an InfoCard with a username is called "registering the
 * InfoCard" or "creating an InfoCard account", and this is required before
 * a self-issued InfoCard can be used for DACS authentication
 * (via local_infocard_authenticate).
 *
 * At present, to register a self-issued InfoCard a user submits DACS
 * credentials (representing an identity belonging to the jurisdiction where the
 * registration is taking place) and his InfoCard (again, meaning the
 * SAML token supplied by his Identity Selector).  If registration succeeds,
 * the username in the credentials becomes associated with the InfoCard,
 * and subsequently the InfoCard can be used for DACS authentication.
 *
 * At authentication time, we need to be able to look up the previously
 * registered username (stored with the account record) given an InfoCard.
 * For system administration, we need to be able to look up the account
 * given the registered username.
 *
 * Registration Cases:
 *   1. Lookup of username fails:
 *      1a. Lookup of hash succeeds: assume a new username is being
 *          associated with the InfoCard; delete old entry, add entry with
 *          new username
 *      1b. Lookup of hash fails: assume this is a new account; create an
 *          account for username (if a new username is being associated with
 *          the InfoCard AND the hash algorithm has been reconfigured,
 *          this case is possible - if we search and find an entry with the PPID
 *          we can treat it like 1a)
 *
 *   2. Lookup of username succeeds:
 *      2a. if the entry's hash value has changed, delete old entry, add updated
 *          entry (the PPID, exponent, modulus, or hash algorithm have changed)
 *      2b. if the entry's hash has not changed, do nothing.
 *
 * TOKEN is a parsed and validated InfoCard.
 * The hash algorithm to use is ALG, the initial state for the account is
 * NEW_STATE, and DATA is optional opaque (private) data.
 */
static int
sic_register_entry(Vfs_handle *h, Ic_token *token, char *hashstr,
				  Digest_desc *desc, char *sic_username,
				  Ic_state new_state, Ds *data)
{
  int is_disabled;
  long enc_entry_len;
  char *enc_entry;
  Ds *ds, *fid;
  Sic_entry sic, *existing_sic;

  if (token == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No token, cannot register InfoCard"));
	return(-1);
  }

  if (sic_username == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No username, cannot register InfoCard"));
	return(-1);
  }

  /*
   * This may be a completely new registration (no record exists for the
   * username and no record exists for the InfoCard), or an update to an
   * existing account entry (a change to a username-to-InfoCard mapping).
   * For the latter, there are two cases: the username (identity) is the
   * same but the InfoCard has changed, or the InfoCard is the same but the
   * username has changed.
   *
   * Note that we implicitly assume that there is a one-to-one mapping between
   * hash values and accounts, so the hash function better be good.
   *
   * If a previously registered account exists for this username,
   * delete it before processing the new registration:
   *   InfoCard-1 --> (hash(PPID+xxx), bob) and (bob, has(PPID+xxx))
   * then:
   *   InfoCard-2 --> (hash(PPID+yyy), bob) and (bob, hash(PPID+yyy))
   * delete entry for InfoCard-1 so that two different InfoCards do not point
   * to the same identity.
   */
  log_msg((LOG_DEBUG_LEVEL, "Registering (or re-registering) InfoCard"));
  log_msg((LOG_DEBUG_LEVEL, "username=\"%s\", hashstr=\"%s\"",
		   sic_username, hashstr));
  fid = icx_friendly_identifier(token->ppid, 1);
  log_msg((LOG_DEBUG_LEVEL, "friendly identifier=\"%s\"", ds_buf(fid)));

  existing_sic = NULL;
  if (sic_delete_entry(h, hashstr, sic_username) == -1) {
	log_msg((LOG_ERROR_LEVEL,
			 "Cannot delete existing entry, hashstr=\"%s\", username=\"%s\"",
			 hashstr, sic_username));
	log_msg((LOG_ERROR_LEVEL, "Cannot register InfoCard"));
	return(-1);
  }
  else
	log_msg((LOG_DEBUG_LEVEL, "Deleted existing entry for username=\"%s\"",
			 sic_username));

  /* XXX assume for now that the hash algorithm has not been changed. */

  sic_init_entry(&sic, hashstr, sic_username, token);
  if (new_state == IC_DISABLED) {
	sic.state = IC_DISABLED;
	is_disabled = 1;
  }
  else {
	sic.state = IC_ENABLED;
	is_disabled = 0;
  }

  sic.desc = desc;
  sic.private = data;

  ds = sic_format_xml_entry(&sic);
  enc_entry_len = mime_encode_base64(ds_ucbuf(ds), ds_len(ds), &enc_entry);

  /*
   * Write two records, one keyed by the hash value and the other keyed
   * by the username.  This avoids a sequential search in case either of the
   * two keys becomes invalid.
   */
  if (vfs_put(h, sic_make_key_from_hashstr(hashstr), enc_entry,
			  enc_entry_len + 1) == -1)
	return(-1);

  if (vfs_put(h, sic_make_key_from_username(sic_username), enc_entry,
			  enc_entry_len + 1) == -1)
	return(-1);

  log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
		   "DACS InfoCard %s registered for \"%s\"%s",
		   ds_buf(fid), sic_username, is_disabled ? " (disabled)" : ""));

  return(0);
}

static MAYBE_UNUSED int
ic_replace_entry(Vfs_handle *h, char *username, char *given_passwd,
				 Passwd_digest_alg alg, Pw_state new_state,
				 Pw_op data_op, Ds *data)
{

  return(-1);
}

/*
 * Modify an existing entry.
 */
static int
sic_modify_entry(Vfs_handle *h, char *username, int ops, Ds *data)
{
  int st;
  long enc_entry_len;
  char *enc_entry;
  Ds *ds;
  Sic_entry *sic;

  if ((sic = sic_read(h, NULL, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(-1);
  }

  if (ops & IC_OP_DISABLE) {
	if (sic->state == IC_DISABLED) {
	  log_msg((LOG_NOTICE_LEVEL, "Attempt to disable a disabled entry"));
	  return(0);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Disabling DACS account for \"%s\"", username));

	sic->state = IC_DISABLED;
  }
  else if (ops & IC_OP_ENABLE) {
	if (sic->state == IC_ENABLED) {
	  log_msg((LOG_NOTICE_LEVEL, "Attempt to enable an enabled entry"));
	  return(0);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Enabling DACS account for \"%s\"", username));

	sic->state = IC_ENABLED;
  }

  if (ops & IC_OP_SET_DATA) {
	if (data == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No data provided to set-data operation"));
	  return(-1);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Setting data for DACS account \"%s\"", username));


	sic->private = data;
  }
  else if (ops & IC_OP_DELETE_DATA) {
	if (sic->private != NULL)
	  ds_free(sic->private);
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Deleting data for DACS account \"%s\"", username));

	sic->private = NULL;
  }

  ds = sic_format_xml_entry(sic);
  enc_entry_len = mime_encode_base64(ds_ucbuf(ds), ds_len(ds), &enc_entry);
  st = vfs_put(h, sic_make_key_from_username(username), enc_entry,
			   enc_entry_len + 1);

  return(st);
}

static int
ic_disable_entry(Vfs_handle *h, char *username)
{
  int st;

  st = sic_modify_entry(h, username, IC_OP_DISABLE, NULL);

  return(st);
}

static int
ic_enable_entry(Vfs_handle *h, char *username)
{
  int st;

  st = sic_modify_entry(h, username, IC_OP_ENABLE, NULL);

  return(st);
}

static void
list_qsort(void *base, size_t nmemb, size_t size,
		   int (*compar)(const void *, const void *))
{
  void *b;
  Dsvec *dsv;

  dsv = (Dsvec *) base;
  b = (void *) dsvec_base(dsv);

  qsort(b, nmemb, size, compar);
}

static int
list_add(char *naming_context, char *name, void ***names)
{
  char *p, *s;
  Dsvec *dsv;

  dsv = (Dsvec *) names;

  if (naming_context != NULL && (p = strprefix(name, naming_context)) != NULL)
    s = p + 1;
  else
    s = name;

  if (strprefix(s, SIC_USERNAME_PREFIX) != NULL) {
	dsvec_add_ptr(dsv, s);
	return(1);
  }

  if (strprefix(s, MIC_IDENTITY_PREFIX) != NULL) {
	dsvec_add_ptr(dsv, s);
	return(1);
  }

  return(0);
}

static int
list_compar(const void *a, const void *b)
{
  char **p, **q;

  p = (char **) a;
  q = (char **) b;

  return(strcmp(*p, *q));
}

static Dsvec *
ic_list_entries(Vfs_handle *h)
{
  int n;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(char *));
  h->list_sort = list_qsort;
  if ((n = vfs_list(h, NULL, list_compar, list_add, (void ***) dsv)) == -1)
	return(NULL);

  return(dsv);
}

static int
list_entries_text(FILE *fp, Vfs_handle *h, Dsvec *dsv)
{
  int i;
  char *key;
  Mic_entry *mic;
  Sic_entry *sic;

  for (i = 0; i < dsvec_len(dsv); i++) {
	key = (char *) dsvec_ptr_index(dsv, i);
	
	mic = NULL;
	sic = NULL;
	if (is_sic_entry(key)) {
	  if ((sic = sic_read_entry(h, key)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "List of key \"%s\" failed", key));
		return(-1);
	  }

	  fprintf(fp, "%s self-issued,%s,%s\n", sic->username,
			  (sic->state == IC_DISABLED) ? "disabled" : "enabled",
			  ds_buf(sic->friendly_id));
	}
	else {
	  char *authtype_str;

	  if ((mic = mic_read_entry(h, key)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "List of key \"%s\" failed", key));
		return(-1);
	  }

	  ic_lookup_sts_authtype_str(mic->sts_auth->authtype, &authtype_str, NULL);

	  fprintf(fp, "%s managed,%s,%s,%s\n",
			  mic->identity,
			  mic_lookup_use_mode_str(mic->use_mode),
			  (mic->state == IC_DISABLED) ? "disabled" : "enabled",
			  authtype_str);
	}
  }

  return(1);
}

static int
ic_compar(const void *ap, const void *bp)
{
  int st;
  char *a_name, *b_name;
  Ic_entry *a, *b;

  a = *(Ic_entry **) ap;
  b = *(Ic_entry **) bp;

  if (a->sic != NULL)
	a_name = auth_identity_mine(a->sic->username);
  else
	a_name = a->mic->identity;

  if (b->sic != NULL)
	b_name = auth_identity_mine(b->sic->username);
  else
	b_name = b->mic->identity;

  if ((st = strcmp(a_name, b_name)) != 0)
	return(st);

  if (a->mic != NULL)
	return(-1);
  return(1);
}

static int
list_entries_html(FILE *fp, Vfs_handle *h, Dsvec *dsv)
{
  int i;
  char *key;
  Dsvec *attrs, *dsv_ic;
  Html_header_conf *hc;
  Html_table *tab;
  Ic_entry *ic;
  Mic_entry *mic;
  Sic_entry *sic;

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

  tab = html_table(NULL, NULL);
  tab->row_class = "tr";
  tab->auto_row_nclasses = 2;
  tab->auto_column_class = "td";
  attrs = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(attrs, "border");
  dsvec_add_ptr(attrs, "width=\"100%\"");
  html_table_begin(tab, attrs, 6);

  tab->in_header = 1;
  html_row_begin(tab);
  html_cellf(tab, "Identity of Owner");
  html_cellf(tab, "InfoCard Type");
  html_cellf(tab, "Mode");
  html_cellf(tab, "Status");
  html_cellf(tab, "Auth?");
  html_cellf(tab, "STS Auth Method");
  html_row_end(tab);
  tab->in_header = 0;

  /*
   * Sorting the list by identity is nice for display purposes.
   */
  dsv_ic = dsvec_init(NULL, sizeof(Ic_entry *));
  for (i = 0; i < dsvec_len(dsv); i++) {
	key = (char *) dsvec_ptr_index(dsv, i);
	
	if (is_sic_entry(key)) {
	  if ((sic = sic_read_entry(h, key)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "List of key \"%s\" failed", key));
		return(-1);
	  }
	  ic = ALLOC(Ic_entry);
	  ic->sic = sic;
	  ic->mic = NULL;
	  dsvec_add_ptr(dsv_ic, ic);
	}
	else {
	  if ((mic = mic_read_entry(h, key)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "List of key \"%s\" failed", key));
		return(-1);
	  }
	  ic = ALLOC(Ic_entry);
	  ic->sic = NULL;
	  ic->mic = mic;
	  dsvec_add_ptr(dsv_ic, ic);
	}
  }

  dsvec_sort(dsv_ic, ic_compar);

  for (i = 0; i < dsvec_len(dsv_ic); i++) {
	ic = (Ic_entry *) dsvec_ptr_index(dsv_ic, i);
	
	html_row_begin(tab);
	if ((sic = ic->sic) != NULL) {
	  html_cell(tab, auth_identity_mine(sic->username));
	  html_cell(tab, "Self-issued");
	  html_cell(tab, "--");
	  html_cellf(tab, "%s",
				 (sic->state == IC_DISABLED) ? "disabled" : "enabled");
	  html_cell(tab, "Yes");
	  html_cell(tab, "--");
	}
	else {
	  char *authtype_str;

	  mic = ic->mic;
	  html_cell(tab, mic->identity);
	  html_cell(tab, "Managed");
	  ic_lookup_sts_authtype_str(mic->sts_auth->authtype, &authtype_str, NULL);
	  html_cellf(tab, "%s", mic_lookup_use_mode_str(mic->use_mode));
	  html_cellf(tab, "%s",
				 (mic->state == IC_DISABLED) ? "disabled" : "enabled");
	  html_cellf(tab, "%s", (icx_lookup_claim(mic->claims, ICX_DACS_CLAIM_URI,
											  "dacs_identity") == NULL)
				 ? "No" : "Yes");
	  html_cellf(tab, "%s", authtype_str);
	}
	html_row_end(tab);
  }

  html_table_end(tab);
  fprintf(fp, "%s", ds_buf(tab->ds));
  html_table_free(tab);
  emit_html_trailer(fp);

  return(1);
}

static int
list_entries_xml(FILE *fp, Vfs_handle *h, Dsvec *dsv)
{

  return(-1);
}

/*
 * Display information about a given account (USERNAME) or all accounts
 * (if USERNAME is NULL).
 */
static int
list_entries(Vfs_handle *h, char *username, char *identity)
{
  int st;
  char *key;
  Dsvec *dsv;

  if (username != NULL) {
	key = sic_make_key_from_username(username);
	dsv = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(dsv, key);
  }
  else if (identity != NULL) {
	key = mic_make_key_from_identity(identity);
	dsv = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(dsv, key);
  }
  else {
	if ((dsv = ic_list_entries(h)) == NULL)
	  return(-1);
  }

  if (test_emit_format(EMIT_FORMAT_HTML))
	st = list_entries_html(stdout, h, dsv);
  else if (test_emit_format(EMIT_FORMAT_XML))
	st = list_entries_xml(stdout, h, dsv);
  else
	st = list_entries_text(stdout, h, dsv);

  return(st);
}

/*
 * Return the value of claim ATTRNAME in TOKEN, or NULL.
 * ATTRNAME is either a "built-in" pseudo claim name (without a namespace),
 * a URI claim, or a DACS shorthand claim name with the format:
 * {standard,dacs}:<claim-name>
 */
static char *
get_claim_value(Ic_token *token, char *attrname, Ic_claim_parse *cp)
{
  char *an, *attrval;

  log_msg((LOG_TRACE_LEVEL, "Looking for claim \"%s\"", attrname));

  if ((an = expand_dacs_claim_name(attrname, cp)) == NULL)
	return(NULL);

  attrval = NULL;
  if (cp->ns_uri == NULL) {
	if (streq(attrname, "issuer"))
	  attrval = token->issuer;
	else if (streq(attrname, "confirm_method")) {
	  if (token->confirm == IC_CONFIRM_HOLDER)
		attrval = "holder";
	  else if (token->confirm == IC_CONFIRM_BEARER)
		attrval = "bearer";
	  else
		attrval = NULL;
	}
	else if (streq(attrname, "ppid")
			 || streq(attrname, "privatepersonalidentifier")) {
	  Ds *ds;

	  if ((ds = icx_friendly_identifier(token->ppid, 1)) != NULL)
		attrval = ds_buf(ds);
	}
	else if (streq(attrname, "exponent"))
	  attrval = ds_buf(token->exponent);
	else if (streq(attrname, "modulus"))
	  attrval = ds_buf(token->modulus);
  }
  else {
	/* It's not a pseudo name; try the shorthand or full URI syntax... */
	if (cp->type == ICX_STANDARD_CLAIM
		&& (streq(cp->name, "ppid") ||
			streq(cp->name, "privatepersonalidentifier"))) {
	  Ds *ds;

	  if ((ds = icx_friendly_identifier(token->ppid, 1)) != NULL)
		attrval = ds_buf(ds);
	}
	else
	  attrval = kwv_lookup_value(token->kwv_claims, an);
  }

  if (attrval != NULL)
	log_msg((LOG_TRACE_LEVEL, "Claim value is \"%s\"", attrval));
  else
	log_msg((LOG_TRACE_LEVEL, "Claim not found"));

  return(attrval);
}

static void
format_claim(Html_table *tab, Ds *ds, Ic_claim_parse *cp, char *claim_val)
{

  if (tab != NULL) {
	html_cellf(tab, "<span class=\"claim_uri\">%s/</span><span class=\"claim_name\">%s</span>", html_encode(cp->ns_uri), html_encode(cp->name));
	html_cell(tab, cp->ns_abbrev);
	html_cell(tab, html_encode(claim_val));
  }
  else
	ds_asprintf(ds, "%s/%s=%s\n", cp->ns_uri, cp->name, claim_val);
}

/*
 * ATTRLIST is a space-separated list of claims to be displayed,
 * or NULL if all claims are to be displayed.
 */
static int
format_selected_claims(Ic_token *token, char *attrlist, char **msg)
{
  int i, n;
  char *attrname, *attrval;
  Ds *ds;
  Dsvec *dsv;
  Html_header_conf *hc;
  Html_table *tab;

  ds = ds_init(NULL);
  if (test_emit_format(EMIT_FORMAT_HTML)) {
	hc = emit_html_header_conf(NULL);
	hc->no_cache = 1;
	hc->title = "InfoCard Claims";

	if (conf_val(CONF_CSS_PATH) != NULL)
	  hc->css = ds_xprintf("%s/dacs_infocard.css",
						   conf_val(CONF_CSS_PATH));
	else
	  hc->css = CSS_DIR/**/"/dacs_infocard.css";

	emit_html_header(stdout, hc);

	ds_asprintf(ds, "<h1>InfoCard Claims</h1><p>\n");
	tab = html_table(ds, NULL);
	tab->row_class = "tr";
	tab->auto_row_nclasses = 3;
	tab->auto_column_class = "td";
  }
  else if (test_emit_format(EMIT_FORMAT_XML)) {
	/*
	emit_xml_header(stdout, "dacs_infocard");
	*/
	tab = NULL;
  }
  else
	tab = NULL;

  if (attrlist == NULL) {
	Kwv_iter *iter;
	Kwv_pair *pair;

	/* Select all claims. */
	iter = kwv_iter_begin(token->kwv_claims, NULL);
	dsv = dsvec_init(NULL, sizeof(char *));
	pair = kwv_iter_first(iter);
	while (pair != NULL) {
	  attrname = pair->name;
	  dsvec_add_ptr(dsv, attrname);
	  pair = kwv_iter_next(iter);
	}
	kwv_iter_end(iter);
  }
  else {
	/*
	 * Extract the individual claims, which are separated by one or more
	 * spaces.
	 */
	if ((dsv = strsplit(attrlist, " ", 0)) == NULL)
	  return(-1);
  }

  n = 0;
  for (i = 0; i < dsvec_len(dsv); i++) {
	Ic_claim_parse cp;

	attrname = (char *) dsvec_ptr_index(dsv, i);

	attrval = get_claim_value(token, attrname, &cp);
	if (attrval != NULL) {
	  if (test_emit_format(EMIT_FORMAT_HTML) && n == 0) {
		Dsvec *attrs;

		attrs = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(attrs, "class=\"claim_table\"");
		dsvec_add_ptr(attrs, "border");
		dsvec_add_ptr(attrs, "width=\"100%\"");
		html_table_begin(tab, attrs, 3);
		tab->in_header = 1;
		html_cells(tab, 3, "Claim URI", "Abbrev. Namespace", "Value");
		tab->in_header = 0;
	  }
	  format_claim(tab, ds, &cp, attrval);
	  n++;
	}
  }

  if (test_emit_format(EMIT_FORMAT_HTML)) {
	if (n)
	  html_table_end(tab);
	ds_asprintf(ds, "</p>\n");
  }

  *msg = ds_buf(ds);

  return(0);
}

/*
 * Perform test(s) OP on the entry for USERNAME.
 */
static MAYBE_UNUSED int
sic_test_entry(Vfs_handle *h, Ic_op op, char *username)
{
  int exists;
  Sic_entry *sic;

  if ((exists = sic_exists_username_entry(h, username)) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(-1);
  }

  switch (op) {
  case IC_OP_TEST_EXISTS:
	return(exists);
	/*NOTREACHED*/

  case IC_OP_TEST_DISABLED:
	if (exists && (sic = sic_read(h, NULL, username)) == NULL)
	  return(-1);
	if (!exists || sic->state != IC_DISABLED)
	  return(0);
	return(1);
	/*NOTREACHED*/

  case IC_OP_TEST_ENABLED:
	if (exists && (sic = sic_read(h, NULL, username)) == NULL)
	  return(-1);
	if (!exists || sic->state != IC_ENABLED)
	  return(0);
	return(1);
	/*NOTREACHED*/

  case IC_OP_TEST_PRIVATE:
	if (exists && (sic = sic_read(h, NULL, username)) == NULL)
	  return(-1);
	if (!exists || sic->private == NULL || ds_len(sic->private) == 0)
	  return(0);
	return(1);
	/*NOTREACHED*/

  default:
	log_msg((LOG_ERROR_LEVEL, "Urecognized test operation"));
	return(-1);
	/*NOTREACHED*/
  }

  /*NOTREACHED*/
  return(-1);
}

static void
dacs_usage(void)
{

  fprintf(stderr, "Usage: dacsinfocard %s [op] [flags] [--] username\n",
		  standard_command_line_usage);
  fprintf(stderr, "Where op is:\n");
  fprintf(stderr, "-a|-add|-r|-reg|-register: add a new entry (default)\n");
  fprintf(stderr, "-d|-del|-delete: delete an entry\n");
  fprintf(stderr, "-l|-list:      list all entries\n");
  fprintf(stderr, "-dis|-disable: disable an entry\n");
  fprintf(stderr, "-en|-ena|-enable: enable an entry\n");
  fprintf(stderr, "-t token :     specify token t (possibly insecure)\n");
  fprintf(stderr, "-tf file:      read token from file\n");
  fprintf(stderr, "-pdd:          delete account private data\n");
  fprintf(stderr, "-pdg:          get account private data\n");
  fprintf(stderr, "-pds data:     set account private data\n");
  fprintf(stderr, "-pdsf file:    set account private data from file\n");
  fprintf(stderr, "-emit fname:   print the token's value of field fname\n");
  fprintf(stderr, "-get fname fvalue: look for an entry with token field fname having value fvalue\n");
  fprintf(stderr, "-vfs vfs_uri:  add a VFS definition\n");

  exit(1);
}

static int
check_username(char *username)
{

  if (!is_valid_auth_username(username)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid username: \"%s\"", username));
	return(-1);
  }

  /*
   * We don't want "Bob" and "bob" because that doesn't work well
   * with the NAME_COMPARE directive.
   */
  if (!streq(username, strtolower(username))) {
	log_msg((LOG_ERROR_LEVEL, "Username must be lowercase: \"%s\"", username));
	return(-1);
  }

  return(0);
}

/*
 * dacsinfocard(1) - command-line interface
 */
static int
dacsinfocard_command(int argc, char **argv)
{
  int do_disable, do_emit, do_enable, do_test, i, no_stdin, st;
  char *certfile, *claim, *errmsg, *keyfile;
  char *passwd, *token_file, *username;
  Crypt_keys *keys;
  Ds *data;
  Ic_config *conf;
  Ic_op op, op_data;
  Icx_ctx *ctx;
  Vfs_handle *h;

  errmsg = "Internal error";
  h = NULL;
  claim = NULL;
  certfile = keyfile = NULL;
  passwd = NULL;
  data = NULL;
  op_data = IC_OP_NONE;
  do_disable = 0;
  do_emit = 0;
  do_enable = 0;
  do_test = 0;
  no_stdin = 0;
  token_file = NULL;

  /* This is solely to ensure that the user is an administrator. */
  if ((keys = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL) {
	errmsg = "Permission denied";
	goto fail;
  }
  crypt_keys_free(keys);
	  
  op = IC_OP_NONE;
  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-tf")) {
	  if (argv[++i] == NULL) {
		errmsg = "Name of token file required after -tf flag";
		goto fail;
	  }

	  token_file = argv[i];
	}
	else if (streq(argv[i], "-emit")) {
	  if (argv[++i] == NULL) {
		errmsg = "Token field name required after -emit flag";
		goto fail;
	  }
	  do_emit = 1;
	  claim = argv[i];
	}
	else if (streq(argv[i], "-kf")) {
	  if (argv[++i] == NULL) {
		errmsg = "Key file path expected after -kf flag";
		goto fail;
	  }
	  keyfile = argv[i];
	}
	else if (streq(argv[i], "-cf")) {
	  if (argv[++i] == NULL) {
		errmsg = "Certificate file path expected after -cf flag";
		goto fail;
	  }
	  certfile = argv[i];
	}
	else if (streq(argv[i], "-l") || streq(argv[i], "-list")) {
	  if (op != IC_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = IC_OP_LIST;
	}
	else if (streq(argv[i], "--")) {
	  /* End of flag arguments */
	  i++;
	  break;
	}
	else if (argv[i][0] == '-') {
	  errmsg = NULL;
	  goto fail;
	}
	else
	  break;
  }

  conf = ic_config();
  conf->certfile = certfile;
  conf->keyfile = keyfile;
  ctx = ic_init(conf);
  /* XXX */
  icx_set_config_audience(ctx);

  if (do_emit) {
	char *val, *xmlToken;
	Ds *ds;
	Ic_token *token;

	if (token_file == NULL) {
	  errmsg = "A token must be specified";
	  goto fail;
	}

	if ((ds = ds_load_file(NULL, token_file)) == NULL) {
	  errmsg = "Cannot load token file";
	  goto fail;
	}

	xmlToken = ds_buf(ds);
	if ((token = ic_parse_token(ctx, xmlToken)) == NULL) {
	  errmsg = "Error parsing xmlToken";
	  goto fail;
	}

	if (strcaseeq(claim, "PPID"))
	  val = ds_buf(token->ppid);
	else if (strcaseeq(claim, "exponent"))
	  val = ds_buf(token->exponent);
	else if (strcaseeq(claim, "modulus"))
	  val = ds_buf(token->modulus);
	else {
	  char *c;

	  kwv_set_mode(token->kwv_claims, "+i");
	  if ((c = expand_dacs_claim_name(claim, NULL)) == NULL)
		val = NULL;
	  else
		val = kwv_lookup_value(token->kwv_claims, c);
	}

	if (val == NULL)
	  exit(1);

	printf("%s\n", val);

	exit(0);
  }

  username = NULL;
  if (op == IC_OP_LIST) {
	if (i == argc)
	  username = NULL;
	else if (i == (argc - 1))
	  username = argv[argc - 1];
	else {
	  errmsg = NULL;
	  goto fail;
	}
  }
  else {
	if (i != (argc - 1)) {
	  errmsg = NULL;
	  goto fail;
	}
	username = argv[argc - 1];
  }

  if (username != NULL && check_username(username) == -1) {
	errmsg = "Invalid USERNAME argument";
	goto fail;
  }

  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }

  st = 0;
  if (op == IC_OP_LIST) {
	if (list_entries(h, username, NULL) == -1) {
	  errmsg = "Error listing entries";
	  goto fail;
	}
  }

  if (vfs_close(h) == -1) {
	h = NULL;
	errmsg = "vfs_close() failed";
	goto fail;
  }

  return(st);

 fail:
  if (h != NULL) {
	if (h->error_msg != NULL)
	  fprintf(stderr, "dacsinfocard: %s\n", h->error_msg);
	vfs_close(h);
  }
  if (errmsg != NULL)
	fprintf(stderr, "dacsinfocard: %s\n", errmsg);
  dacs_usage();
  /*NOTREACHED*/

  return(-1);



#ifdef NOTDEF
  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-r") || streq(argv[i], "-reg")) {
	  if (op != IC_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = IC_OP_REGISTER;
	}
	else if (streq(argv[i], "-d") || streq(argv[i], "-del")
			 || streq(argv[i], "-delete")) {
	  if (op != IC_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = IC_OP_DELETE;
	}
	else if (streq(argv[i], "-dis") || streq(argv[i], "-disable"))
	  do_disable = 1;
	else if (streq(argv[i], "-en") || streq(argv[i], "-ena")
			 || streq(argv[i], "-enable"))
	  do_enable = 1;
	else if (streq(argv[i], "-g") || streq(argv[i], "-get")) {
	  if (op != IC_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = IC_OP_GET_DIGEST;
	}
	else if (streq(argv[i], "-pds")) {
	  if (op_data != IC_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Private data required after -sd flag";
		goto fail;
	  }

	  op_data = IC_OP_SET_DATA;
	  data = ds_set(NULL, argv[i]);
	}
	else if (streq(argv[i], "-pdd")) {
	  if (op_data != IC_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }

	  op_data = IC_OP_DELETE_DATA;
	}
	else if (streq(argv[i], "-pdg")) {
	  if (op_data != IC_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }

	  op = op_data = IC_OP_GET_DATA;
	}
	else if (streq(argv[i], "-pdsf")) {
	  if (op_data != IC_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Private data file is missing";
		goto fail;
	  }

	  if (streq(argv[i], "-")) {
		if (no_stdin) {
		  errmsg = "The standard input has already been read";
		  goto fail;
		}
		no_stdin = 1;
		data = ds_load_file(NULL, NULL);
	  }
	  else
		data = ds_load_file(NULL, argv[i]);
	  if (data == NULL) {
		errmsg = ds_xprintf("Error reading private data from \"%s\"\n",
							argv[i]);
		goto fail;
	  }
	  op_data = IC_OP_SET_DATA;
	}
	else if (streq(argv[i], "-p")) {
	  if (passwd != NULL) {
		errmsg = "Multiple password specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Password required after -p flag";
		goto fail;
	  }

	  passwd = strdup(argv[i]);
	  strzap(argv[i]);
	}
	else if (streq(argv[i], "-pf")) {
	  if (argv[++i] == NULL) {
		errmsg = "Password file is missing";
		goto fail;
	  }

	  if (streq(argv[i], "-")) {
		if (no_stdin) {
		  errmsg = "The standard input has already been read";
		  goto fail;
		}
		no_stdin = 1;
		passwd = get_passwd(GET_PASSWD_STDIN, NULL);
	  }
	  else
		passwd = get_passwd(GET_PASSWD_FILE, argv[i]);
	  if (passwd == NULL) {
		errmsg = ds_xprintf("Error reading password from \"%s\"\n", argv[i]);
		goto fail;
	  }
	}
	else if (streq(argv[i], "-test")) {
	  if (argv[++i] == NULL) {
		errmsg = "Test type is missing";
		goto fail;
	  }

	  if (op != IC_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }

	  if (do_test) {
		errmsg = "Only one test is allowed";
		goto fail;
	  }

	  do_test = 1;
	  if (streq(argv[i], "exists") || streq(argv[i], "ex"))
		op = IC_OP_TEST_EXISTS;
	  else if (streq(argv[i], "disabled") || streq(argv[i], "dis"))
		op = IC_OP_TEST_DISABLED;
	  else if (streq(argv[i], "enabled") || streq(argv[i], "ena")
			   || streq(argv[i], "en"))
		op = IC_OP_TEST_ENABLED;
	  else if (streq(argv[i], "data"))
		op = IC_OP_TEST_PRIVATE;
	  else {
		errmsg = "Unrecognized test type";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-vfs")) {
	  Vfs_directive *vd;

	  if (argv[++i] == NULL) {
		errmsg = "VFS argument is missing";
		goto fail;
	  }

	  /* A VFS spec */
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		errmsg = "Invalid vfs_uri";
		goto fail;
	  }
	  add_vfs_uri(var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf"), argv[i]);
	}
	else if (streq(argv[i], "--")) {
	  /* End of flag arguments */
	  i++;
	  break;
	}
	else if (argv[i][0] == '-') {
	  errmsg = NULL;
	  goto fail;
	}
	else
	  break;
  }

  if (do_disable && do_enable) {
	errmsg = "-disable and -enable are mutually exclusive";
	goto fail;
  }

  /*
   * If no "exclusive" operation was requested, but either enable/disable
   * and/or one of the data set/delete operations is specified, then the reset
   * operation is implied.
   * Failing that, the add operation is the default.
   */
  if (op == IC_OP_NONE) {
	if (do_disable)
	  op |= IC_OP_DISABLE | IC_OP_MODIFY;
	else if (do_enable)
	  op |= IC_OP_ENABLE | IC_OP_MODIFY;

	if (op_data == IC_OP_SET_DATA || op_data == IC_OP_DELETE_DATA)
	  op |= op_data | IC_OP_MODIFY;

	if (op == IC_OP_NONE)
	  op = IC_OP_REGISTER;
  }

  /* Check for improper combinations of disable/enable */
  if ((do_disable || do_enable)
	  && (op == IC_OP_LIST || op == IC_OP_GET_DATA || do_test)) {
	errmsg = "Account enable/disable requires add, set, or update";
	goto fail;
  }

  /* Check for improper combinations of a data operation */
  if (op_data != IC_OP_NONE && op_data != IC_OP_GET_DATA) {
	if (op == IC_OP_LIST || do_test
		|| (op_data == IC_OP_DELETE_DATA)) {
	  errmsg = "Account private data operation requires add, set, or update";
	  goto fail;
	}
  }

  username = NULL;
  if (op == IC_OP_LIST) {
	if (i == argc)
	  username = NULL;
	else if (i == (argc - 1))
	  username = argv[argc - 1];
	else {
	  errmsg = NULL;
	  goto fail;
	}
  }
  else {
	if (i != (argc - 1)) {
	  errmsg = NULL;
	  goto fail;
	}
	username = argv[argc - 1];
  }

  if (username != NULL && check_username(username) == -1) {
	errmsg = "Invalid USERNAME argument";
	goto fail;
  }

  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }

  st = 0;
  if (op == IC_OP_LIST) {
	if (list_entries(h, username, NULL) == -1) {
	  errmsg = "Error listing entries";
	  goto fail;
	}
  }
  else if (do_test) {
	if ((st = sic_test_entry(h, op, username)) == -1) {
	  st = 2;
	  errmsg = "Error testing entry";
	  goto fail;
	}
	/*
	 * Reverse the sense of the return value because exit code 0 means "true"
	 * and anything else means "false" or "error".
	 */
	if (st != -1)
	  st = !st;
  }
  else if (op == IC_OP_GET_DATA) {
	char *p;
	Ds *ds;

	if ((ds = sic_getdata_entry(h, username)) == NULL) {
	  errmsg = "Error getting private data";
	  goto fail;
	}

	if ((p = ds_buf(ds)) != NULL && *p != '\0')
	  printf("%s", ds_buf(ds));
  }
  else if (op & IC_OP_GET_DIGEST) {
	char *digest_str;

	if ((digest_str = sic_getdigest_entry(h, username)) == NULL) {
	  st = -1;
	  errmsg = "Error getting digest";
	  goto fail;
	}
	printf("%s\n", digest_str);
  }
  else if (op & IC_OP_MODIFY) {
	if ((st = sic_modify_entry(h, username, op, data)) == -1) {
	  errmsg = "Error modifying entry";
	  goto fail;
	}
  }
  else if (op == IC_OP_DELETE) {
	if ((st = sic_delete_entry(h, NULL, username)) == -1) {
	  errmsg = "Error deleting entry";
	  goto fail;
	}
  }
  else if (op == IC_OP_DISABLE) {
	if ((st = ic_disable_entry(h, username)) == -1) {
	  errmsg = "Error disabling entry";
	  goto fail;
	}
  }
  else if (op == IC_OP_ENABLE) {
	if ((st = ic_enable_entry(h, username)) == -1) {
	  errmsg = "Error enabling entry";
	  goto fail;
	}
  }
  else if (op == IC_OP_UPDATE) {
	char *digest_name;
	Ic_state new_state;

	digest_name = NULL;
	if (ic_get_digest_algorithm(&digest_name, &alg) == -1) {
	  errmsg = "An unrecognized digest method is configured";
	  goto fail;
	}

	if (do_disable)
	  new_state = IC_DISABLED;
	else if (do_enable)
	  new_state = IC_ENABLED;
	else
	  new_state = IC_UNCHANGED;

	st = ic_replace_entry(h, username, passwd, alg, new_state,
						  op_data, data);
	if (st == -1) {
	  errmsg = "Error adding new entry";
	  goto fail;
	}
  }
#endif
}

int
dacsinfocard_main(int argc, char **argv, int do_init, void *main_out)
{
  int st;
  unsigned int ncookies;
  char *attrlist, *enc_xmlToken, *errmsg, *hashstr, *msg, *remote_addr;
  char *p, *xmlToken, *operation, *digest_name;
  Cookie *cookies;
  Credentials *credentials, *selected;
  Ic_token *token;
  Digest_desc desc;
  DACS_app_type app_type;
  Ic_config *conf;
  Ic_op op;
  Kwv *kwv;
  Proc_lock *lock;
  Vfs_handle *h;

  errmsg = NULL;
  h = NULL;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	app_type = DACS_UTILITY;
	log_module_name = "dacsinfocard";
  }
  else {
	app_type = DACS_WEB_SERVICE;
	log_module_name = "dacs_infocard";
  }

  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (h != NULL) {
	  if (h->error_msg != NULL)
		errmsg = strdup(h->error_msg);
	  vfs_close(h);
	  h = NULL;
	}

	if (errmsg == NULL)
	  errmsg = "Internal error";

	log_err((LOG_ERROR_LEVEL, "%s", errmsg));

	if (test_emit_xml_format()) {
	  Common_status status;

	  emit_xml_header(stdout, "dacs_infocard");
      printf("<%s>\n", make_xml_root_element("dacs_infocard"));
      init_common_status(&status, NULL, NULL, errmsg);
      fprintf(stdout, "%s", make_xml_common_status(&status));
      printf("</dacs_infocard>\n");
      emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_HTML)) {
	  emit_html_header_status_line(stdout, "400", errmsg);
	  printf("Request failed: %s\n", errmsg);
	  emit_html_trailer(stdout);
	}
	else {
	  fprintf(stderr, "%s\n", errmsg);
	  dacs_usage();
	  /*NOTREACHED*/
	}

	return(-1);
  }

  if (app_type == DACS_UTILITY && !dacs_saw_command_line_log_level)
	log_set_level(NULL, LOG_WARN_LEVEL);

  /* XXX This course-grained lock prevents concurrent updates. */
  if ((lock = proc_lock_create(PROC_LOCK_INFOCARD)) == NULL) {
    log_msg((LOG_ERROR_LEVEL, "Can't set lock"));
	errmsg = "Can't set lock";
	goto fail;
  }

  /* Here is where the command version splits from the web service version. */
  if (app_type == DACS_UTILITY) {
	if ((st = dacsinfocard_command(argc, argv)) == -1)
	  return(-1);

	return(st);
  }
  
  /*
   * The following is for the web service (dacs_infocard) only.
   */

  operation = kwv_lookup_value(kwv, "OPERATION");
  enc_xmlToken = kwv_lookup_value(kwv, "xmlToken");
  attrlist = kwv_lookup_value(kwv, "ATTRLIST");

#ifdef NOTDEF
  /* IE will happily run CardSpace more than once in one form submission. */
  if (kwv_lookup_value(kwv, "xmlToken2") != NULL)
	log_msg((LOG_DEBUG_LEVEL, "Hey, there is an xmlToken2 argument!"));
#endif

  if (operation == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing OPERATION argument"));
	errmsg = "No OPERATION argument was given";
	goto fail;
  }

  if (strcaseeq(operation, "DELETE"))
	op = IC_OP_DELETE;
  else if (strcaseeq(operation, "DISABLE"))
	op = IC_OP_DISABLE;
  else if (strcaseeq(operation, "ENABLE"))
	op = IC_OP_ENABLE;
  else if (strcaseeq(operation, "LIST"))
	op = IC_OP_LIST;
  else if (strcaseeq(operation, "REGISTER"))
	op = IC_OP_REGISTER;
  else if (strcaseeq(operation, "TOKEN_ATTRVALS"))
	op = IC_OP_TOKEN_ATTRVALS;
  else if (strcaseeq(operation, "TOKEN_VALIDATE"))
	op = IC_OP_TOKEN_VALIDATE;
  else {
	log_msg((LOG_ERROR_LEVEL, "Unrecognized OPERATION argument"));
	errmsg = "Invalid OPERATION argument";
	goto fail;
  }

  if ((op == IC_OP_TOKEN_VALIDATE
	   || op == IC_OP_TOKEN_ATTRVALS
	   || op == IC_OP_REGISTER)
	  && enc_xmlToken == NULL) {
	errmsg = "An xmlToken argument is required";
	goto fail;
  }

  token = NULL;
  hashstr = NULL;
  digest_name = NULL;

  /* If we're given a token, parse and validate it. */
  if (enc_xmlToken != NULL) {
	Icx_ctx *ctx;

	if ((xmlToken = url_decode(enc_xmlToken, NULL, NULL)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Decode of xmlToken failed"));
	  goto fail;
	}

	conf = ic_config();
	/*
	 * Check for configuration of maximum SAML token size and token validity
	 * window size.
	 */
	if ((p = conf_val(CONF_INFOCARD_TOKEN_MAX_LENGTH)) != NULL) {
	  if (strnum(p, STRNUM_SIZE_T, &conf->max_token_size) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid INFOCARD_TOKEN_MAX_LENGTH: %s", p));
		goto fail;
	  }
	}

	if ((p = conf_val(CONF_INFOCARD_TOKEN_DRIFT_SECS)) != NULL) {
	  if (strcaseeq(p, "disable"))
		conf->max_drift_secs = -1;
	  else if (strnum(p, STRNUM_I, &conf->max_drift_secs) == -1
			   || conf->max_drift_secs <= 0) {
		log_msg((LOG_ERROR_LEVEL, "Invalid INFOCARD_TOKEN_DRIFT_SECS: %s", p));
		goto fail;
	  }
	}

	ctx = ic_init(conf);

	if (icx_set_config_audience(ctx) == -1) {
	  errmsg = "Invalid audience configuration";
	  goto fail;
	}

	st = icx_load_certificate(ctx, conf_val(CONF_INFOCARD_STS_CERTFILE));
	if (st == -1) {
	  errmsg = "Could not load INFOCARD_STS_CERTFILE";
	  goto fail;
	}

	/* XXX key key should be an arg or configurable... */
	if (icx_load_key(ctx, conf_val(CONF_INFOCARD_STS_KEYFILE), NULL) == -1) {
	  errmsg = "Could not load INFOCARD_STS_KEYFILE";
	  goto fail;
	}

	icx_set_replay_detection(ctx, NULL, NULL);

	msg = NULL;
	if ((token = ic_parse_token(ctx, xmlToken)) == NULL) {
	  if (op == IC_OP_TOKEN_VALIDATE) {
		msg = "Token is invalid";
		st = 0;
		goto done;
	  }
#ifdef NOTDEF
	  if ((filename = save_buffer_to_temp(xmlToken, 0)) != NULL)
		log_msg((LOG_ERROR_LEVEL, "Saved xmlToken to temp file: %s", filename));
#endif

	  errmsg = "Error parsing xmlToken argument";
	  goto fail;
	}

	if (op == IC_OP_TOKEN_VALIDATE) {
	  msg = "Token is valid";
	  st = 0;
	  goto done;
	}

	if (op == IC_OP_TOKEN_ATTRVALS) {
	  if (attrlist == NULL || *attrlist == '\0')
		st = format_selected_claims(token, NULL, &msg);
	  else
		st = format_selected_claims(token, attrlist, &msg);
	  goto done;
	}

	if (ic_get_digest_algorithm(&digest_name, &desc) == -1) {
	  errmsg = "Configuration error";
		goto fail;
	}

	hashstr = ic_hash(token, digest_name);
	log_msg((LOG_TRACE_LEVEL, "hashstr=\"%s\"", hashstr));
  }

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

  selected = NULL;
  if (get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL) == -1) {
	errmsg = "Selected credentials error";
	goto fail;
  }

#ifdef NOTDEF
  is_admin = is_dacs_admin(selected);

  /*
   * Not yet supported.
   * An admin identity should be able to provide a USERNAME upon which to
   * do some operations (delete, enable, disable, etc.).
   */
  if ((username = kwv_lookup_value(kwv, "USERNAME")) != NULL) {
	if (!is_admin) {
	  errmsg = "Invalid request";
	  goto fail;
	}

	if (check_username(username) == -1) {
	  errmsg = "Invalid USERNAME argument";
	  goto fail;
	}
  }
#endif

  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }

  switch (op) {
	char *ic_username;

  case IC_OP_DELETE:
	st = sic_delete_entry(h, hashstr, NULL);
	if (st == 0)
	  msg = "The InfoCard account has been deleted.";
	break;

  case IC_OP_DISABLE:
	st = ic_disable_entry(h, hashstr);
	if (st == 0)
	  msg = "The InfoCard account has been disabled.";
	break;

  case IC_OP_ENABLE:
	st = ic_enable_entry(h, hashstr);
	if (st == 0)
	  msg = "The InfoCard account has been enabled.";
	break;

  case IC_OP_LIST:
	st = list_entries(h, hashstr, NULL);
	break;

  case IC_OP_REGISTER:
	ic_username = ic_select_username(token, selected);

	st = sic_register_entry(h, token, hashstr, &desc, ic_username,
							IC_ENABLED, NULL);
	if (st == 0)
	  msg = "The self-issued InfoCard has been registered.";
	break;

  default:
	goto fail;
	/*NOTREACHED*/
	break;
  }

  done:

  if (st == -1) {
	if (errmsg == NULL)
	  errmsg = "Operation failed";
	goto fail;
  }

  if (h != NULL && vfs_close(h) == -1) {
	h = NULL;
	errmsg = "vfs_close() failed";
	goto fail;
  }

  if (test_emit_format(EMIT_FORMAT_HTML)) {
	Html_header_conf *hc;

	hc = emit_html_header_conf(NULL);
	hc->no_cache = 1;
	hc->title = "DACS InfoCard";

	if (conf_val(CONF_CSS_PATH) != NULL)
	  hc->css = ds_xprintf("%s/dacs_infocard.css",
						   conf_val(CONF_CSS_PATH));
	else
	  hc->css = CSS_DIR/**/"/dacs_infocard.css";

	emit_html_header(stdout, hc);

	if (op != IC_OP_LIST) {
	  if (msg == NULL)
		printf("\nThe operation succeeded.\n");
	  else
		printf("\n%s\n", msg);
	}
	/* XXX there should probably be configurable success/failure redirection */
	emit_html_trailer(stdout);
  }
  else if (test_emit_format(EMIT_FORMAT_PLAIN)) {
	emit_plain_header(stdout);
	printf("%s", msg);
	emit_plain_trailer(stdout);
  }
  else if (test_emit_xml_format()) {
	Common_status status;

	emit_xml_header(stdout, "dacs_infocard");
	printf("<%s>\n", make_xml_root_element("dacs_infocard"));
	init_common_status(&status, NULL, "0", "Request succeeded");
	printf("%s", make_xml_common_status(&status));
	printf("</dacs_infocard>\n");
	emit_xml_trailer(stdout);
  }

  return(0);
}

#else

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

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

  exit((rc == -1) ? 2 : rc);
}
#endif
