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

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * dacs_select_credentials
 *
 * This web service selects/deselects credentials to make their
 * identities "current" with respect to access control.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2014\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: select_credentials.c 2710 2014-09-04 23:10:39Z brachman $";
#endif

#include "dacs.h"

static const char *log_module_name = "dacs_select_credentials";

int
main(int argc, char **argv)
{
  int i, ncred, nselected, select_unauth, xargc;
  char *cookie_syntax, *errmsg, *remote_addr, **xargv;
  char *jurisdiction, *operation, *username;
  unsigned int ncookies;
  Common_status common_status;
  Cookie *cookies;
  Credentials *cr, *credentials;
  Html_header_conf *hc;
  Kwv *kwv;
  Kwv_pair *k;
  Scredentials *selected;

  errmsg = "Internal error";
  hc = emit_html_header_conf(NULL);
  hc->no_cache = 1;

  xargc = argc;
  xargv = argv;
  dacs_init_allow_dups_default = 1;
  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (test_emit_xml_format()) {
	  emit_xml_header(stdout, "dacs_select_credentials");
	  printf("<%s>\n", make_xml_root_element("dacs_select_credentials"));
	  init_common_status(&common_status, NULL, NULL, errmsg);
	  fprintf(stdout, "%s", make_xml_common_status(&common_status));
	  printf("</dacs_select_credentials>\n");
	  emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_JSON)) {
	  emit_json_header(stdout, "dacs_select_credentials");
      init_common_status(&common_status, NULL, NULL, errmsg);
      fprintf(stdout, "%s", make_json_common_status(&common_status));
	  emit_json_trailer(stdout);
	}
	else {
	  hc->title = ds_xprintf("DACS Selected Credentials for Federation %s",
							 non_null(conf_val(CONF_FEDERATION_NAME)));
	  emit_html_header(stdout, hc);
	  printf("<p>dacs_select_credentials: fatal error.");
	  printf("<p>%s\n", errmsg);
	  emit_html_trailer(stdout);
	}
	exit(1);
  }

  if (should_use_argv) {
	if (argc > 1) {
	  log_msg((LOG_ERROR_LEVEL, "Bad parameter: '%s'", argv[1]));
	  log_msg((LOG_ERROR_LEVEL, "QUERY_STRING: '%s'",
			   getenv("QUERY_STRING")));

	  for (i = 0; i < xargc; i++)
		log_msg((LOG_ERROR_LEVEL, "Arg%d: %s", i, xargv[i]));
	  errmsg = "Usage: unrecognized parameter";
	  goto fail;
	}
  }

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	errmsg = "No REMOTE_ADDR found";
	goto fail;
  }

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

  ncred = get_valid_credentials(cookies, remote_addr, 0, &credentials);
  if (ncred == -1) {
	errmsg = "Error processing credentials";
	goto fail;
  }

  if (get_scredentials(cookies, &selected) == -1) {
	errmsg = "Error processing selected credentials";
	goto fail;
  }

  log_msg((LOG_TRACE_LEVEL, "Cookies=%d, Credentials=%d", ncookies, ncred));

  if ((k = kwv_lookup(kwv, "OPERATION")) == NULL) {
	errmsg = "No OPERATION argument provided";
	goto fail;
  }
  else {
	if (k->next != NULL) {
	  errmsg = "Duplicate OPERATION argument";
	  goto fail;
	}
	operation = k->val;
  }
  log_msg((LOG_TRACE_LEVEL, "Operation is %s", operation));

  if ((k = kwv_lookup(kwv, "COOKIE_SYNTAX")) != NULL) {
	if (k->next != NULL) {
	  errmsg = "Duplicate COOKIE_SYNTAX argument";
	  goto fail;
	}
	cookie_syntax = k->val;
	if (configure_cookie_syntax(cookie_syntax, &errmsg) == -1)
	  goto fail;
  }

  if (selected == NULL)
	log_msg((LOG_TRACE_LEVEL, "No selection is present"));
  else {
	if (selected->unauth != NULL)
	  log_msg((LOG_TRACE_LEVEL, "Unauth is selected"));
	else {
	  Scredentials_selected *s;

	  for (s = selected->selected; s != NULL; s = s->next)
		log_msg((LOG_TRACE_LEVEL, "Selected: %s",
				 auth_identity(NULL, s->jurisdiction, s->username, NULL)));
	}
  }

  /* Either or both of these may be missing. */
  username = kwv_lookup_value(kwv, "DACS_USERNAME");
  jurisdiction = kwv_lookup_value(kwv, "DACS_JURISDICTION");

  select_unauth = 0;
  if (credentials == NULL) {
	char *str;

	if (selected != NULL) {
	  make_set_void_selected_cookie_header(&str);
	  printf("%s", str);
	}
	nselected = 0;
  }
  else if (strcaseeq(operation, "DESELECT")) {
	int ndeselected;
	char *str;
	Scredentials_selected *s;

	/*
	 * This operation deselects credentials from the set of selected
	 * credentials according to the arguments:
	 * 1. If the arguments match selected credentials, those
	 *    credentials are deselected.  If, as a result, no credentials
	 *    remain selected, the result is that the user effectively becomes
	 *    unauthenticated and a selection cookie is issued that "hides" all
	 *    credentials.
	 * 2. If the arguments don't match selected credentials, the set of
	 *    selected credentials is unchanged.
	 */

	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	nselected = 0;
	if (selected != NULL) {
	  if (selected->unauth != NULL)
		nselected = 1;
	  else {
		for (s = selected->selected; s != NULL; s = s->next) {
		  for (cr = credentials; cr != NULL; cr = cr->next) {
			if (streq(cr->unique, s->unique)) {
			  cr->selected = 1;
			  nselected++;
			  break;
			}
		  }
		}
	  }
	}

	ndeselected = 0;
	for (cr = credentials; cr != NULL; cr = cr->next) {
	  if ((username == NULL || streq(cr->username, username))
		  && (jurisdiction == NULL
			  || streq(cr->home_jurisdiction, jurisdiction))) {
		if (cr->selected) {
		  cr->selected = 0;
		  ndeselected++;
		}
	  }
	}

	if (ndeselected) {
	  if (ndeselected == nselected) {
		make_set_scredentials_cookie_header(NULL, &str);
		select_unauth = 1;
	  }
	  else
		make_set_scredentials_cookie_header(credentials, &str);
	}
	else {
	  /* No argument matched... */
	  log_msg((LOG_TRACE_LEVEL,
			   "Ignoring request: arguments do not match any credentials"));
	  str = "";
	}

	printf("%s", str);
  }
  else if (strcaseeq(operation, "DESELECT_UNAUTH")) {
	char *str;

	/*
	 * This operation specifically deselects the "unauth" selection that
	 * hides all credentials.
	 */
	if (selected == NULL || selected->unauth == NULL) {
	  errmsg = "DESELECT_UNAUTH: unauth is not currently selected";
	  goto fail;
	}

	nselected = 0;
	make_set_void_selected_cookie_header(&str);
	printf("%s", str);
  }
  else if (strcaseeq(operation, "SELECT")) {
	char *str;

	/*
	 * This operation effectively replaces the current set of selected
	 * credentials (if any) with the set described by the arguments:
	 * 1. If the arguments match existing credentials, then those
	 *    credentials are selected, the other credentials are hidden.  A
	 *    selection cookie is issued that "hides" all other credentials.
	 * 2. Otherwise, the arguments do not match existing credentials.
	 *    This is considered to be an error.  Any existing selection is
	 *    left unchanged.
	 */
	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	nselected = 0;

	for (cr = credentials; cr != NULL; cr = cr->next) {
	  if ((username == NULL || streq(cr->username, username))
		  && (jurisdiction == NULL
			  || streq(cr->home_jurisdiction, jurisdiction))) {
		if (!cr->selected) {
		  cr->selected = 1;
		  nselected++;
		}
	  }
	}

	if (nselected == 0) {
	  errmsg = "SELECT: no credentials match";
	  goto fail;
	}

	log_msg((LOG_TRACE_LEVEL, "SELECT: selecting %d", nselected));
	make_set_scredentials_cookie_header(credentials, &str);

	printf("%s", str);
  }
  else if (strcaseeq(operation, "SELECT_UNAUTH")) {
	char *str;

	/*
	 * This operation specifically selects the "unauth" selection that
	 * hides all credentials.
	 */
	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	nselected = 1;
	select_unauth = 1;

	log_msg((LOG_TRACE_LEVEL, "SELECT_UNAUTH: selecting"));
	make_set_scredentials_cookie_header(NULL, &str);

	printf("%s", str);
  }
  else if (strcaseeq(operation, "ADD")) {
	int nadded;
	char *str;
	Scredentials_selected *s;

	/*
	 * This operation adds to the set of selected credentials, based
	 * on the arguments.  A new selection cookie is issued if the set
	 * changes.
	 * 1. If no arguments are given, the set of selected credentials is
	 *    unchanged.
	 * 2. If the arguments match selected credentials, those
	 *    credentials are also selected.
	 * 3. If no arguments match selected credentials, the set of
	 *    selected credentials is unchanged.
	 */

	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	nselected = 0;
	if (selected != NULL) {
	  if (selected->unauth != NULL)
		nselected = 1;
	  else {
		for (s = selected->selected; s != NULL; s = s->next) {
		  for (cr = credentials; cr != NULL; cr = cr->next) {
			if (streq(cr->unique, s->unique)) {
			  cr->selected = 1;
			  nselected++;
			  break;
			}
		  }
		}
	  }
	}

	nadded = 0;
	for (cr = credentials; cr != NULL; cr = cr->next) {
	  if ((username == NULL || streq(cr->username, username))
		  && (jurisdiction == NULL
			  || streq(cr->home_jurisdiction, jurisdiction))) {
		if (!cr->selected) {
		  cr->selected = 1;
		  nadded++;
		}
	  }
	}

	nselected += nadded;
	if (nadded == 0)
	  str = "\n";
	else {
	  log_msg((LOG_TRACE_LEVEL, "ADD: adding %d", nadded));
	  make_set_scredentials_cookie_header(credentials, &str);
	}

	printf("%s", str);
  }
  else if (strcaseeq(operation, "CLEAR")) {
	char *str;

	/*
	 * Reset everything - nothing selected and nothing hidden.
	 */
	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	nselected = 0;
	make_set_void_selected_cookie_header(&str);

	printf("%s", str);
  }
  else if (strcaseeq(operation, "LIST")) {
	/*
	 * This operation doesn't change the set of selected credentials,
	 * it merely lists them.
	 */
	nselected = 0;
	for (cr = credentials; cr != NULL; cr = cr->next)
	  cr->selected = 0;

	if (selected != NULL) {
	  if (selected->unauth != NULL) {
		select_unauth = 1;
		nselected = 1;
	  }
	  else {
		Scredentials_selected *ss;

		for (cr = credentials; cr != NULL; cr = cr->next) {
		  for (ss = selected->selected; ss != NULL; ss = ss->next) {
			if (streq(cr->unique, ss->unique)) {
			  cr->selected = 1;
			  nselected++;
			  break;
			}
		  }
		}
	  }
	}
  }
  else {
	errmsg = "Unrecognized OPERATION argument";
	goto fail;
  }

  if (test_emit_xml_format()) {
	emit_xml_header(stdout, "dacs_select_credentials");
	printf("<dacs_select_credentials>\n");
	printf("<ok>\n");
	if (select_unauth)
	  printf("<unauth/>\n");
	else if (nselected == 0)
	  ;
	else {
	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (cr->selected)
		  printf("<selected ");
		else
		  printf("<deselected ");
		printf("federation=\"%s\" jurisdiction=\"%s\" username=\"%s\"/>\n",
			   cr->federation, cr->home_jurisdiction, cr->username);
	  }
	}
	printf("</ok>\n");
	printf("</dacs_select_credentials>\n");
	emit_xml_trailer(stdout);
  }
  else if (test_emit_format(EMIT_FORMAT_JSON)) {
	emit_json_header(stdout, "dacs_select_credentials");
	printf("{ \"ok\":");

	if (select_unauth)
	  printf(" { \"unauth\":true }");
	else if (nselected == 0)
	  printf(" { }");
	else {
	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (cr != credentials)
		  printf(",");
		if (cr->selected)
		  printf(" { \"selected\": { ");
		else
		  printf(" { \"deselected\": { ");
		printf("\"federation\":\"%s\", \"jurisdiction\":\"%s\", \"username\":\"%s\" }}",
			   cr->federation, cr->home_jurisdiction, cr->username);
	  }
	}
	printf("}}\n");
	emit_json_trailer(stdout);
  }
  else {
	if (conf_val(CONF_CSS_PATH) != NULL)
	  hc->css = ds_xprintf("%s/dacs_select_credentials.css",
						   conf_val(CONF_CSS_PATH));
	else
	  hc->css = CSS_DIR/**/"/dacs_select_credentials.css";
	hc->title = ds_xprintf("DACS Selected Credentials for Federation %s",
						   non_null(conf_val(CONF_FEDERATION_NAME)));
	emit_html_header(stdout, hc);

	if (nselected == 0)
	  printf("<p>No credentials are selected.\n");
	else if (select_unauth)
	  printf("<p>You are effectively unauthenticated.\n");
	else {
	  printf("<p><b>Selected credentials</b>:\n");
	  printf("<ol>\n");
	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (cr->selected)
		  printf("<li><b>%s</b> within federation <b>%s</b></li>\n",
				 auth_identity(NULL, cr->home_jurisdiction,
							   cr->username, NULL), cr->federation);
	  }
	  printf("</ol>\n");
	}
    printf("</p>\n");
    emit_html_trailer(stdout);
  }

  exit(0);
}
