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

/*
 * WS-MetadataExchange responder for DACS managed InfoCards
 */

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

#include "icx.h"

static const char *log_module_name = "dacs_mex";

/*
 * Get the MessageID from the XML document, or NULL if it is not found.
 */
static xmlChar *
get_request_id(xmlNodePtr root)
{
  xmlNodePtr env, header, message_id, ptr, val;

  if (root == NULL)
	return(NULL);

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

  if ((header = env->children) == NULL
	  || header->type != XML_ELEMENT_NODE
	  || !xmlStreq(header->name, (xmlChar *) "Header"))
	return(NULL);

  for (ptr = header->children; ptr != NULL; ptr = ptr->next) {
	if (ptr->type == XML_ELEMENT_NODE
		&& xmlStreq(ptr->name, (xmlChar *) "MessageID")) {
	  message_id = ptr;
	  if ((val = ptr->children) == NULL)
		return(NULL);

	  if (val->type != XML_TEXT_NODE || val->content == NULL)
		return(NULL);

	  return((xmlChar *) val->content);
	}
  }

  return(NULL);
}

int
main(int argc, char **argv)
{
  int use_soap;
  char *certfile, *method, *p;
  char *errmsg, *request_id, *token_serviceurl;
  size_t len, post_data_len;
  Ds *buf, *cert, *ds, *post_data;
  Ic_sts_authtype authtype;
  Kwv *kwv;
  xmlParserCtxtPtr parser_ctx;

  parser_ctx = NULL;
  errmsg = NULL;

  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (parser_ctx != NULL)
	  xmlFreeParserCtxt(parser_ctx);
	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);
  }

  if ((method = getenv("REQUEST_METHOD")) == NULL)
	method = "POST";
  log_msg((LOG_DEBUG_LEVEL, "method=%s", method));

  if (strcaseeq(method, "POST"))
	use_soap = 1;
  else
	use_soap = 0;

  if (use_soap)
	fprintf(stdout, "Content-Type: application/soap+xml;charset=utf-8\n");
  else
    fprintf(stdout, "Content-Type: application/xml;charset=utf-8\n");

  {
	Entity_body *eb;

	if ((eb = cgiparse_get_entity_body()) == NULL) {
	  errmsg = "No entity body?";
	  goto fail;
	}
	post_data = ds_setn(NULL, eb->body, eb->content_length);
	post_data_len = ds_len(post_data);
	ds_appendc(post_data, (int) '\0');
	log_msg((LOG_TRACE_LEVEL, "read post_data (%u bytes):\n%s",
			 post_data_len, ds_buf(post_data)));
  }

  if ((p = kwv_lookup_value(kwv, "AUTHTYPE")) != NULL) {
	if (strcaseeq(p, "PASSWD"))
	  authtype = INFOCARD_AUTHTYPE_PASSWD;
	else if (strcaseeq(p, "CERT"))
	  authtype = INFOCARD_AUTHTYPE_CERT;
	else if (strcaseeq(p, "CARD"))
	  authtype = INFOCARD_AUTHTYPE_CARD;
	else {
	  errmsg = "Invalid AUTHTYPE argument";
	  goto fail;
	}
  }
  else
	authtype = INFOCARD_AUTHTYPE_PASSWD;

  /*
   * Grab the important parts of the token request.  That's pretty much just
   * the request ID.
   */
  request_id = "";
  if (use_soap && post_data_len) {
	xmlDocPtr doc;
	xmlNodePtr root;

	if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	  errmsg = "Unable to initialize parser context";
	  goto fail;
	}

	doc = xmlCtxtReadMemory(parser_ctx,
							(const char *) ds_buf(post_data),
							(int) post_data_len, 
							NULL, NULL, 0);
	if (doc == NULL) {
	  xmlErrorPtr err;

	  err = xmlCtxtGetLastError(parser_ctx);
	  errmsg = ds_xprintf("Token parse failed, non-well-formed XML: %s",
						  (err != NULL) ? err->message : "null");
	  goto fail;
	}

	root = xmlDocGetRootElement(doc);
	if ((request_id = (char *) get_request_id(root)) == NULL) {
	  errmsg = "No MessageID?";
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "request_id=%s", request_id));
  }

  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, we will use the '|' character as 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, "<?xml version=|1.0|?>");
  ds_concat(buf,
			"<S:Envelope xmlns:S=|http://www.w3.org/2003/05/soap-envelope| xmlns:wsa=|http://www.w3.org/2005/08/addressing|>");
  ds_concat(buf, "<S:Header>");
  ds_concat(buf, "<wsa:Action S:mustUnderstand=|1|>");
  ds_concat(buf, "http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse");
  ds_concat(buf, "</wsa:Action>");
  ds_concat(buf, "<wsa:RelatesTo>");
  ds_concat(buf, request_id);
  ds_concat(buf, "</wsa:RelatesTo>");
  ds_concat(buf, "</S:Header>");

  ds_concat(buf, "<S:Body>");
  ds_concat(buf,
			"<Metadata xmlns=|http://schemas.xmlsoap.org/ws/2004/09/mex|>");
  ds_concat(buf, "<MetadataSection Dialect=|http://schemas.xmlsoap.org/wsdl/| Identifier=|http://schemas.xmlsoap.org/ws/2005/02/trust|>");
  ds_asprintf(buf, "<wsdl:definitions name=|STS_wsdl| targetNamespace=|%s| xmlns:tns=|%s| xmlns:xs=|http://www.w3.org/2001/XMLSchema| xmlns:wsdl=|http://schemas.xmlsoap.org/wsdl/| xmlns:wsa=|http://www.w3.org/2005/08/addressing| xmlns:wst=|http://schemas.xmlsoap.org/ws/2005/02/trust| xmlns:wsid=|http://schemas.xmlsoap.org/ws/2006/02/addressingidentity| xmlns:wsaw=|http://www.w3.org/2006/05/addressing/wsdl| xmlns:wsp=|http://schemas.xmlsoap.org/ws/2004/09/policy| xmlns:sp=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy| xmlns:wsu=|http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd| xmlns:soap12=|http://schemas.xmlsoap.org/wsdl/soap12/| xmlns:ds=|http://www.w3.org/2000/09/xmldsig#| xmlns:ic=|http://schemas.xmlsoap.org/ws/2005/05/identity| xmlns:q1=|%s|>",
			token_serviceurl, token_serviceurl, token_serviceurl);

  ds_concat(buf, "<wsdl:types>");
  ds_concat(buf, "<xs:schema targetNamespace=|http://schemas.xmlsoap.org/ws/2005/02/trust/Imports|>");
  ds_asprintf(buf, "<xs:import schemaLocation=|| namespace=|%s|/>",
			  token_serviceurl);
  ds_concat(buf, "</xs:schema>");
  ds_concat(buf, "</wsdl:types>");

  ds_concat(buf, "<wsdl:message name=|RequestSecurityTokenMsg|>");
  ds_concat(buf, "<wsdl:part name=|request| type=|q1:MessageBody| />");
  ds_concat(buf, "</wsdl:message>");
  ds_concat(buf, "<wsdl:message name=|RequestSecurityTokenResponseMsg|>");
  ds_concat(buf, "<wsdl:part name=|response| type=|q1:MessageBody| />");
  ds_concat(buf, "</wsdl:message>");

  ds_concat(buf, "<wsdl:portType name=|SecurityTokenService|>");
  ds_concat(buf, "<wsdl:operation name=|Issue|>");
  ds_concat(buf, "<wsdl:input wsaw:Action=|http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue| message=|tns:RequestSecurityTokenMsg|>");
  ds_concat(buf, "</wsdl:input>");
  ds_concat(buf, "<wsdl:output wsaw:Action=|http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue| message=|tns:RequestSecurityTokenResponseMsg|>");
  ds_concat(buf, "</wsdl:output>");
  ds_concat(buf, "</wsdl:operation>");
  ds_concat(buf, "</wsdl:portType>");

  ds_concat(buf, "<wsp:Policy wsu:Id=|STS_endpoint_policy|>");
  ds_concat(buf, "<wsp:ExactlyOne>");
  ds_concat(buf, "<wsp:All>");

  /*
   * This informs the Identity Selector that this is an IP/STS rather than
   * a member of an RP/STS chain. (3.3, 4.2.1)
   * XXX should this be configurable?
   */
  ds_concat(buf, "<ic:RequireFederatedIdentityProvisioning />");

  ds_concat(buf, "<sp:TransportBinding>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:TransportToken>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:HttpsToken RequireClientCertificate=|false| />");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:TransportToken>");
  ds_concat(buf, "<sp:AlgorithmSuite>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:Basic256/>");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:AlgorithmSuite>");
  ds_concat(buf, "<sp:Layout>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:Strict/>");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:Layout>");
  ds_concat(buf, "<sp:IncludeTimestamp/>");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:TransportBinding>");

  if (authtype == INFOCARD_AUTHTYPE_PASSWD) {
	ds_concat(buf, "<sp:SignedSupportingTokens xmlns:sp=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:UsernameToken sp:IncludeToken=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:WssUsernameToken10/>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:UsernameToken>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:SignedSupportingTokens>");
  }
  else if (authtype == INFOCARD_AUTHTYPE_CERT) {
	ds_concat(buf, "<sp:EndorsingSupportingTokens xmlns:sp=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:X509Token sp:IncludeToken=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:WssX509V3Token10/>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:X509Token>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:EndorsingSupportingTokens>");
  }
  else if (authtype == INFOCARD_AUTHTYPE_CARD) {
	ds_concat(buf, "<sp:EndorsingSupportingTokens xmlns:sp=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy| xmlns:wst=|http://schemas.xmlsoap.org/ws/2005/02/trust|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:IssuedToken sp:IncludeToken=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient|>");
	ds_concat(buf, "<sp:Issuer>");
	ds_concat(buf, "<wsa:Address>");
	ds_concat(buf, "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self");
	ds_concat(buf, "</wsa:Address>");
	ds_concat(buf, "</sp:Issuer>");
	ds_concat(buf, "<sp:RequestSecurityTokenTemplate>");
	ds_concat(buf, "<wst:TokenType>");
	ds_concat(buf, "urn:oasis:names:tc:SAML:1.0:assertion");
	ds_concat(buf, "</wst:TokenType>");
	ds_concat(buf, "<wst:KeyType>");
	ds_concat(buf, "http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey");
	ds_concat(buf, "</wst:KeyType>");
	ds_concat(buf, "<wst:Claims xmlns:ic=|http://schemas.xmlsoap.org/ws/2005/05/identity|>");
	ds_concat(buf, "<ic:ClaimType Uri=|http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier|/>");
	ds_concat(buf, "</wst:Claims>");
	ds_concat(buf, "</sp:RequestSecurityTokenTemplate>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:RequireInternalReference/>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:IssuedToken>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:EndorsingSupportingTokens>");
  }
  else if (authtype == INFOCARD_AUTHTYPE_KERBEROS) {
	/* Not actually supported. */
	ds_concat(buf, "<sp:ProtectionToken>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp:KerberosToken sp:IncludeToken=|http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Once|>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "<sp: WssGssKerberosV5ApReqToken11/>");
	ds_concat(buf, "</wsp:Policy>");
	ds_concat(buf, "</sp:KerberosToken>");
	ds_concat(buf, "<wsp:Policy>");
	ds_concat(buf, "</sp:ProtectionToken>");
  }
  else {
	/* ??? */
	errmsg = "Unrecognized UserCredential type";
	goto fail;
  }

  ds_concat(buf, "<sp:Wss11>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:MustSupportRefThumbprint/>");
  ds_concat(buf, "<sp:MustSupportRefEncryptedKey/>");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:Wss11>");
  ds_concat(buf, "<sp:Trust10>");
  ds_concat(buf, "<wsp:Policy>");
  ds_concat(buf, "<sp:RequireClientEntropy/>");
  ds_concat(buf, "<sp:RequireServerEntropy/>");
  ds_concat(buf, "</wsp:Policy>");
  ds_concat(buf, "</sp:Trust10>");
  ds_concat(buf, "<wsaw:UsingAddressing wsdl:required=|true| />");
  ds_concat(buf, "</wsp:All>");
  ds_concat(buf, "</wsp:ExactlyOne>");
  ds_concat(buf, "</wsp:Policy>");

  ds_concat(buf, "<wsdl:binding name=|Transport_binding| type=|tns:SecurityTokenService|>");
  ds_concat(buf, "<wsp:PolicyReference URI=|#STS_endpoint_policy|/>");
  ds_concat(buf, "<soap12:binding transport=|http://schemas.xmlsoap.org/soap/http|/>");
  ds_concat(buf, "<wsdl:operation name=|Issue|>");
  ds_concat(buf, "<soap12:operation soapAction=|http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue| style=|document|/>");
  ds_concat(buf, "<wsdl:input>");
  ds_concat(buf, "<soap12:body use=|literal|/>");
  ds_concat(buf, "</wsdl:input>");
  ds_concat(buf, "<wsdl:output>");
  ds_concat(buf, "<soap12:body use=|literal|/>");
  ds_concat(buf, "</wsdl:output>");
  ds_concat(buf, "</wsdl:operation>");
  ds_concat(buf, "</wsdl:binding>");

  ds_concat(buf, "<wsdl:service name=|STS_0|>");
  ds_concat(buf, "<wsdl:port name=|STS_0_port| binding=|tns:Transport_binding|>");
  ds_asprintf(buf, "<soap12:address location=|%s| />", token_serviceurl);
  ds_concat(buf, "<wsa:EndpointReference>");
  ds_asprintf(buf, "<wsa:Address>%s</wsa:Address>", token_serviceurl);
  ds_concat(buf, "<wsid:Identity>");
  ds_concat(buf, "<ds:KeyInfo>");
  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) {
	errmsg = "INFOCARD_STS_CERTFILE must be defined";
	goto fail;
  }
  if ((ds = ds_load_file(NULL, certfile)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not load server cert from %s", certfile));
	exit(1);
  }
  cert = pem_cert_strip(ds);
  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, "</wsdl:port>");
  ds_concat(buf, "</wsdl:service>");

  ds_concat(buf, "</wsdl:definitions>");
  ds_concat(buf, "</MetadataSection>");

  ds_asprintf(buf, "<MetadataSection Dialect=|http://www.w3.org/2001/XMLSchema| Identifier=|%s|>", token_serviceurl);
  ds_asprintf(buf, "<xs:schema xmlns:tns=|%s| xmlns:xs=|http://www.w3.org/2001/XMLSchema| elementFormDefault=|qualified| targetNamespace=|%s|>",
			  token_serviceurl, token_serviceurl);
  ds_concat(buf, "<xs:complexType name=|MessageBody|>");
  ds_concat(buf, "<xs:sequence>");
  ds_concat(buf, "<xs:any maxOccurs=|unbounded| minOccurs=|0| namespace=|##any|/>");
  ds_concat(buf, "</xs:sequence>");
  ds_concat(buf, "</xs:complexType>");
  ds_concat(buf, "</xs:schema>");
  ds_concat(buf, "</MetadataSection>");

  ds_concat(buf, "</Metadata>");	
  ds_concat(buf, "</S:Body>");

  ds_concat(buf, "</S:Envelope>");

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

  /* Emit the message. */
  len = ds_len(buf);
  log_msg((LOG_TRACE_LEVEL, "Content-Length: %u", len));
  log_msg((LOG_TRACE_LEVEL, "%s", ds_buf(buf)));

  fprintf(stdout, "Content-Length: %u\n\n", (unsigned int) len);
  fprintf(stdout, "%s\n", ds_buf(buf));

  exit(0);
}

