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

/*
 * Authentication based on InfoCards.
 * The account file is maintained by dacsinfocard and must be run-time
 * accessible by this CGI program.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: local_infocard_auth.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include "dacs.h"

static const char *log_module_name = "local_infocard_authenticate";

/*
 * InfoCard-based authentication
 * If authentication succeeds, return 0 and set IC_AUTH
 * appropriately; return -1 otherwise.
 */
int
local_infocard_auth(char *enc_xmlToken, char *aux, Ic_auth *ic_auth)
{
  int st;
  char *xmlToken;
  Ic_auth *ic;
  Ic_config *conf;
  Proc_lock *lock;
  Vfs_handle *h;

  if (enc_xmlToken == NULL || enc_xmlToken[0] == '\0')
	return(-1);

  if ((xmlToken = url_decode(enc_xmlToken, NULL, NULL)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Decode of xmlToken failed"));
	return(-1);
  }

  if ((lock = proc_lock_create(PROC_LOCK_INFOCARD)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not obtain lock"));
	return(-1);
  }

  st = -1;
  if ((h = vfs_open_item_type("infocards")) == NULL) {
	log_msg((LOG_ALERT_LEVEL, "Can't open item type \"infocards\""));
	goto done;
  }

  conf = ic_config();

  if ((conf->certfile = conf_val(CONF_INFOCARD_STS_CERTFILE)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_CERTFILE must be defined"));
	goto done;
  }

  if ((conf->keyfile = conf_val(CONF_INFOCARD_STS_KEYFILE)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_KEYFILE must be defined"));
	goto done;
  }

  conf->audience = ic_get_config_audience();
  conf->issuer = NULL;
  conf->enable_replay_detection = 0;
  conf->enable_signature_validation = 1;
  conf->max_token_size = 0;
  conf->max_drift_secs = 0;

  /*
   * Check if the token is valid and associated with a registered account.
   */
  if ((ic = ic_lookup_entry(h, xmlToken, conf)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of InfoCard failed"));
	vfs_close(h);
	goto done;
  }
  log_msg((LOG_TRACE_LEVEL, "Found %s InfoCard",
		   (ic->type == IC_MANAGED_TYPE) ? "managed" : "self-issued"));
  log_msg((LOG_TRACE_LEVEL, "state=\"%s\", use_mode=\"%s\", username=\"%s\"",
		   (ic->state == IC_ENABLED) ? "enabled" : "disabled",
		   (ic->use_mode == IC_USE_MODE_DACS) ? "dacs"
		   : ((ic->use_mode == IC_USE_MODE_STATIC) ? "static" : "dynamic"),
		   ic->username));
  if (ic->roles != NULL)
	log_msg((LOG_TRACE_LEVEL, "roles=\"%s\"", ic->roles));

  if (vfs_close(h) == -1)
	log_msg((LOG_ERROR_LEVEL, "vfs_close() failed"));

  /*
   * The account must be enabled for it to be used for authentication.
   */
  if (ic->state == IC_DISABLED) {
	log_msg((LOG_NOTICE_LEVEL, "Account is disabled for username \"%s\"",
			 ic->username));
	goto done;
  }
  else
	st = 0;

  *ic_auth = *ic;

 done:
  proc_lock_delete(lock);

  return(st);
}

#ifdef PROG
int
main(int argc, char **argv)
{
  int emitted_dtd, self_issued;
  unsigned int ui;
  char *aux, *enc_xmlToken, *errmsg, *jurisdiction, *req_type, *username;
  char *xmlToken;
  Auth_reply_ok ok;
  Ic_auth ic_auth;
  Kwv *kwv;

  emitted_dtd = 0;
  errmsg = "Internal error";
  enc_xmlToken = aux = jurisdiction = username = xmlToken = NULL;
  req_type = NULL;

  if (dacs_init(DACS_LOCAL_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
	/* If we fail here, we may not have a DTD with which to reply... */
  fail:
	if (enc_xmlToken != NULL)
	  strzap(enc_xmlToken);
	if (xmlToken != NULL)
	  strzap(xmlToken);
	if (aux != NULL)
	  strzap(aux);
	if (emitted_dtd) {
	  printf("%s\n", make_xml_auth_reply_failed(NULL, NULL));
	  emit_xml_trailer(stdout);
	}
	log_msg((LOG_ERROR_LEVEL, "Failed: reason=%s", errmsg));

	exit(1);
  }

  /* This must go after initialization. */
  emitted_dtd = emit_xml_header(stdout, "auth_reply");

  if (argc > 1) {
	errmsg = "Usage: unrecognized parameter";
	goto fail;
  }

  for (ui = 0; ui < kwv->nused; ui++) {
	if (streq(kwv->pairs[ui]->name, "xmlToken") && enc_xmlToken == NULL)
	  username = kwv->pairs[ui]->val;
	else if (streq(kwv->pairs[ui]->name, "AUXILIARY") && aux == NULL)
	  aux = kwv->pairs[ui]->val;
	else if (streq(kwv->pairs[ui]->name, "TYPE") && req_type == NULL) {
	  req_type = kwv->pairs[ui]->val;
	  if (!strcaseeq(req_type, "selfissued")
		  && !strcaseeq(req_type, "managed")) {
		errmsg = "Unrecognized TYPE argument";
		goto fail;
	  }
	}
	else if (streq(kwv->pairs[ui]->name, "DACS_JURISDICTION")
			 && jurisdiction == NULL)
	  jurisdiction = kwv->pairs[ui]->val;
	else if (streq(kwv->pairs[ui]->name, "DACS_VERSION"))
	  ;
	else
	  log_msg((LOG_DEBUG_LEVEL, "Parameter: '%s'", kwv->pairs[ui]->name));
  }

  /* Verify that we're truly responsible for DACS_JURISDICTION */
  if (dacs_verify_jurisdiction(jurisdiction) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Missing or incorrect DACS_JURISDICTION"));
	goto fail;
  }

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

  if (local_infocard_auth(xmlToken, aux, &ic_auth) == -1) {
	errmsg = "InfoCard/account incorrect";
	goto fail;
  }
  if (username != NULL && !streq(username, ic_auth.username)) {
	errmsg = "Asserted username does not match actual username";
	goto fail;
  }
  self_issued = (ic_auth.type == IC_SELF_ISSUED_TYPE);

  if (req_type != NULL) {
	if (strcaseeq(req_type, "SELFISSUED") && !self_issued) {
	  errmsg = "Require self-issued InfoCard, got managed InfoCard";
	  goto fail;
	}
	if (strcaseeq(req_type, "MANAGED") && self_issued) {
	  errmsg = "Require managed InfoCard, got self-issued InfoCard";
	  goto fail;
	}
  }

  if (xmlToken != NULL)
	strzap(xmlToken);
  if (aux != NULL)
	strzap(aux);

  ok.username = ic_auth.username;
  /* If this wasn't specified, dacs_authenticate will use the default. */
  ok.lifetime = kwv_lookup_value(kwv, "CREDENTIALS_LIFETIME_SECS");
  ok.roles_reply = NULL;

#ifdef NOTDEF
  {
	/* Here's a simple demonstration of how roles might be returned. */
	Roles_reply *rr;

	rr = ALLOC(Roles_reply);
	rr->ok = ALLOC(Roles_reply_ok);
	rr->ok->roles = "auggie,harley,bandito";
	rr->failed = NULL;
	rr->error = NULL;
	ok.roles_reply = rr;
  }
#endif

  printf("%s\n", make_xml_auth_reply_ok(&ok));

  emit_xml_trailer(stdout);
  exit(0);
}
#endif
