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

/*
 * Create a managed InfoCard
 *
 * Before a managed card can be created for a user at a particular jurisdicion,
 * the user must already be able to authenticate at that jurisdiction.
 *
 * There are three flavours of DACS managed InfoCards corresponding to the
 * three authentication methods that are currently supported between the
 * Identity Selector (IS) and the IP/STS:
 *   1) Username/Password
 *      - the user is prompted by his IS for a username and password
 *        (previously established for the user at the jurisdiction)
 *      - the username/password might be per user, the same as for one of
 *        the user's other accounts, or be fixed or empty
 *
 *   2) X509V3 client certificate
 *      - the user, who has been issued an X509 client certificate by some
 *        unspecified means, sends the cert during login form submission to
 *        the Relying Party (a jurisdiction at which the cert has previously
 *        been registered)
 *      - the client cert must be verified by the jurisdiction's web server,
 *        but everything else about the cert is not significant (e.g., it can
 *        be self-signed)
 *      - the DN in the cert might be registered (so the cert can be replaced
 *        if it expires, for instance, without invalidating it), or a
 *        thumbprint might be registered (meaning that only this exact cert
 *        may be used)
 *     
 *   3) Self-issued InfoCard
 *      - the user has previously created a self-issued InfoCard and
 *        registered it at the jurisdiction where authentication will occur
 *      - the PPID of the card is registered; no other fields need to be
 *        recorded
 *
 * ACLs must be in place to:
 *   1) limit access to this web service to users authenticated in the
 *      same jurisdiction as where this web service is run
 *
 *   2) if a managed InfoCard is generated by this web service, it is written
 *      to a file named <username>_<rand>.crd in the INFOCARD_CARD_OUTPUTDIR and
 *      that file should only be readable by DACS_IDENTITY; each jurisdiction
 *      should probably have a different INFOCARD_CARD_OUTPUTDIR if their
 *      username name spaces are not identical
 */

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

#include "icx.h"

#include <libxml/c14n.h>

static const char *log_module_name = "dacs_managed_infocard";

/*
 * If a client cert was sent with the request, it was verified
 * by the web server (currently assumes mod_ssl), register (or re-register)
 * its thumbprint in the account associated with DACS identity IDENT.
 */
static int
is_client_cert(char *client_verify, char *client_cert,
			   char *client_s_dn, char *client_s_dn_cn, Ds *thumb, char *ident)
{

  if (client_verify != NULL && client_cert != NULL
	  && client_s_dn != NULL && client_s_dn_cn != NULL) {
	if (streq(client_verify, "SUCCESS"))
	  return(1);
  }

  return(0);
}

/*
 * If AUTHTYPE is INFOCARD_AUTHTYPE_PASSWD, the UNAME is the default username.
 */
static int
token_service(Ds *buf, Ic_sts_authtype authtype, char *uname,
			  char *token_serviceurl, char *mex_url, Ds *thumb, Ic_token *token)
{
  char *certfile;
  Ds *cert;

  ds_concat(buf, "<ic:TokenService>");
  ds_concat(buf, "<wsa:EndpointReference xmlns:wsa=|http://www.w3.org/2005/08/addressing|>");
  ds_asprintf(buf, "<wsa:Address>%s</wsa:Address>", token_serviceurl);
  ds_concat(buf, "<wsa:Metadata>");
  ds_concat(buf, "<mex:Metadata xmlns:mex=|http://schemas.xmlsoap.org/ws/2004/09/mex|>");
  ds_concat(buf, "<mex:MetadataSection>");
  ds_concat(buf, "<mex:MetadataReference>");

  if (authtype == INFOCARD_AUTHTYPE_PASSWD)
	ds_asprintf(buf, "<wsa:Address>%s</wsa:Address>", mex_url);
  else if (authtype == INFOCARD_AUTHTYPE_CERT)
	ds_asprintf(buf, "<wsa:Address>%s?AUTHTYPE=cert</wsa:Address>", mex_url);
  else if (authtype == INFOCARD_AUTHTYPE_CARD)
	ds_asprintf(buf, "<wsa:Address>%s?AUTHTYPE=card</wsa:Address>", mex_url);
  else if (authtype == INFOCARD_AUTHTYPE_KERBEROS)
	return(-1);
  else
	return(-1);

  ds_concat(buf, "</mex:MetadataReference>");
  ds_concat(buf, "</mex:MetadataSection>");
  ds_concat(buf, "</mex:Metadata>");
  ds_concat(buf, "</wsa:Metadata>");

  ds_concat(buf, "<wsid:Identity xmlns:wsid=|http://schemas.xmlsoap.org/ws/2006/02/addressingidentity|>");
  ds_concat(buf, "<ds:KeyInfo xmlns:ds=|http://www.w3.org/2000/09/xmldsig#|>");
  ds_concat(buf, "<ds:X509Data>");

  ds_concat(buf, "<ds:X509Certificate>");
  /*
   * Get the PEM-encoded server cert and remove the beginning and ending
   * headers and newlines from it.
   */
  if ((certfile = conf_val(CONF_INFOCARD_STS_CERTFILE)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_CERTFILE must be defined"));
	return(-1);
  }
  cert = pem_cert_load_stripped(certfile);
  ds_concatn(buf, ds_buf(cert), ds_len(cert) - 1);
  log_msg((LOG_DEBUG_LEVEL, "Inserted cert from %s", certfile));
  ds_concat(buf, "</ds:X509Certificate>");

  ds_concat(buf, "</ds:X509Data>");
  ds_concat(buf, "</ds:KeyInfo>");
  ds_concat(buf, "</wsid:Identity>");
  ds_concat(buf, "</wsa:EndpointReference>");

  ds_concat(buf, "<ic:UserCredential>");
  if (authtype == INFOCARD_AUTHTYPE_CERT) {
	/* IP/STS auth is by X.509 certificate. */
	ds_concat(buf, "<ic:DisplayCredentialHint>");
	ds_concat(buf, "To authenticate, use your X.509 client certificate");
	ds_concat(buf, "</ic:DisplayCredentialHint>");
	ds_concat(buf, "<ic:X509V3Credential>");
	ds_concat(buf, "<ds:X509Data xmlns:ds=|http://www.w3.org/2000/09/xmldsig#|>");
	ds_concat(buf, "<wsse:KeyIdentifier xmlns:wsse=|http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd| ValueType=|http://docs.oasis-open.org/wss/2004/xx/oasis-2004xx-wss-soap-message-security-1.1#ThumbprintSHA1| EncodingType=|http://docs.oasis-open.org/wss/2004/01/oasis200401-wss-soap-message-security-1.0#Base64Binary|>");
	ds_concat(buf, ds_buf(thumb));
	ds_concat(buf, "</wsse:KeyIdentifier>");
	ds_concat(buf, "</ds:X509Data>");
	ds_concat(buf, "</ic:X509V3Credential>");
  }
  else if (authtype == INFOCARD_AUTHTYPE_PASSWD) {
	char *prompt_fmt, *sts_title;

	/* IP/STS auth is username/password. */
	prompt_fmt = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
								  "infocard_sts_username_password_prompt_fmt");
	if (prompt_fmt == NULL)
	  prompt_fmt = INFOCARD_DEFAULT_STS_USERNAME_PASSWD_PROMPT_FMT;

	sts_title = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
								 "infocard_sts_title");
	if (sts_title == NULL)
	  sts_title = INFOCARD_DEFAULT_STS_TITLE;

	ds_concat(buf, "<ic:DisplayCredentialHint>");
	ds_asprintf(buf, prompt_fmt, sts_title);
	ds_concat(buf, "</ic:DisplayCredentialHint>");
	ds_concat(buf, "<ic:UsernamePasswordCredential>");
	/* A Username for the IP/STS authentication credential is optional (5.1) */
	ds_asprintf(buf, "<ic:Username>%s</ic:Username>", uname);
	ds_concat(buf, "</ic:UsernamePasswordCredential>");
  }
  else if (authtype == INFOCARD_AUTHTYPE_CARD) {
	/* IP/STS auth is a self-issued credential. */
	ds_concat(buf, "<ic:SelfIssuedCredential>");
	/* A base64 encoded PPID */
	ds_concat(buf, "<ic:PrivatePersonalIdentifier>");
	ds_asprintf(buf, "%s", ds_buf(token->ppid));
	ds_concat(buf, "</ic:PrivatePersonalIdentifier>");
	ds_concat(buf, "</ic:SelfIssuedCredential>");
	log_msg((LOG_DEBUG_LEVEL | LOG_SENSITIVE_FLAG,
			 "Self-issued credential PPID is %s",
			 ds_buf(icx_friendly_identifier(token->ppid, 1))));
  }

  ds_concat(buf, "</ic:UserCredential>");
  ds_concat(buf, "</ic:TokenService>");

  return(0);
}

static void
add_issuer_info_entry(Ds *buf, char *name, char *value)
{

  ds_concat(buf, "<ic07:IssuerInformationEntry>");
  ds_asprintf(buf, "<ic07:EntryName>%s</ic07:EntryName>", name);
  ds_asprintf(buf, "<ic07:EntryValue>%s</ic07:EntryValue>", value);
  ds_concat(buf, "</ic07:IssuerInformationEntry>");
}

/*
 * Add zero or more custom descriptive items.
 * Each directive is a colon-separated name/value pair.
 * The name may not contain a colon.
 * XXX neither may contain a '|' character...
 */
static int
config_issuer_info_entries(Ds *buf)
{
  char *p, *s;
  Kwv_pair *v;

  for (v = conf_var(CONF_INFOCARD_ISSUER_INFO_ENTRY); v != NULL; v = v->next) {
	s = strdup(v->val);
	if ((p = strchr(s, (int) ':')) == NULL)
	  return(-1);
	*p++ = '\0';
	if (*p == '\0')
	  return(-1);
	add_issuer_info_entry(buf, s, p);
  }

  return(0);
}

static int
is_admin_mode(void)
{
  int admin;
  unsigned int ncookies;
  char *remote_addr;
  Cookie *cookies;
  Credentials *credentials, *selected;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No REMOTE_ADDR"));
	return(-1);
  }

  if (get_cookies(NULL, &cookies, &ncookies) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cookie parse error"));
	return(-1);
  }

  if (get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL) < 1) {
	log_msg((LOG_ERROR_LEVEL, "No credentials"));
	return(-1);
  }

  admin = is_dacs_admin(selected);

  return(admin);
}

#ifdef NOTDEF
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);
}
#endif

/*
 * In the "static" mode of operation, the caller may specify from zero to
 * MIC_MAX_STATIC_CLAIMS claims.  A privatepersonalidentifier is always
 * created automatically, so any user request for that claim is ignored.
 * The claims are specified by up to MIC_MAX_STATIC_CLAIMS arguments (not
 * counting any PPID claims) of the form: CLAIM_<num>_<type>
 * where <num> starts at one and continues with consecutive integers and
 * <type> is:
 *   o NAME for the name of the claim, which must consist of between one
 *     and MIC_MAX_STATIC_NAME_CLAIM_SIZE characters valid in a URI path
 *     segment
 *   o VALUE is the value associated with the claim and consists of between
 *     one and MIC_MAX_STATIC_VALUE_CLAIM_SIZE printable characters
 *   o URI is the URI namespace with which NAME is associated; for convenience,
 *     "standard" signifies the self-issued InfoCard namespace
 *     (http://schemas.xmlsoap.org/ws/2005/05/identity/claims), and "dacs"
 *     is short for the DACS namespace (http://dacs.dss.ca/claims);
 *     any other non-empty string can be any syntactically valid URI of up to
 *     MIC_MAX_STATIC_URI_CLAIM_SIZE, and
 *     an empty string indicates that the default URI should be used
 *   o LABEL is a string that an identity selector should display with the
 *     claim and consists of between one and MIC_MAX_STATIC_LABEL_CLAIM_SIZE
 *     printable characters
 *   o DESC is a string that an identity selector should display with
 *     the claim and consists of between one and MIC_MAX_STATIC_DESC_CLAIM_SIZE
 *     printable characters; if missing or the empty string, the value of
 *     the corresponding LABEL argument is used
 * The argument CLAIM_URI has the same syntax as the CLAIM_<num>_URI
 * argument and establishes a default URI that will be used if any
 * CLAIM_<num>_URI argument is missing or is the empty string.
 * The first missing or null-string-valued CLAIM_<number>_NAME or
 * CLAIM_<number>_VALUE argument indicates the end of the list.
 * Any syntactical or length violation causes a fatal error.
 */
static Icx_claim *
get_claim_arg(Kwv *kwv, int num, char **errmsg)
{
  char *default_uri, *val;
  Ds *ds;
  Icx_claim *claim;

  *errmsg = NULL;
  claim = NULL;
  ds = ds_init(NULL);

  /* Check if there is a default URI for the claim namespace. */
  if ((val = kwv_lookup_value(kwv, "CLAIM_URI")) != NULL && *val != '\0') {
	if (strlen(val) > MIC_MAX_STATIC_URI_CLAIM_SIZE) {
	  *errmsg = ds_xprintf("CLAIM_URI is longer than %u characters",
						   MIC_MAX_STATIC_URI_CLAIM_SIZE);
	  goto done;
	}
	default_uri = val;
  }
  else
	default_uri = ICX_DACS_CLAIM_URI;
  log_msg((LOG_TRACE_LEVEL, "Default claim URI=\"%s\"", default_uri));

  ds_asprintf(ds, "CLAIM_%d_NAME", num);
  if ((val = kwv_lookup_value(kwv, ds_buf(ds))) == NULL || *val == '\0')
	goto done;
  if (strlen(val) > MIC_MAX_STATIC_NAME_CLAIM_SIZE) {
	*errmsg = ds_xprintf("CLAIM_%d_NAME is longer than %u characters",
						 num, MIC_MAX_STATIC_NAME_CLAIM_SIZE);
	goto done;
  }
  if (!uri_is_valid_path_part(val)) {
	*errmsg = ds_xprintf("Invalid claim name: %s", val);
	goto done;
  }
  claim = ALLOC(Icx_claim);
  claim->name = val;

  ds_reset(ds);
  ds_asprintf(ds, "CLAIM_%d_VALUE", num);
  if ((val = kwv_lookup_value(kwv, ds_buf(ds))) == NULL || *val == '\0') {
	/* The empty string is not a valid value... there's no claim. */
	claim = NULL;
	goto done;
  }
  if (strlen(val) > MIC_MAX_STATIC_VALUE_CLAIM_SIZE) {
	*errmsg = ds_xprintf("CLAIM_%d_VALUE is longer than %u characters",
						 num, MIC_MAX_STATIC_VALUE_CLAIM_SIZE);
	goto done;
  }
  claim->value = val;

  ds_reset(ds);
  ds_asprintf(ds, "CLAIM_%d_URI", num);
  if ((val = kwv_lookup_value(kwv, ds_buf(ds))) == NULL || *val == '\0')
	val = default_uri;
  if (val == NULL) {
	*errmsg = ds_xprintf("No URI is specified for CLAIM %d", num);
	goto done;
  }
  if (streq(val, "standard")) {
	claim->uri = ICX_STANDARD_CLAIM_URI;
	claim->type = ICX_STANDARD_CLAIM;
  }
  else if (streq(val, "dacs")) {
	claim->uri = ICX_DACS_CLAIM_URI;
	claim->type = ICX_DACS_CLAIM;
  }
  else if (uri_parse(val) != NULL) {
	if (strlen(val) > MIC_MAX_STATIC_URI_CLAIM_SIZE) {
	  *errmsg = ds_xprintf("CLAIM_%d_URI is longer than %u characters",
						   num, MIC_MAX_STATIC_URI_CLAIM_SIZE);
	  goto done;
	}
	claim->uri = val;
	claim->type = ICX_USER_CLAIM;
  }
  else {
	*errmsg = ds_xprintf("Invalid claim URI: %s", val);
	goto done;
  }

  ds_reset(ds);
  ds_asprintf(ds, "CLAIM_%d_LABEL", num);
  if ((val = kwv_lookup_value(kwv, ds_buf(ds))) == NULL || *val == '\0') {
	*errmsg = ds_xprintf("Missing claim label: %s", ds_buf(ds));
	goto done;
  }
  if (strlen(val) > MIC_MAX_STATIC_LABEL_CLAIM_SIZE) {
	*errmsg = ds_xprintf("CLAIM_%d_LABEL is longer than %u characters",
						 num, MIC_MAX_STATIC_LABEL_CLAIM_SIZE);
	goto done;
  }
  claim->label = val;

  ds_reset(ds);
  ds_asprintf(ds, "CLAIM_%d_DESC", num);
  if ((val = kwv_lookup_value(kwv, ds_buf(ds))) == NULL || *val == '\0')
	val = claim->label;
  if (strlen(val) > MIC_MAX_STATIC_DESC_CLAIM_SIZE) {
	*errmsg = ds_xprintf("CLAIM_%d_DESC is longer than %u characters",
						 num, MIC_MAX_STATIC_DESC_CLAIM_SIZE);
	goto done;
  }
  claim->description = val;

 done:
  ds_free(ds);

  if (*errmsg != NULL)
	return(NULL);

  return(claim);
}

static char *item_type = ITEM_TYPE_INFOCARDS;

int
main(int argc, char **argv)
{
  int admin, i, save_to_file, st;
  char *card_baseurl, *card_url, *enc, *ext, *p, *ppid, *username;
  char *card_image_url, *ca_certfile, *certfile, *mex_url, *sts_title;
  char *card_suffix, *card_image_vfs_uri, *token_issuer;
  char *errmsg, *suffix_str, *sts_auth_type, *sts_auth_type_conf, *use_mode_str;
  char *infocard_digest, *privacy_url, *private_key_key, *private_keyfile;
  char *card_id, *card_filename, *card_name, *card_version;
  char *identity, *infocard_identity, *infocard_signature, *privacy_version;
  char *self_issued_ppid, *token_serviceurl;
  char *card_image_base_url, *card_image_file, *card_image_subtype;
  char *ssl_client_verify, *ssl_client_cert, *ssl_client_s_dn;
  char *ssl_client_s_dn_cn;
  char *lifetime_secs_str, *time_expires;
  unsigned char *image_str;
  unsigned int lifetime_secs;
  long enc_image_len;
  size_t image_len;
  Ic_sts_authtype authtype;
  Ic_use_mode use_mode;
  DACS_name dacs_name;
  Ds *buf, *canonicalbuf, *cert, *signature, *signedinfo;
  Ds *sbuf, *the_digest, *thumb;
  Dsvec *custom_claims;
  Ic_state initial_state;
  Ic_token *token;
  Icx_card_defs *card_defs;
  Icx_claim *claim_def;
  Kwv *kwv;
  Mic_sts_auth *sts_auth;
  Proc_lock *lock;
  RSA *priv_key;
  Vfs_handle *h;

  emit_format_default = EMIT_FORMAT_FILE;
  initial_state = IC_ENABLED;
  card_name = NULL;

  errmsg = NULL;
  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (errmsg == NULL)
	  errmsg = "Internal error";

	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 {
	  emit_plain_header(stdout);
	  fprintf(stdout, "%s\n", errmsg);
	  emit_plain_trailer(stdout);
	}

	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	exit(1);
  }

  save_to_file = (test_emit_format(EMIT_FORMAT_FILE) == 0);
  log_msg((LOG_DEBUG_LEVEL, "save_to_file=%d", save_to_file));

  /* 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;
  }

  /*
   * A managed InfoCard is always associated with an identity.
   * The possibilities are:
   * 1. If an identity is not specified, the DACS identity of the user who
   *    submitted the request is used.
   * 2. If a USERNAME parameter is specified, the DACS identity of USERNAME
   *    relative to the current jurisdiction is used.
   */
  if ((admin = is_admin_mode()) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot determine admin status"));
	admin = 0;
  }

  ssl_client_cert = NULL;
  ssl_client_verify = NULL;
  ssl_client_s_dn = NULL;
  ssl_client_s_dn_cn = NULL;

  /*
   * There are four usage modes, selected by the optional MODE argument:
   * "DACS" (IC_USE_MODE_DACS):
   *   The primary purpose of the card is DACS authentication, and
   *   claims are defined and filled according to DACS configuration;
   *   Either INFOCARD_CARD_DEFS_URL and INFOCARD_CARD_FILL_URL are specified,
   *   or a minimal set of static claims is automatically defined.
   *   This is the default.
   *
   * "STATIC" (IC_USE_MODE_STATIC):
   * "ISTATIC" (IC_USE_MODE_ISTATIC):
   *   The caller defines the claims and their values when the card
   *   is created; DACS is responsible for storing this information
   *   and producing secure tokens from it.
   *
   * "DYNAMIC" (IC_USE_MODE_DYNAMIC):
   *   The caller provides URLs for web services to define and
   *   fill claims, overriding INFOCARD_CARD_DEFS_URL and
   *   INFOCARD_CARD_FILL_URL; the caller is responsible for
   *   managing claim definitions and values
   *
   * Regardless of the mode, an account entry is always created.
   *
   * It is assumed that appropriate ACLs are in place.
   */
  use_mode = IC_USE_MODE_DACS;
  if ((use_mode_str = kwv_lookup_value(kwv, "MODE")) != NULL) {
	if (strcaseeq(use_mode_str, "DACS"))
	  use_mode = IC_USE_MODE_DACS;
	else if (strcaseeq(use_mode_str, "STATIC"))
	  use_mode = IC_USE_MODE_STATIC;
	else if (strcaseeq(use_mode_str, "ISTATIC"))
	  use_mode = IC_USE_MODE_ISTATIC;
	else if (strcaseeq(use_mode_str, "DYNAMIC"))
	  use_mode = IC_USE_MODE_DYNAMIC;
	else {
	  errmsg = ds_xprintf("Invalid MODE argument: \"%s\"", use_mode_str);
	  goto fail;
	}
	log_msg((LOG_TRACE_LEVEL, "MODE is \"%s\"", use_mode_str));
  }
  else {
	use_mode_str = "";
	log_msg((LOG_TRACE_LEVEL, "Using default MODE (DACS)"));
  }

  if ((identity = getenv("DACS_IDENTITY")) == NULL) {
	errmsg = "User is not authenicated";
	goto fail;
  }
  if (parse_dacs_name(identity, &dacs_name) != DACS_USER_NAME) {
	errmsg = "Invalid DACS_IDENTITY";
	goto fail;
  }
  username = dacs_name.username;
  ssl_client_cert = getenv("SSL_CLIENT_CERT");
  ssl_client_verify = getenv("SSL_CLIENT_VERIFY");
  ssl_client_s_dn = getenv("SSL_CLIENT_S_DN");
  ssl_client_s_dn_cn = getenv("SSL_CLIENT_S_DN_CN");

  custom_claims = NULL;
  switch (use_mode) {
  case IC_USE_MODE_DACS:
	infocard_identity = kwv_lookup_value(kwv, "INFOCARD_IDENTITY");
	if (infocard_identity != NULL && *infocard_identity != '\0') {
	  if (!admin) {
		errmsg = "Only administrator can use INFOCARD_IDENTITY argument";
		goto fail;
	  }

	  if (parse_dacs_name(infocard_identity, &dacs_name) != DACS_USER_NAME) {
		errmsg = "Invalid INFOCARD_IDENTITY";
		goto fail;
	  }
	  identity = infocard_identity;
	  username = dacs_name.username;
	}

	break;

  case IC_USE_MODE_STATIC:
  case IC_USE_MODE_ISTATIC:
	custom_claims = dsvec_init(NULL, sizeof(Icx_claim *));
	for (i = 1; (claim_def = get_claim_arg(kwv, i, &errmsg)) != NULL; i++) {
	  /* A PPID is automatically added later, so ignore a user request. */
	  if (streq(claim_def->uri, ICX_STANDARD_CLAIM_URI)
		  && streq(claim_def->name, "privatepersonalidentifier")) {
		log_msg((LOG_DEBUG_LEVEL, "Ignoring request for PPID claim"));
		continue;
	  }

	  if (dsvec_len(custom_claims) == MIC_MAX_STATIC_CLAIMS) {
		errmsg = "Too many static claims";
		goto fail;
	  }

	  /*
	   * An admin can define (or not) dacs_identity and dacs_roles,
	   * but an ordinary user cannot.
	   */
	  if (claim_def->type == ICX_DACS_CLAIM
		  && (streq(claim_def->name, "dacs_identity")
			  || streq(claim_def->name, "dacs_roles"))) {
		if (!admin) {
		  errmsg = "Cannot define dacs_identity or dacs_roles claims";
		  goto fail;
		}
	  }

	  dsvec_add_ptr(custom_claims, claim_def);
	  log_msg((LOG_DEBUG_LEVEL, "Define claim %d: %s=\"%s\"",
			   i, claim_def->name, claim_def->value));
	}

	if (claim_def == NULL && errmsg != NULL)
	  goto fail;

	if (use_mode == IC_USE_MODE_ISTATIC && !admin) {
	  claim_def = ALLOC(Icx_claim);
	  claim_def->type = ICX_DACS_CLAIM;
	  claim_def->name = "dacs_identity";
	  claim_def->uri = ICX_DACS_CLAIM_URI;
	  claim_def->value = identity;
	  claim_def->label = "DACS Identity";
	  claim_def->description = "A DACS identity";
	  dsvec_add_ptr(custom_claims, claim_def);
	}

	card_name = kwv_lookup_value(kwv, "CARD_NAME");

	break;

  case IC_USE_MODE_DYNAMIC:
	errmsg = ds_xprintf("MODE \"%s\" is not implemented", use_mode_str);
	goto fail;
	/*NOTREACHED*/
	break;

  case IC_USE_MODE_UNKNOWN:
  default:
	errmsg = ds_xprintf("MODE \"%s\" is not implemented", use_mode_str);
	goto fail;
	/*NOTREACHED*/
	break;
  }

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

  if ((card_baseurl = conf_val(CONF_INFOCARD_CARDID_BASE_URL)) == NULL) {
	errmsg = "INFOCARD_CARDID_BASE_URL must be defined";
	goto fail;
  }

  if (use_mode == IC_USE_MODE_STATIC || use_mode == IC_USE_MODE_ISTATIC)
	suffix_str = ds_buf(icx_friendly_identifier(NULL, 0));
  else {
	if ((card_suffix = conf_val(CONF_INFOCARD_CARDID_SUFFIX)) == NULL
		|| strcaseeq(card_suffix, "default"))
	  suffix_str = card_suffix = INFOCARD_DEFAULT_CARDID_SUFFIX;
	else if (strcaseeq(card_suffix, "identity"))
	  suffix_str = identity;
	else if (strcaseeq(card_suffix, "username"))
	  suffix_str = username;
	else if (strcaseeq(card_suffix, "random"))
	  suffix_str = crypto_make_random_a64(NULL, 20);
	else
	  suffix_str = card_suffix;
  }

  if (suffix_str == NULL) {
	errmsg = "Invalid card suffix selector";
	goto fail;
  }

  if (!uri_is_valid_path_part(suffix_str)) {
	errmsg = ds_xprintf("INFOCARD_CARDID_SUFFIX is syntactically invalid: %s",
						suffix_str);
	goto fail;
  }
  log_msg((LOG_TRACE_LEVEL, "suffix_str=\"%s\"", suffix_str));

  /*
   * This required element provides a unique identifier for the InfoCard
   * in the form of a URI.  The IP must be able to identify the InfoCard
   * based on this identifier.
   */
  card_id = ds_xprintf("%s/%s", card_baseurl, suffix_str);

  /*
   * Adapted from 4.1.1.1:
   * This required element provides a versioning epoch for the InfoCard
   * issuance infrastructure used by the IP.  The minimum value for this
   * field MUST be 1. Note that it is possible to include version information
   * in CardId as it is a URI, which can have hierarchical content.  The
   * version is specified as a separate value to allow the IP to change its
   * issuance infrastructure, and thus its versioning epoch, independently
   * without changing the CardId of all issued InfoCards.  For example, when
   * an IP makes a change to the supported claim types or any other policy
   * pertaining to the issued cards, the version number allows the IP to
   * determine if the InfoCard needs to be refreshed.  The version number
   * is assumed to be monotonically increasing. If two InfoCards have the
   * same CardId value but different CardVersion values, then the one with a
   * higher numerical CardVersion value should be treated as being
   * more up-to-date.
   */
  if ((card_version = conf_val(CONF_INFOCARD_CARD_VERSION)) == NULL)
	card_version = "1";

  /*
   * (4.3.4)
   * XXX
   */
  ppid = ds_xprintf("%s", crypto_make_random_a64("PPID-", 20));

  /* An optional element that provides a friendly textual name. */
  if (card_name == NULL) {
	if (identity != NULL)
	  card_name = identity;
	else
	  card_name = "DACS Managed InfoCard";
  }

  card_defs = ALLOC(Icx_card_defs);
  card_defs->ppid = ppid;
  card_defs->card_id = card_id;
  card_defs->card_version = card_version;
  card_defs->card_name = card_name;
  card_defs->claims = NULL;

  card_image_base_url = conf_val(CONF_INFOCARD_CARD_IMAGE_BASE_URL);
  if (card_image_base_url == NULL) {
	errmsg = "INFOCARD_CARD_IMAGE_BASE_URL must be defined";
	goto fail;
  }

  card_image_subtype = kwv_lookup_value(kwv, "CARD_IMAGE_SUBTYPE");
  if (card_image_subtype != NULL && *card_image_subtype != '\0') {
	log_msg((LOG_TRACE_LEVEL, "card_image_subtype=\"%s\"", card_image_subtype));
	if (!admin) {
	  errmsg = "Only admin can specify CARD_IMAGE_SUBTYPE";
	  goto fail;
	}
  }

  card_image_file = NULL;
  card_image_vfs_uri = NULL;
  if ((card_image_url = kwv_lookup_value(kwv, "CARD_IMAGE_URL")) != NULL
	  && *card_image_url != '\0') {
	Uri *uri;

	log_msg((LOG_TRACE_LEVEL, "card_image_url=\"%s\"", card_image_url));
	if (!admin) {
	  errmsg = "Only admin can specify CARD_IMAGE_URL";
	  goto fail;
	}

	/*
	 * As a security precaution, if CARD_IMAGE_URL is a file reference,
	 * we will force it to be relative to INFOCARD_CARD_IMAGE_BASE_URL;
	 * otherwise this feature could potentially be used (only by an admin) to
	 * grab any file readable by this program.
	 */
	if ((uri = uri_parse(card_image_url)) == NULL) {
	  /*
	   * A relative path, which may be missing an initial slash.
	   */
	  if (*card_image_url == '/')
		card_image_vfs_uri = ds_xprintf("%s%s",
										card_image_base_url, card_image_url);
	  else
		card_image_vfs_uri = ds_xprintf("%s/%s",
										card_image_base_url, card_image_url);
	  card_image_file = card_image_url;
	}
	else if (streq(uri->scheme, "file")) {
	  /* This assumes INFOCARD_CARD_IMAGE_BASE_URL is a file reference. */
	  card_image_vfs_uri = ds_xprintf("%s%s", card_image_base_url, uri->path);
	  card_image_file = uri->path;
	}
	else {
	  /* CARD_IMAGE_URL is a URL that is not a file reference.  Use it as-is. */
	  card_image_vfs_uri = card_image_url;
	  card_image_file = card_image_url;
	}

	log_msg((LOG_TRACE_LEVEL, "card_image_vfs_uri=\"%s\"", card_image_vfs_uri));
  }

  /*
   * card_filename
   * card_id
   * card_name
   * card_path
   * card_url
   */
  /* XXX */
  card_filename = ds_xprintf("%s_%s.crd",
							 username, crypto_make_random_string(NULL, 8));
  card_url = ds_xprintf("%s/%s", card_baseurl,
						percent_encode_chars(card_filename, "%;?&", 0));

  /*
   * XXX there can be a prioritized list of IP/STS endpoints,
   * with different UserCredential types
   *   "If the policy is retrieved for a token service, but the token service
   *    is not available, no fail-over occurs, at which point CardSpace will
   *    show an error to the user."
   */
  if ((sts_auth_type_conf = conf_val(CONF_INFOCARD_STS_AUTH_TYPE)) == NULL) {
	errmsg = "INFOCARD_STS_AUTH_TYPE must be defined";
	goto fail;
  }

#ifdef NOTDEF
  /*
   * The idea here is that by configuring INFOCARD_STS_AUTH_TYPE as "user",
   * the request would include an argument to select the authentication
   * credential type (e.g., STS_AUTH_TYPE=card).
   * While this might be a questionable feature for Joe User, it might be a
   * useful capability for an administrator.  The complication is that
   * for the "passwd" type, a password might need to be provided and stored
   * (and managed) for use by dacs_sts(8).
   * So for now this is omitted.
   */
  if (strcaseeq(sts_auth_type_conf, "user")) {
	if ((sts_auth_type = kwv_lookup_value(kwv, "STS_AUTH_TYPE")) == NULL) {
	  errmsg = "STS_AUTH_TYPE argument is required";
	  goto fail;
	}
  }
  else
	sts_auth_type = sts_auth_type_conf;
#endif

  sts_auth_type = sts_auth_type_conf;

  thumb = NULL;
  token = NULL;
  self_issued_ppid = NULL;
  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 (strcaseeq(sts_auth_type, "PASSWD")
	  || strcaseeq(sts_auth_type, "PASSWORD")) {
	char *digest_name;

	if (card_image_vfs_uri == NULL) {
	  card_image_file = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
										 "infocard_card_image_passwd");
	  if (card_image_file == NULL)
		card_image_file = INFOCARD_CARD_IMAGE_PASSWD;
	}
	sts_auth->authtype = authtype = INFOCARD_AUTHTYPE_PASSWD;
	sts_auth->digest = NULL;
	digest_name = NULL;
	if (ic_get_digest_algorithm(&digest_name, sts_auth->desc) == -1) {
	  errmsg = "No InfoCard digest is configured";
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "IP/STS authtype will be username/password"));
  }
  else if (strcaseeq(sts_auth_type, "CERT")) {
	if (ssl_client_cert == NULL || *ssl_client_cert == '\0') {
	  errmsg = "No SSL_CLIENT_CERT was provided";
	  goto fail;
	}
	if ((thumb = pem_cert_thumbprint(ssl_client_cert)) == NULL) {
	  errmsg = "Cannot obtain thumbprint of SSL_CLIENT_CERT";
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "Cert thumbprint is \"%s\" (%s)",
			 ds_buf(thumb), pem_cert_thumbprint_fmt_str(ssl_client_cert)));

	if (!is_client_cert(ssl_client_verify, ssl_client_cert,
						ssl_client_s_dn, ssl_client_s_dn_cn, thumb, identity)) {
	  errmsg
		= "A suitable verified client cert was not received with this request";
	  goto fail;
	}

	if (card_image_vfs_uri == NULL) {
	  card_image_file = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
										 "infocard_card_image_cert");
	  if (card_image_file == NULL)
		card_image_file = INFOCARD_CARD_IMAGE_CERT;
	}

	sts_auth->authtype = authtype = INFOCARD_AUTHTYPE_CERT;
	sts_auth->thumbprint = thumb;
	log_msg((LOG_DEBUG_LEVEL, "IP/STS authtype will be client cert"));
  }
  else if (strcaseeq(sts_auth_type, "CARD")) {
	char *enc_xmlToken, *xmlToken;
	Icx_ctx *ctx;

	/*
	 * A self-issued InfoCard (token) is submitted with the request
	 * for a managed InfoCard; the IP/STS requires the user to possess this
	 * self-issued card when the managed card is used.
	 */
	if ((enc_xmlToken = kwv_lookup_value(kwv, "xmlToken")) == NULL) {
	  errmsg = "An xmlToken argument is required";
	  goto fail;
	}

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

	ctx = ic_init(ic_config());

	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);

	if ((token = ic_parse_token(ctx, xmlToken)) == NULL) {
	  errmsg = "Token is invalid";
	  goto fail;
	}
	if (token->issuer != NULL) {
	  errmsg = "A self-issued token is required";
	  goto fail;
	}
	self_issued_ppid = ds_buf(token->ppid);

	if (card_image_vfs_uri == NULL) {
	  card_image_file = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
										 "infocard_card_image_card");
	  if (card_image_file == NULL)
		card_image_file = INFOCARD_CARD_IMAGE_CARD;
	}

	sts_auth->authtype = authtype = INFOCARD_AUTHTYPE_CARD;
	sts_auth->self_issued_ppid = self_issued_ppid;
	log_msg((LOG_DEBUG_LEVEL, "Card authtype will be self-issued credential"));
  }
  else if (strcaseeq(sts_auth_type, "KERBEROS")) {
	errmsg = "KerberosV5Credential is not supported";
	goto fail;
  }
  else {
	errmsg = ds_xprintf("Unrecognized INFOCARD_STS_AUTH_TYPE: \"%s\"",
						sts_auth_type);
	goto fail;
  }

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

  switch (use_mode) {
  case IC_USE_MODE_DACS:
	{
	  Icx_claim *claim;

	  if (icx_get_card_defs(identity, card_defs,
							conf_val(CONF_FEDERATION_NAME),
							conf_val(CONF_JURISDICTION_NAME)) == -1) {
		errmsg = "Cannot retrieve card definitions";
		goto fail;
	  }

	  if (card_defs->ppid != NULL)
		ppid = card_defs->ppid;

	  if ((claim = icx_lookup_claim(card_defs->claims, ICX_DACS_CLAIM_URI,
									"dacs_identity")) == NULL) {
		errmsg = "Cannot set dacs_identity claim";
		goto fail;
	  }
	  claim->value = identity;

	  /* Save the claim values for later retrieval by dacs_sts. */
	  st = mic_register_entry(h, identity, card_id, card_defs->claims,
							  use_mode, sts_auth, initial_state);
	  vfs_close(h);
	  if (st == -1) {
		errmsg = "Cannot register managed InfoCard";
		goto fail;
	  }

	  break;
	}

  case IC_USE_MODE_STATIC:
  case IC_USE_MODE_ISTATIC:
	{
	  Icx_claim *claim, *ppid_claim;

	  /* Save the claim values for later retrieval by dacs_sts. */
	  ppid_claim = icx_find_standard_claim("privatepersonalidentifier");
	  if (ppid_claim == NULL) {
		vfs_close(h);
		goto fail;
	  }
	  claim = ALLOC(Icx_claim);
	  *claim = *ppid_claim;
	  claim->value = ppid;
	  dsvec_add_ptr(custom_claims, claim);
	  log_msg((LOG_DEBUG_LEVEL, "Added PPID claim: \"%s\"", ppid));

	  card_defs->claims = custom_claims;
	  st = mic_register_entry(h, identity, card_id, custom_claims,
							  use_mode, sts_auth, initial_state);
	  vfs_close(h);
	  if (st == -1) {
		errmsg = "Cannot register managed InfoCard";
		goto fail;
	  }

	  break;
	}

  case IC_USE_MODE_DYNAMIC:
	break;

  case IC_USE_MODE_UNKNOWN:
  default:
	break;
  }

  /*
   * An optional element that contains a base64 encoded inline image that
   * provides a graphical image of the InfoCard.  It SHOULD contain an image
   * within the size range of 60 pixels wide by 45 pixels high and 200 pixels
   * wide by 150 pixels high.
   */
  if (card_image_vfs_uri == NULL)
	card_image_vfs_uri = ds_xprintf("%s/%s",
									card_image_base_url, card_image_file);
  log_msg((LOG_TRACE_LEVEL, "card_image_file=\"%s\"", card_image_file));
  log_msg((LOG_TRACE_LEVEL, "card_image_vfs_uri=\"%s\"", card_image_vfs_uri));

  /*
   * Only certain image types are allowed by the spec:
   * PNG, JPEG, GIF, TIFF, BMP
   */
  if (card_image_subtype != NULL && *card_image_subtype != '\0') {
	/* XXX validate it? */
  }
  else {
	if ((ext = strextname(card_image_file)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "card_image_file ext=\"%s\"", ext));
	  if (strcaseeq(ext, ".png"))
		card_image_subtype = "png";
	  else if (strcaseeq(ext, ".jpeg") || strcaseeq(ext, ".jpg"))
		card_image_subtype = "jpeg";
	  else if (strcaseeq(ext, ".gif"))
		card_image_subtype = "gif";
	  else if (strcaseeq(ext, ".tiff") || strcaseeq(ext, ".tif"))
		card_image_subtype = "tiff";
	  else if (strcaseeq(ext, ".bmp"))
		card_image_subtype = "bmp";
	  else {
		log_msg((LOG_DEBUG_LEVEL, "Cannot recognize imagefile extension"));
		/* XXX the default is a wild guess... inspect the file? */
		card_image_subtype = "png";
	  } 
	}
	else {
	  log_msg((LOG_DEBUG_LEVEL, "No extension, default image format is PNG"));
	  card_image_subtype = "png";
	}
  }

  sts_title = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
							   "infocard_sts_title");
  if (sts_title == NULL)
	sts_title = INFOCARD_DEFAULT_STS_TITLE;

  if ((privacy_url = conf_val(CONF_INFOCARD_IP_PRIVACY_URL)) == NULL) {
	errmsg = "INFOCARD_IP_PRIVACY_URL must be defined";
	goto fail;
  }
  if ((privacy_version = conf_val(CONF_INFOCARD_IP_PRIVACY_VERSION)) == NULL)
	privacy_version = "1";
  else {
	if (*privacy_version == '\0')
	  privacy_version = NULL;
	else if (strnum(privacy_version, STRNUM_UINZ, NULL) == -1) {
	  errmsg = "Invalid INFOCARD_IP_PRIVACY_VERSION";
	  goto fail;
	}
  }
  
  if ((mex_url = conf_val(CONF_INFOCARD_MEX_URL)) == NULL) {
	errmsg = "INFOCARD_MEX_URL must be defined";
	goto fail;
  }

  if ((private_key_key = conf_val(CONF_INFOCARD_STS_KEYFILE_PASSWORD)) == NULL)
	log_msg((LOG_DEBUG_LEVEL,
			 "INFOCARD_STS_KEYFILE_PASSWORD is not defined"));

  if ((private_keyfile = conf_val(CONF_INFOCARD_STS_KEYFILE)) == NULL) {
	errmsg = "INFOCARD_STS_KEYFILE must be defined";
	goto fail;
  }

  if ((token_issuer = conf_val(CONF_INFOCARD_TOKEN_ISSUER)) == NULL) {
	errmsg = "INFOCARD_TOKEN_ISSUER must be defined";
	goto fail;
  }
  if (strcaseeq(token_issuer, "self"))
	token_issuer = "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self";

  if ((token_serviceurl = conf_val(CONF_INFOCARD_STS_URL)) == NULL) {
	errmsg = "INFOCARD_STS_URL must be defined";
	goto fail;
  }
  log_msg((LOG_DEBUG_LEVEL, "token_serviceurl=%s", token_serviceurl));

  /*
   * For convenience and clarity, the '|' character is used instead of a double
   * quote when assembling the message.  When the message has been composed,
   * we'll replace every '|' character with a '"'.
   * XXX There is currently no way to escape a '|'
   */
  buf = ds_init(NULL);
  ds_concat(buf, "<dsig:Object xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#| Id=|_Object_InfoCard|>");
  ds_concat(buf, "<ic:InformationCard xmlns:ic=|http://schemas.xmlsoap.org/ws/2005/05/identity| xml:lang=|en-us|>");
  ds_concat(buf, "<ic:InformationCardReference>");

  /* A URI; note that this is visible to the user. */
  ds_asprintf(buf, "<ic:CardId>%s</ic:CardId>", card_id);
  log_msg((LOG_DEBUG_LEVEL, "New CardId is %s", card_id));

  ds_asprintf(buf, "<ic:CardVersion>%s</ic:CardVersion>", card_version);
  log_msg((LOG_DEBUG_LEVEL, "New CardVersion is %s", card_version));
  ds_concat(buf, "</ic:InformationCardReference>");

  /* Note that this is visible to the user. */
  ds_asprintf(buf, "<ic:CardName>%s</ic:CardName>", card_name);
  log_msg((LOG_DEBUG_LEVEL, "New CardName is %s", card_name));

  log_msg((LOG_DEBUG_LEVEL, "Card image is type \"%s\" from \"%s\"",
		   card_image_subtype, card_image_vfs_uri));
  ds_asprintf(buf, "<ic:CardImage MimeType=|image/%s|>", card_image_subtype);
  if (vfs_get_any(card_image_vfs_uri, NULL, (void **) &image_str, &image_len)
	  == -1) {
	errmsg = ds_xprintf("Cannot load card image: %s", card_image_vfs_uri);
	goto fail;
  }
  enc_image_len = mime_encode_base64(image_str, image_len, &enc);
  log_msg((LOG_TRACE_LEVEL, "Image size (bytes): unencoded=%u, encoded=%ld",
		   image_len, enc_image_len));
  ds_concat(buf, enc);
  ds_concat(buf, "</ic:CardImage>");

  ds_asprintf(buf, "<ic:Issuer>%s</ic:Issuer>", token_issuer);

  ds_concat(buf, "<ic:TimeIssued>");
  ds_asprintf(buf, "%s", icx_gmdatetime(0, 0));
  ds_concat(buf, "</ic:TimeIssued>");

  if ((time_expires = conf_val(CONF_INFOCARD_CARD_DATETIME_EXPIRES)) != NULL) {
	char *endp;

	if (parse_datetime(time_expires, NULL, &endp) == NULL || *endp != '\0') {
	  errmsg = ds_xprintf("Invalid INFOCARD_CARD_DATETIME_EXPIRES: \"%s\"",
						  time_expires);
	  goto fail;
	}
  }
  else {
	if ((lifetime_secs_str = conf_val(CONF_INFOCARD_CARD_LIFETIME_SECS))
		!= NULL) {
	  if (strnum(lifetime_secs_str, STRNUM_UI, &lifetime_secs) == -1) {
		errmsg = ds_xprintf("Invalid INFOCARD_CARD_LIFETIME_SECS: \"%s\"",
							lifetime_secs_str);
		goto fail;
	  }
	}
	else
	  lifetime_secs = INFOCARD_DEFAULT_CARD_LIFETIME_SECS;
	log_msg((LOG_DEBUG_LEVEL, "lifetime_secs=%u", lifetime_secs));
	time_expires = icx_gmdatetime(0, (long) lifetime_secs);
  }
  ds_asprintf(buf, "<ic:TimeExpires>%s</ic:TimeExpires>", time_expires);
  log_msg((LOG_DEBUG_LEVEL, "TimeExpires=%s", time_expires));

  ds_concat(buf, "<ic:TokenServiceList>");
  st = token_service(buf, authtype, identity, token_serviceurl, mex_url, thumb,
					 token);
  if (st == -1) {
	errmsg = "Error creating TokenService element";
	goto fail;
  }
  ds_concat(buf, "</ic:TokenServiceList>");

  ds_concat(buf, "<ic:SupportedTokenTypeList>");
  ds_concat(buf, "<wst:TokenType xmlns:wst=|http://schemas.xmlsoap.org/ws/2005/02/trust|>urn:oasis:names:tc:SAML:1.0:assertion</wst:TokenType>");
  ds_concat(buf, "</ic:SupportedTokenTypeList>");

  ds_concat(buf, "<ic:SupportedClaimTypeList>");

  /* Add standard and custom claims. */
  for (i = 0; i < dsvec_len(card_defs->claims); i++) {
	claim_def = (Icx_claim *) dsvec_ptr_index(card_defs->claims, i);

	ds_asprintf(buf, "<ic:SupportedClaimType Uri=|%s/%s|>",
				claim_def->uri, claim_def->name);
	ds_asprintf(buf, "<ic:DisplayTag>%s</ic:DisplayTag>",
				claim_def->label);
	ds_asprintf(buf, "<ic:Description>%s</ic:Description>",
				claim_def->description);
	ds_concat(buf, "</ic:SupportedClaimType>");
	log_msg((LOG_DEBUG_LEVEL, "Defining claim %s/%s",
			 claim_def->uri, claim_def->name));
  }

  ds_concat(buf, "</ic:SupportedClaimTypeList>");

  if ((p = conf_val(CONF_INFOCARD_REQUIRE_APPLIES_TO)) != NULL
	  && !strcaseeq(p, "no")) {
	/*
	 * Token requests must include information in its wsp:AppliesTo element
	 * to identify the Relying Party where the token will be used.
	 */
	if (strcaseeq(p, "true"))
	  ds_asprintf(buf, "<ic:RequireAppliesTo Optional=|true| />");
	else if (strcaseeq(p, "false"))
	  ds_asprintf(buf, "<ic:RequireAppliesTo Optional=|false| />");
	else if (strcaseeq(p, "yes"))
	  ds_asprintf(buf, "<ic:RequireAppliesTo />");
	else {
	  errmsg = ds_xprintf("Invalid INFOCARD_REQUIRE_APPLIES_TO: \"%s\"", p);
	  goto fail;
	}
  }

  if (privacy_version != NULL)
	ds_asprintf(buf, "<ic:PrivacyNotice Version=\"%s\">%s</ic:PrivacyNotice>",
				privacy_version, privacy_url);
  else
	ds_asprintf(buf, "<ic:PrivacyNotice>%s</ic:PrivacyNotice>", privacy_url);

  if (conf_val_eq(CONF_INFOCARD_STRONG_RP_IDENTITY, "yes")) {
	/*
	 * The Relying Party must use a cryptographically protected identity;
	 * i.e., an X.509 server cert.
	 */
	ds_concat(buf, "<ic07:RequireStrongRecipientIdentity xmlns:ic07=|http://schemas.xmlsoap.org/ws/2007/01/identity| />");
  }

  /*
   * <ic07:IssuerInformation>
   *  <IssuerInformationEntry>
   *   <EntryName> xs:string </EntryName>
   *   <EntryValue> xs:string </EntryValue>
   *  </IssuerInformationEntry> +
   * </ic07:IssuerInformation>
   */
  ds_concat(buf, "<ic07:IssuerInformation xmlns:ic07=|http://schemas.xmlsoap.org/ws/2007/01/identity|>");
  add_issuer_info_entry(buf, "Generated By", dacs_version_string());
  if (config_issuer_info_entries(buf) == -1) {
	errmsg = "Invalid INFOCARD_ISSUER_INFO_ENTRY";
	goto fail;
  }
  ds_concat(buf, "</ic07:IssuerInformation>");

  ds_concat(buf, "</ic:InformationCard>");
  ds_concat(buf, "</dsig:Object>");

  /* Replace '|' with '"'. */
  ds_subst(buf, (unsigned char) '|', (unsigned char) '"');

  canonicalbuf = icx_canonicalize_xml(buf);

  /* Compute the digest of the InfoCard */
  the_digest = sha1((unsigned char *) ds_buf(canonicalbuf),
					ds_len(canonicalbuf) - 1);

  mime_encode_base64((unsigned char *) ds_buf(the_digest),
					 ds_len(the_digest), &infocard_digest);

  signedinfo = ds_init(NULL);
  ds_concat(signedinfo, "<dsig:SignedInfo xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#|>");
  ds_concat(signedinfo, "<dsig:CanonicalizationMethod Algorithm=|http://www.w3.org/2001/10/xml-exc-c14n#| />");
  ds_concat(signedinfo, "<dsig:SignatureMethod Algorithm=|http://www.w3.org/2000/09/xmldsig#rsa-sha1| />");
  ds_concat(signedinfo, "<dsig:Reference URI=|#_Object_InfoCard|>");
  ds_concat(signedinfo, "<dsig:Transforms>");
  ds_concat(signedinfo, "<dsig:Transform Algorithm=|http://www.w3.org/2001/10/xml-exc-c14n#| />");
  ds_concat(signedinfo, "</dsig:Transforms>");
  ds_concat(signedinfo, "<dsig:DigestMethod Algorithm=|http://www.w3.org/2000/09/xmldsig#sha1| />");
  ds_asprintf(signedinfo, "<dsig:DigestValue>%s</dsig:DigestValue>",
			  infocard_digest);
  ds_concat(signedinfo, "</dsig:Reference>");
  ds_concat(signedinfo, "</dsig:SignedInfo>");

  /* Replace '|' with '"'. */
  ds_subst(signedinfo, (unsigned char) '|', (unsigned char) '"');

  canonicalbuf = icx_canonicalize_xml(signedinfo);

  /* Compute the signature of the canonicalized digest */
  priv_key = pem_load_private_key(private_keyfile, private_key_key);

  signature = crypto_sign_buf(canonicalbuf, priv_key);
  RSA_free(priv_key);
  mime_encode_base64((unsigned char *) ds_buf(signature),
					 ds_len(signature), &infocard_signature);

  sbuf = ds_init(NULL);
  ds_concat(sbuf,
			"<dsig:Signature xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#|>");
  ds_concat(sbuf, ds_buf(signedinfo));
  ds_asprintf(sbuf, "<dsig:SignatureValue>%s</dsig:SignatureValue>",
			  infocard_signature);
  ds_concat(sbuf, "<dsig:KeyInfo>");
  ds_concat(sbuf, "<dsig:X509Data>");

  /* Insert the signing certificate(s) */
  if ((certfile = conf_val(CONF_INFOCARD_STS_CERTFILE)) == NULL) {
	errmsg = "INFOCARD_STS_CERTFILE must be defined";
	goto fail;
  }
  if ((cert = pem_cert_load_stripped(certfile)) == NULL) {
	errmsg = ds_xprintf("Cannot load cert file %s", certfile);
	goto fail;
  }
  ds_asprintf(sbuf, "<dsig:X509Certificate>%s</dsig:X509Certificate>",
			  ds_buf(cert));
  ds_free(cert);

  if ((ca_certfile = conf_val(CONF_INFOCARD_STS_CACERTFILE)) == NULL) {
	errmsg = "CONF_INFOCARD_STS_CACERTFILE must be defined";
	goto fail;
  }
  if ((cert = pem_cert_load_stripped(ca_certfile)) == NULL) {
	errmsg = ds_xprintf("Cannot load CA cert file %s", ca_certfile);
	goto fail;
  }
  ds_asprintf(sbuf, "<dsig:X509Certificate>%s</dsig:X509Certificate>",
			  ds_buf(cert));
  ds_free(cert);

  ds_concat(sbuf, "</dsig:X509Data>");
  ds_concat(sbuf, "</dsig:KeyInfo>");

  /* Replace '|' with '"'. */
  ds_subst(sbuf, (unsigned char) '|', (unsigned char) '"');

  ds_concat(sbuf, ds_buf(buf));
  ds_concat(sbuf, "</dsig:Signature>");

  if (!save_to_file) {
	/* Emit the card now. */
	printf("Content-Disposition: attachment; filename=\"%s.crd\"\n", username);
	printf("Content-Type: application/x-informationCard\n");
	printf("Content-Length: %lu\n", (unsigned long) ds_len(sbuf) - 1);
	printf("\n");
	printf("%s", ds_buf(sbuf));
	log_msg((LOG_TRACE_LEVEL, "Managed InfoCard has been emitted"));
  }
  else {
	if (test_emit_xml_format()) {
	  /* XXX Not implemented yet. */
	}
	else {
	  int fd, st;
	  char *card_path, *card_outputdir;
	  Html_header_conf *hc;

	  /*
	   * Write the card to a file and emit some HTML that includes a link
	   * to the card.  This assumes that directory INFOCARD_CARD_OUTPUTDIR
	   * can be accessed as the INFOCARD_CARDID_BASE_URL.
	   */
	  hc = emit_html_header_conf(NULL);
	  hc->no_cache = 1;
	  hc->title = sts_title;

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

	  emit_html_header(stdout, hc);

	  /* This is where the new card will be written. */
	  if ((card_outputdir = conf_val(CONF_INFOCARD_CARD_OUTPUTDIR)) == NULL) {
		errmsg = "INFOCARD_CARD_OUTPUTDIR must be defined";
		goto fail;
	  }
	  card_path = ds_xprintf("%s/%s", card_outputdir, card_filename);

	  if (unlink(card_path) == -1)
		log_msg((LOG_WARN_LEVEL, "Cannot unlink %s", card_path));

	  st = 0;
	  /*
	   * The mode and group must limit access to the web server (and DACS)
	   * and an access control rule should also be in place to restrict
	   * access to <username>_<rand>.crd to DACS_IDENTITY in the current
	   * jurisdiction.
	   */
	  if ((fd = open(card_path, O_CREAT | O_WRONLY, 0640)) != -1) {
		size_t len;
		ssize_t n;

		len = ds_len(sbuf) - 1;
		if ((n = write(fd, ds_buf(sbuf), len)) != len) {
		  log_err((LOG_ERROR_LEVEL, "write"));
		  st = -1;
		}

		if (st == 0 && close(fd) == -1) {
		  log_err((LOG_ERROR_LEVEL, "close"));
		  st = -1;
		}
	  }
	  if (st == 0)
		log_msg((LOG_DEBUG_LEVEL, "Wrote card to %s", card_path));
	  else {
		printf("Could not write card to %s", card_path);
		log_msg((LOG_ERROR_LEVEL, "Could not write card to %s", card_path));
		unlink(card_path);
		emit_html_trailer(stdout);
		exit(1);
	  }

	  printf("<p>A new managed InfoCard has been created for <tt><b>%s</b></tt>.\n", identity);
	  printf("<p>");
	  printf("IP/STS authentication method: ");
	  if (authtype == INFOCARD_AUTHTYPE_CERT) {
		printf("X509 client certificate<br>\n");
		if (ssl_client_s_dn_cn != NULL)
		  printf("client_s_dn_cn=%s,<br>\n", ssl_client_s_dn_cn);
	  }
	  else if (authtype == INFOCARD_AUTHTYPE_PASSWD)
		printf("username/password<br>\n");
	  else if (authtype == INFOCARD_AUTHTYPE_CARD)
		printf("self-issued InfoCard<br>\n");

	  printf("<p>Use <a href=\"%s\">this link</a> to get your managed InfoCard\n",
			 card_url);

	  emit_html_trailer(stdout);
	}
  }

  exit(0);
}

