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

/*
 * Generic HTTP authentication, such as for use against a Google (tm) account
 * (deprecated by Google in 2012).
 *
 * See:
 *    http://code.google.com/apis/accounts/AuthForInstalledApps.html
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: local_http_auth.c 2785 2015-02-25 20:52:17Z brachman $";
#endif

#include "dacs.h"

static const char *log_module_name = "local_http_auth";

/*
 * Authenticate against a web-based authentication service.
 * Return 0 if authentication succeeds, -1 otherwise.
 *
 * If the web service succeeds (status == 200) and returns a valid auth_reply
 * document, and VALID_AUTH_REPLY is non-NULL, return the parsed document.
 *
 * Possible enhancement: optional regex that must match reply for
 * auth to succeed (or fail) in addition to the 200 status code.
 */
int
local_http_auth(char *username, char *password, char *aux, Url_auth *url_auth,
				Auth_reply **valid_auth_reply)
{
  int argnum, i, reply_len, status_code;
  char *cookies[2], *reply;
  Dsvec *dsv, *response_headers;
  Http_params *params;

  if (username == NULL || password == NULL || url_auth == NULL
	  || url_auth->url == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Required argument is missing"));
	return(-1);
  }

  dsv = dsvec_init_size(NULL, sizeof(Http_params), 5);
  argnum = 0;
  params = http_param(dsv, NULL, username, NULL, 0);
  params->name = (url_auth->username_parameter != NULL)
	? url_auth->username_parameter : "USERNAME";
  params->filename = NULL;
  argnum++;

  params = http_param(dsv, NULL, password, NULL, 0);
  params->name = (url_auth->password_parameter != NULL)
	? url_auth->password_parameter : "PASSWORD";
  argnum++;

  for (i = 0; i < dsvec_len(url_auth->options); i++) {
	char *option;

	params = http_param(dsv, NULL, NULL, NULL, 0);
	option = dsvec_ptr(url_auth->options, i, char *);
	if (kwv_parse_str(option, &params->name, &params->value) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid option: \"%s\"", option));
	  return(-1);
	}
	argnum++;
  }

  reply = NULL;
  reply_len = -1;
  cookies[0] = NULL;
  response_headers = dsvec_init(NULL, sizeof(char *));

  if (http_invoke(url_auth->url, url_auth->method,
				  ssl_verify ? HTTP_SSL_ON_VERIFY
				  : (use_ssl ? HTTP_SSL_ON : HTTP_SSL_URL_SCHEME),
				  argnum, (Http_params *) dsvec_base(dsv), NULL, cookies,
				  &reply, &reply_len, &status_code,
				  response_headers) == -1 || status_code != 200) {
	if (reply != NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Web service failed with status %d and document:", status_code));
	  log_msg((LOG_ERROR_LEVEL, "%s", reply));
	}
	return(-1);
  }

  /*
   * The web service request succeeded (the status was 200).
   *
   * If VALID_AUTH_REPLY is NULL, the caller does not care whether a document is
   * present or if that document is valid, so authentication has succeeded.
   *
   * If VALID_AUTH_REPLY is non-NULL, an auth_reply document must be present,
   * syntactically valid, and will indicate success or failure of
   * authentication; a valid document will always be returned regardless of
   * the final outcome.
   * The caller must perform additional validation on the document, such as
   * whether the returned username is suitable.
   */
  if (valid_auth_reply != NULL) {
	Auth_reply *auth_reply;

	*valid_auth_reply = NULL;
	if (reply == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No auth_reply document is present"));
	  return(-1);
	}

	if (parse_xml_auth_reply(reply, &auth_reply) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid auth_reply document"));
	  return(-1);
	}

	log_msg((LOG_DEBUG_LEVEL, "Valid auth_reply document is present"));
	*valid_auth_reply = auth_reply;
	if (auth_reply->ok == NULL) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "auth_reply document says authentication failed"));
	  return(-1);
	}
	log_msg((LOG_DEBUG_LEVEL,
			 "auth_reply document says authentication succeeded"));
  }
  else
	log_msg((LOG_DEBUG_LEVEL, "auth_reply document is not needed"));

  return(0);
}

#ifdef PROG
int
main(int argc, char **argv)
{
  int emitted_dtd, i, need_auth_reply, st;
  char *aux, *errmsg, *jurisdiction, *username, *password, *use_auth_reply;
  Auth_reply *auth_reply;
  Auth_reply_ok ok;
  Kwv *kwv;
  Url_auth url_auth;

  emitted_dtd = 0;
  errmsg = "internal";
  username = password = aux = jurisdiction = NULL;
  use_auth_reply = NULL;
  auth_reply = NULL;

  dacs_init_allow_dups_default = 0;
  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 (password != NULL)
	  strzap(password);
	if (aux != NULL)
	  strzap(aux);
	if (emitted_dtd) {
	  printf("%s\n", make_xml_auth_reply_failed(NULL, NULL));
	  emit_xml_trailer(stdout);
	}
	if (errmsg != NULL)
	  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;
  }

  url_auth.method = HTTP_POST_METHOD;
  url_auth.url = NULL;
  url_auth.username_parameter = NULL;
  url_auth.password_parameter = NULL;
  url_auth.options = NULL;

  for (i = 0; i < kwv->nused; i++) {
	if (streq(kwv->pairs[i]->name, "USERNAME"))
	  username = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "PASSWORD"))
	  password = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUXILIARY"))
	  aux = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_JURISDICTION"))
	  jurisdiction = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_VERSION"))
	  ;
	else if (streq(kwv->pairs[i]->name, "AUTH_URL"))
	  url_auth.url = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUTH_METHOD")) {
	  if ((url_auth.method = http_string_to_method(kwv->pairs[i]->val))
		  == HTTP_UNKNOWN_METHOD) {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized AUTH_METHOD: \"%s\"",
				 kwv->pairs[i]->val));
		goto fail;
	  }
	}
	else if (streq(kwv->pairs[i]->name, "USE_AUTH_REPLY"))
	  use_auth_reply = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "USERNAME_PARAMETER")
			 && url_auth.username_parameter == NULL)
	  url_auth.username_parameter = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "PASSWORD_PARAMETER")
			 && url_auth.password_parameter == NULL)
	  url_auth.password_parameter = kwv->pairs[i]->val;
	else {
	  if (url_auth.options == NULL)
		url_auth.options = dsvec_init(NULL, sizeof(char *));

	  dsvec_add_ptr(url_auth.options,
					ds_xprintf("%s=%s", kwv->pairs[i]->name,
							   kwv->pairs[i]->val));
	}
  }

  if (url_auth.url == NULL) {
	errmsg = "Require AUTH_URL argument to be specified";
	goto fail;
  }

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

  if (use_auth_reply != NULL
	  && (strcaseeq(use_auth_reply, "yes") || strcaseeq(use_auth_reply, "on")))
	need_auth_reply = 1;
  else
	need_auth_reply = 0;

  if (need_auth_reply)
	st = local_http_auth(username, password, aux, &url_auth, &auth_reply);
  else
	st = local_http_auth(username, password, aux, &url_auth, NULL);
  if (st == -1) {
    errmsg = "Username/Password/Aux incorrect";
	goto fail;
  }

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

  if (auth_reply != NULL) {
	ok.username = auth_reply->ok->username;
	if (auth_reply->ok->lifetime != NULL && auth_reply->ok->lifetime[0] != '\0')
	  ok.lifetime = auth_reply->ok->lifetime;
	else
	  ok.lifetime = kwv_lookup_value(kwv, "CREDENTIALS_LIFETIME_SECS");
	ok.roles_reply = auth_reply->ok->roles_reply;
  }
  else {
	/* XXX Map it? */
	ok.username = 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;
  }

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

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