/*
 * Copyright (c) 2003-2016
 * 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.
 ***************************************************************************/

/*
 * Signout (logoff/signout) service
 *
 * By specifying DACS_USERNAME, only logins under a username that exactly
 * matches DACS_USERNAME will be signed out.
 * By specifying DACS_JURISDICTION, only logins under a home jurisdiction
 * that exactly matches DACS_JURISDICTION will be signed out.
 * Both parameters are optional and both may be given.
 * Expired and otherwise invalid credentials are always deleted.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: signout.c 2933 2016-11-23 18:54:34Z brachman $";
#endif

#include "dacs.h"

static const char *log_module_name = "dacs_signout";

typedef enum {
  SIGNOUT_HANDLER_URL         = 1,
  SIGNOUT_HANDLER_DEFAULT_URL = 2,
  SIGNOUT_HANDLER_FILE        = 3,
  SIGNOUT_HANDLER_MESSAGE     = 4,
  SIGNOUT_HANDLER_CREDENTIALS = 5,
  SIGNOUT_HANDLER_NONE        = 6
} Signout_handler_type;

typedef struct Signout_handler {
  Signout_handler_type type;
  char *signout_string;
} Signout_handler;

static int emit_xml = 0;
static int emit_json = 0;

/*
 * Return the configured SIGNOUT_HANDLER, a default, or NULL if something
 * is broken.
 */
static Signout_handler *
lookup_signout_handler(void)
{
  int n;
  char **argv;
  Kwv_pair *v;
  Mkargv conf = { 1, 0, NULL, NULL, NULL };
  static Signout_handler current;

  current.type = SIGNOUT_HANDLER_CREDENTIALS;
  current.signout_string = NULL;

  if ((v = conf_var(CONF_SIGNOUT_HANDLER)) == NULL)
	return(&current);

  if ((n = mkargv(v->val, &conf, &argv)) != 1 && n != 2) {
	log_msg((LOG_ERROR_LEVEL, "Invalid directive: %s", v->val));
	return(NULL);
  }

  if (n == 2) {
	current.signout_string = argv[1];
	if (strcaseeq(argv[0], "url"))
	  current.type = SIGNOUT_HANDLER_URL;
	else if (strcaseeq(argv[0], "defaulturl"))
	  current.type = SIGNOUT_HANDLER_DEFAULT_URL;
	else if (strcaseeq(argv[0], "file"))
	  current.type = SIGNOUT_HANDLER_FILE;
	else if (strcaseeq(argv[0], "message"))
	  current.type = SIGNOUT_HANDLER_MESSAGE;
	else {
	  log_msg((LOG_ERROR_LEVEL,
			   "Error parsing SIGNOUT_HANDLER directive: %s", v->val));
	  return(NULL);
	}

	log_msg((LOG_TRACE_LEVEL, "Using signout handler: %s %s",
			 argv[0], argv[1]));
  }
  else {
	/* The default URL case is not possible in the implicit syntax. */
	current.signout_string = argv[0];
	if (strcaseprefix(argv[0], "http"))
	  current.type = SIGNOUT_HANDLER_URL;
	else if (argv[0][0] == '/')
	  current.type = SIGNOUT_HANDLER_FILE;
	else if (strcaseeq(argv[0], "credentials"))
	  current.type = SIGNOUT_HANDLER_CREDENTIALS;
	else if (argv[0][0] == '"')
	  current.type = SIGNOUT_HANDLER_MESSAGE;
	else {
	  log_msg((LOG_ERROR_LEVEL,
			   "Error parsing SIGNOUT_HANDLER directive: %s", v->val));
	  return(NULL);
	}

	log_msg((LOG_TRACE_LEVEL, "Using signout handler: %s", argv[0]));
  }

  return(&current);
}

static int
void_cookie(char **set_cookie_buf, Credentials *credentials)
{
  char *name;

  log_msg((LOG_INFO_LEVEL, "%s:%s",
		   credentials->home_jurisdiction, credentials->username));

  name = credentials->cookie_name;

  return(make_set_void_cookie_header(name, 0, set_cookie_buf));
}

int
main(int argc, char **argv)
{
  int handler_error;
  char *bp, *jurisdiction, *username;
  char *dacs_signout_handler, *cookie_syntax;
  Kwv *kwv;
  char *errmsg;
  Credentials *cr, *credentials;
  Cookie *c, *cookies;
  unsigned int ncookies, ndeleted, nignored, ninvalid, nselected;
  Signout_handler *handler;

  errmsg = "Internal error";
  handler_error = 0;
  emit_json = 0;
  emit_xml = 0;
  dacs_signout_handler = NULL;

  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1)
	goto fail;

  emit_xml = test_emit_xml_format();
  emit_json = test_emit_format(EMIT_FORMAT_JSON);

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

  username = kwv_lookup_value(kwv, "DACS_USERNAME");
  jurisdiction = kwv_lookup_value(kwv, "DACS_JURISDICTION");
  dacs_signout_handler = kwv_lookup_value(kwv, "DACS_SIGNOUT_HANDLER");

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

#ifdef NOTDEF
  /*
   * XXX This shortcut was deleted so that 1) a handler will still be
   * called if configured and 2) the case where there are no cookies and
   * the case where there are cookies but none result in a signout result
   * in the same behaviour.
   */
  if (ncookies == 0) {
	/* Nothing to do. */
	if (emit_xml) {
	  emit_xml_header(stdout, "dacs_current_credentials");
	  printf("%s", make_xml_dacs_current_credentials(NULL, NULL, 0));
	  emit_xml_trailer(stdout);
	}
	else if (emit_json) {
	  emit_json_header(stdout, "dacs_current_credentials");
	  printf("%s", make_json_dacs_current_credentials(NULL, NULL, 0));
	  emit_json_trailer(stdout);
	}
	else {
	  emit_html_header(stdout, NULL);
	  printf("<p>You are not authenticated within federation <b>%s</b>.\n",
			 conf_val(CONF_FEDERATION_NAME));
	  emit_html_trailer(stdout);
	}
	exit(0);
  }
#endif

  if ((cookie_syntax = kwv_lookup_value(kwv, "COOKIE_SYNTAX")) != NULL) {
	if (configure_cookie_syntax(cookie_syntax, &errmsg) == -1)
	  goto fail;
  }

  credentials = NULL;
  ndeleted = ninvalid = nignored = nselected = 0;

  for (c = cookies; c != NULL; c = c->next) {
	if (is_selected_cookie_name(c->name)) {
	  nselected++;
	  continue;
	}

	if (!is_auth_cookie_name(c->name)) {
	  /* What is this cookie? */
	  nignored++;
	  continue;
	}

	/* Try to delete any invalid cookies. */
	if (cookie_to_credentials(c, NULL, &cr) == -1) {
	  if (cr != NULL) {
		void_cookie(&bp, cr);
		printf("%s", bp);
	  }
	  ninvalid++;
	  continue;
	}

	if ((username == NULL
		 || name_eq(username, cr->username, DACS_NAME_CMP_CONFIG))
		&& (jurisdiction == NULL
			|| name_eq(jurisdiction, cr->home_jurisdiction,
					   DACS_NAME_CMP_CONFIG))) {
	  void_cookie(&bp, cr);
	  printf("%s", bp);
	  cr->expires_secs = 0;
	  ndeleted++;
	  log_msg((LOG_DEBUG_LEVEL | LOG_AUDIT_FLAG,
			   "Signout succeeded for %s",
			   auth_identity_from_credentials_track(cr)));
#ifdef ENABLE_USER_INFO
	  user_info_signout(cr);
#endif
	}

	/* Link together the remaining credentials. */
	if (credentials == NULL)
	  cr->next = NULL;
	else
	  cr->next = credentials;
	credentials = cr;
  }

  /*
   * If we're deleting the last set of credentials, only then remove the
   * selected credentials cookie.
   */
  if (nselected
	  && ((username == NULL && jurisdiction == NULL) || credentials == NULL)) {
	make_set_void_selected_cookie_header(&bp);
	printf("%s", bp);
  }

  /*
   * Check if an XML or JSON reply was requested instead of HTML.
   * XXX it might be useful to include the redirect URL in the returned
   * document if it has been configured
   */
  if (emit_xml) {
	emit_xml_header(stdout, "dacs_current_credentials");
	printf("%s", make_xml_dacs_current_credentials(credentials, NULL, 0));
	emit_xml_trailer(stdout);

	exit(0);
  }

  if (emit_json) {
	emit_json_header(stdout, "dacs_current_credentials");
	printf("%s", make_json_dacs_current_credentials(credentials, NULL, 0));
	emit_json_trailer(stdout);

	exit(0);
  }

  /*
   * What follows is only for HTML output.
   */

  /*
   * Get the configured handler or a default.
   * If NULL is returned, something bad happened.
   */
  if ((handler = lookup_signout_handler()) == NULL) {
	log_msg((LOG_ALERT_LEVEL, "lookup_signout_handler() failed?!"));
	errmsg = "Configuration error";
	goto fail;
  }

  /*
   * If the SIGNOUT_HANDLER is configured for SIGNOUT_HANDLER_DEFAULT_URL,
   * use the URL given by the DACS_SIGNOUT_HANDLER parameter when present,
   * otherwise use the configured URL.
   * In all other cases a DACS_SIGNOUT_HANDLER parameter is silently ignored.
   * No validity check is performed on DACS_SIGNOUT_HANDLER.
   */
  if (handler->type == SIGNOUT_HANDLER_DEFAULT_URL
	  || handler->type == SIGNOUT_HANDLER_URL) {
	Ds ds;
	char *p, *url;

	if (handler->type == SIGNOUT_HANDLER_DEFAULT_URL
		&& dacs_signout_handler != NULL)
	  url = dacs_signout_handler;
	else
	  url = handler->signout_string;

	ds_init(&ds);
	ds_asprintf(&ds, "%s%s", url,
				(strchr(url, '?') != NULL) ? "&" : "?");
	ds_asprintf(&ds, "DACS_VERSION=%s", DACS_VERSION_NUMBER);

	if ((p = conf_val(CONF_FEDERATION_NAME)) != NULL)
	  ds_asprintf(&ds, "&DACS_FEDERATION=%s", p);

	if ((p = conf_val(CONF_JURISDICTION_NAME)) != NULL)
	  ds_asprintf(&ds, "&DACS_JURISDICTION=%s", p);

	ds_asprintf(&ds, "&DACS_SIGNOUT_RESULT=%d", ndeleted);

	emit_http_header_redirect(stdout, ds_buf(&ds));
  }
  else if (handler->type == SIGNOUT_HANDLER_FILE) {
	char *ptr;
	size_t file_size;

	if (load_file(handler->signout_string, &ptr, &file_size) == -1) {
	  log_msg((LOG_ALERT_LEVEL,
			   "Could not load file \"%s\" for SIGNOUT_HANDLER",
			   handler->signout_string));
	  handler_error = 1;
	}
	else {
	  fflush(stdout);
	  /* XXX Errors ignored... */
	  write_buffer(fileno(stdout), ptr, file_size);
	}
  }
  else if (handler->type == SIGNOUT_HANDLER_MESSAGE) {
	emit_html_header(stdout, NULL);
	printf("%s\n", handler->signout_string);
	emit_html_trailer(stdout);
  }

  if (handler_error || handler->type == SIGNOUT_HANDLER_CREDENTIALS) {
	emit_html_header(stdout, NULL);
	if (ndeleted) {
	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (cr->expires_secs == 0) {
		  if (!streq(cr->federation, conf_val(CONF_FEDERATION_NAME)))
			printf("<p>You have signed off as <tt><b>%s</b></tt><br>",
				   auth_identity(cr->federation, cr->home_jurisdiction,
								 cr->username, NULL));
		  else
			printf("<p>You have signed off as <tt><b>%s</b></tt><br>",
				   auth_identity(NULL, cr->home_jurisdiction, cr->username,
								 NULL));
		}
	  }
	}

	if (ncookies - ndeleted - ninvalid) {
	  printf("<P>");
	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (cr->expires_secs != 0)
		  printf("<b>You are still authenticated as <tt><b>%s</b></tt><br>",
				 auth_identity(cr->federation, cr->home_jurisdiction,
							   cr->username, NULL));
	  }
	}
	else
	  printf("<p>You are not authenticated within federation <b>%s</b>.\n",
			 conf_val(CONF_FEDERATION_NAME));

	emit_html_trailer(stdout);
  }

  exit(0);

 fail:
  if (errmsg)
	log_msg((LOG_ERROR_LEVEL, "Failed: %s", errmsg));

  if (emit_xml) {
	Common_status status;

	emit_xml_header(stdout, "dacs_current_credentials");
	fprintf(stdout, "<%s", make_xml_root_element("dacs_current_credentials"));
	init_common_status(&status, NULL, NULL, errmsg);
	fprintf(stdout, "%s", make_xml_common_status(&status));
	fprintf(stdout, "</dacs_current_credentials>\n");
	emit_xml_trailer(stdout);
  }
  else if (emit_json) {
	Common_status status;

	emit_json_header(stdout, "dacs_current_credentials");
	init_common_status(&status, NULL, NULL, errmsg);
	printf("%s", make_json_common_status(&status));
	emit_json_trailer(stdout);
  }
  else {
	emit_html_header(stdout, NULL);
	printf("<p>An error occurred during the service request.\n");
	if (errmsg != NULL)
	  printf("<p>Reason: %s.\n", errmsg);
	emit_html_trailer(stdout);
  }

  exit(1);
}
