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

/*
 * XML parsing common to multiple message types
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: commonxml.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

#include "auth.h"

static char *log_module_name = "commonxml";

Parse_attr_tab common_status_attr_tab[] = {
  { "context",    NULL, ATTR_REQUIRED,  NULL, 0 },
  { "code",       NULL, ATTR_REQUIRED,  NULL, 0 },
  { "message",    NULL, ATTR_REQUIRED,  NULL, 0 },
  { XMLNS_XSI,    NULL, ATTR_IGNORE_NS, NULL, 0 },
  { XMLNS_PREFIX, NULL, ATTR_IGNORE_NS, NULL, 0 },
  { NULL,         NULL, ATTR_END,       NULL, 0 }
};

Common_status *
init_common_status(Common_status *status, char *context, char *code,
				   char *message)
{
  Common_status *cs;

  if (status == NULL)
	cs = ALLOC(Common_status);
  else
	cs = status;

  if (context == NULL)
	cs->context = ds_xprintf("%d", dacs_app_type);
  else
	cs->context = strdup(context);

  if (code == NULL)
	cs->code = ds_xprintf("%d", DACS_ERROR_INTERNAL);
  else
	cs->code = strdup(code);

  if (message == NULL)
	cs->message = strdup("An unknown error occurred");
  else
	cs->message = strdup(message);

  return(cs);
}

char *
make_xml_common_status(Common_status *status)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "<common_status");
  ds_asprintf(&ds, " context=\"%s\"",
			  status->context == NULL ? "" : status->context);
  ds_asprintf(&ds, " code=\"%s\"",
			  status->code == NULL ? "" : status->code);
  ds_asprintf(&ds, " message=\"%s\"",
			  status->message == NULL ? ""
			  : xml_attribute_value_encode(status->message, '"'));
  ds_asprintf(&ds, "/>\n");

  return(ds_buf(&ds));
}

static Dsvec *xmlns_decls = NULL;

int
make_xml_init_xmlns(void)
{

  if (xmlns_decls != NULL) {
	dsvec_free(xmlns_decls);
	xmlns_decls = NULL;
  }

  return(0);
}

int
make_xml_add_xmlns(char *ns)
{

  if (xmlns_decls == NULL)
	xmlns_decls = dsvec_init(NULL, sizeof(char *));

  dsvec_add_ptr(xmlns_decls, strdup(ns));

  return(0);
}

char *
make_xml_root_element(char *root)
{
  unsigned int ui;
  Ds ds;

  if (!test_emit_xml_format())
	return(NULL);

  ds_init(&ds);
  ds_asprintf(&ds, "%s", root);
  if (test_emit_format(EMIT_FORMAT_XML)
	  || test_emit_format(EMIT_FORMAT_XMLSCHEMA)
	  || test_emit_format(EMIT_FORMAT_XMLDTD))
	ds_asprintf(&ds, " xmlns=\"%s\"", parse_xml_xmlns());

  if (test_emit_format(EMIT_FORMAT_XMLSCHEMA)) {
	char *xsd_base_url;

	if ((xsd_base_url = conf_val(CONF_XSD_BASE_URL)) != NULL) {
	  ds_asprintf(&ds, " xmlns:xsi=\"%s\"", XMLNS_XSI);
	  ds_asprintf(&ds, " xsi:schemaLocation=\"%s %s/%s.xsd\"",
				  parse_xml_xmlns(), xsd_base_url, root);
	}

	for (ui = 0; xmlns_decls != NULL && ui < dsvec_len(xmlns_decls); ui++)
	  ds_asprintf(&ds, " %s", dsvec_ptr(xmlns_decls, ui, char *));
  }

  return(ds_buf(&ds));
}

char *
make_xml_roles_reply(Roles_reply *rr)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "<%s>", make_xml_root_element("roles_reply"));
  if (rr->ok != NULL)
	ds_asprintf(&ds, "<ok roles=\"%s\"/>",
				rr->ok->roles == NULL ? "" : rr->ok->roles);
  else if (rr->failed != NULL)
	ds_asprintf(&ds, "<failed reason=\"%s\"/>", rr->failed->reason);
  else if (rr->status != NULL)
	ds_asprintf(&ds, "%s", make_xml_common_status(rr->status));
  else {
	/* ??? */
	return(NULL);
  }

  ds_asprintf(&ds, "</roles_reply>\n");

  return(ds_buf(&ds));
}

char *
make_xml_auth_reply_ok(Auth_reply_ok *ok)
{
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<%s>", make_xml_root_element("auth_reply"));
  ds_asprintf(ds, "<ok username=\"%s\"", ok->username);
  if (ok->lifetime != NULL && ok->lifetime[0] != '\0')
	ds_asprintf(ds, " lifetime=\"%s\"", ok->lifetime);
  if (ok->roles_reply == NULL)
	ds_asprintf(ds, "/>");
  else
	ds_asprintf(ds, ">%s</ok>", make_xml_roles_reply(ok->roles_reply));
  ds_asprintf(ds, "</auth_reply>");

  return(ds_buf(ds));
}

char *
make_xml_auth_reply_failed(char *username, char *redirect_url)
{
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<%s>", make_xml_root_element("auth_reply"));
  ds_asprintf(ds, "<failed");
  if (username != NULL && username[0] != '\0')
	ds_asprintf(ds, " username=\"%s\"", username);
  if (redirect_url != NULL && redirect_url[0] != '\0')
	ds_asprintf(ds, " redirect_url=\"%s\"", redirect_url);
  ds_asprintf(ds, "/></auth_reply>");

  return(ds_buf(ds));
}

char *
make_xml_auth_reply_status(char *context, char *code, char *message)
{
  Common_status status;
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<%s>", make_xml_root_element("auth_reply"));
  init_common_status(&status, context, code, message);
  ds_asprintf(ds, "%s", make_xml_common_status(&status));
  ds_asprintf(ds, "</auth_reply>");

  return(ds_buf(ds));
}

char *
make_xml_auth_reply_prompt(char *prompt)
{
  Ds *ds;

  ds = ds_init(NULL);
  ds_asprintf(ds, "<%s>", make_xml_root_element("auth_reply"));
  ds_asprintf(ds, "%s", prompt);
  ds_asprintf(ds, "</auth_reply>");

  return(ds_buf(ds));
}

/*
 * Use CREDENTIALS to compose an XML credentials string.
 * Store the string in CREDENTIALS_STR.
 * Return the length of the string.
 *
 * Note that the order of attribute specifications in a start-tag or
 * empty-element tag is not significant.  For added security, we might
 * shuffle their order.  Currently, some parsing routines insist on a
 * particular order, however.
 */
int
make_xml_credentials(Credentials *cr, char **credentials_str)
{
  char *auth_style_str;
  Ds s;

  ds_init_size(&s, 128);
  ds_asprintf(&s, "<credentials");
  ds_asprintf(&s, " federation=\"%s\"", cr->federation);
  ds_asprintf(&s, " username=\"%s\"", cr->username);
  ds_asprintf(&s, " jurisdiction=\"%s\"", cr->home_jurisdiction);
  ds_asprintf(&s, " ip=\"%s\"", cr->ip_address);
  ds_asprintf(&s, " roles=\"%s\"", cr->role_str);
  ds_asprintf(&s, " auth_time=\"%lu\"", cr->auth_time);
  ds_asprintf(&s, " expires=\"%lu\"", cr->expires_secs);
  auth_style_str = auth_style_to_string(cr->auth_style);
  ds_asprintf(&s, " auth_style=\"%s\"", auth_style_str);
  ds_asprintf(&s, " unique=\"%s\"", cr->unique);
  ds_asprintf(&s, " version=\"%s\"", cr->version);
  ds_asprintf(&s, " valid_for=\"%s\"", cr->valid_for);
  if (cr->imported_by != NULL)
	ds_asprintf(&s, " imported_by=\"%s\"", cr->imported_by);
  if (cr->ua_hash != NULL)
	ds_asprintf(&s, " ua_hash=\"%s\"", cr->ua_hash);
  s.exact_flag = 1;
  ds_asprintf(&s, "/>");

  *credentials_str = (char *) s.buf;

  return(ds_len(&s));
}

char *
make_xml_dacs_current_credentials(Credentials *selected, Dsvec *dsv_activity,
								  int detailed)
{
  Credentials *cr;
  Ds s;

  ds_init(&s);
  ds_asprintf(&s, "<%s", make_xml_root_element("dacs_current_credentials"));
  ds_asprintf(&s, " federation_name=\"%s\"", conf_val(CONF_FEDERATION_NAME));
  ds_asprintf(&s,
			  " federation_domain=\"%s\"", conf_val(CONF_FEDERATION_DOMAIN));
  ds_asprintf(&s, ">\n");

  for (cr = selected; cr != NULL; cr = cr->next) {
	/*
	 * If expires_secs is zero, then these credentials have been
	 * deleted (refer to signout.c).
	 */
	if (cr->expires_secs == 0)
	  continue;
	ds_asprintf(&s, "<credentials ");
	ds_asprintf(&s, "federation=\"%s\" ", cr->federation);
	ds_asprintf(&s, "jurisdiction=\"%s\" ", cr->home_jurisdiction);
	ds_asprintf(&s, "name=\"%s\" ", cr->username);
	ds_asprintf(&s, "roles=\"%s\" ", cr->role_str);
	ds_asprintf(&s, "auth_style=\"%s\" ",
				auth_style_to_string(cr->auth_style));
	ds_asprintf(&s, "cookie_name=\"%s\"", cr->cookie_name);

	if (detailed) {
	  struct tm *tm;

	  ds_asprintf(&s, " valid_for=\"%s\" ", cr->valid_for);
	  if (cr->imported_by != NULL)
		ds_asprintf(&s, "imported_by=\"%s\" ", cr->imported_by);
	  ds_asprintf(&s, "ip_address=\"%s\" ", cr->ip_address);
	  ds_asprintf(&s, "version=\"%s\" ", cr->version);
	  tm = localtime(&cr->auth_time);
	  ds_asprintf(&s, "auth_time=\"%d (%s)\" ",
				  cr->auth_time, make_short_local_date_string(tm));
	  tm = localtime(&cr->expires_secs);
	  ds_asprintf(&s, "expires_secs=\"%d (%s)\" ",
				  cr->expires_secs, make_short_local_date_string(tm));
	  if (cr->ua_hash != NULL)
		ds_asprintf(&s, "ua_hash=\"%s\" ", cr->ua_hash);
	  
	  if (dsv_activity == NULL)
		ds_asprintf(&s, "/>\n");
	  else {
		int i;
		char *ident_full;
		Dsvec *dsv_active, *dsv_auth;
		User_info *ui;

		ident_full = auth_identity(cr->federation, cr->home_jurisdiction,
								   cr->username, NULL);

		ds_asprintf(&s, ">\n");
		dsv_active = user_info_active(dsv_activity, ident_full);
		dsv_auth = user_info_last_auth(dsv_activity, ident_full);
		if (dsv_auth != NULL && dsvec_len(dsv_auth) > 1) {
		  ui = (User_info *) dsvec_ptr_index(dsv_auth, 1);
		  ds_asprintf(&s,
					  "<previous_auth auth_time=\"%s\" ip_address=\"%s\"/>\n",
					  ui->info.auth.auth_time, ui->info.auth.ip);
		}
		else
		  ds_asprintf(&s, "<previous_auth/>\n");

		if (dsvec_len(dsv_active) > 1) {
		  for (i = 0; i < dsvec_len(dsv_active) - 1; i++) {
			ui = (User_info *) dsvec_ptr_index(dsv_active, i);
			ds_asprintf(&s,
						"<active_auth auth_time=\"%s\" ip_address=\"%s\"/>\n",
						ui->info.auth.auth_time, ui->info.auth.ip);
		  }
		}

		ds_asprintf(&s, "</credentials>\n");
	  }
	}
	else
	  ds_asprintf(&s, "/>\n");
  }

  ds_asprintf(&s, "</dacs_current_credentials>\n");
  return(ds_buf(&s));
}
