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

/*
 * PAM-based local authentication module
 */

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

#include "dacs.h"

#include <netdb.h>

static const char *log_module_name = "local_pam_auth";

/*
 * When a session with pamd is being established, HOSTNAME and PORT must
 * specify where the pamd server is running.
 * On subsequent calls during the session, AUTH_TRANSID must be provided and
 * HOSTNAME and PORT are ignored.
 *
 * Note that USERNAME may not have been provided by the first call, in which
 * case PAM will probably ask for it.
 */
int
local_pam_auth(Kwv *kwv_trans, char *username, char *hostname,
			   in_port_t port, char **mapped_username, char **lifetime,
			   char **xml, Pam_auth_tid **tidp, Auth_prompts **prompts)
{
  int i;
  char *p, *pam_host;
  in_port_t pam_port;
  Kwv_iter *iter;
  Kwv_pair *v;
  Pam_auth_tid *tid;

  log_msg((LOG_TRACE_LEVEL, "local_pam_auth begins..."));

  log_msg((LOG_DEBUG_LEVEL, "local_pam_auth transaction variables:"));
  iter = kwv_iter_begin(kwv_trans, NULL);
  i = 1;
  for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter))
	log_msg((LOG_DEBUG_LEVEL, "  %2d. %s=\"%s\"", i++, v->name, v->val));
  kwv_iter_end(iter);

  /*
   * If a session is being resumed, the (secret) transaction identifier must be
   * provided, otherwise this is a new session.
   */
  if ((p = kwv_lookup_value(kwv_trans, "AUTH_TRANSID")) != NULL) {
	if ((tid = pam_parse_tid(p)) == NULL)
	  return(-1);
	pam_host = tid->host;
	pam_port = tid->port;
  }
  else {
	pam_host = hostname;
	pam_port = port;
	tid = NULL;
  }

  if (pam_auth(kwv_trans, username, &tid, pam_host, pam_port,
			   mapped_username, lifetime, xml, prompts) == -1) {
	if (*mapped_username != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Auth failed for username \"%s\"",
			   *mapped_username));
	return(-1);
  }

  if (tidp != NULL)
	*tidp = tid;

  return(0);
}

#ifdef PROG
int
main(int argc, char **argv)
{
  int emitted_dtd, i;
  in_port_t port;
  char *errmsg, *hostname, *jurisdiction, *username, *password, *aux;
  char *lifetime, *mapped_username, *portname, *xml_prompt;
  Kwv *kwv, *kwv_trans;

  errmsg = "Internal error";
  username = password = aux = jurisdiction = NULL;
  emitted_dtd = 0;
  xml_prompt = NULL;
  mapped_username = NULL;

  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... */
	goto fail;
  }

  /* This must go after initialization. */
  emitted_dtd = emit_xml_header(stdout, "auth_reply");

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

  if ((kwv_trans = kwv_init(10)) == NULL) {

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

	if (emitted_dtd) {
	  printf("%s\n", make_xml_auth_reply_failed(mapped_username, NULL));
	  emit_xml_trailer(stdout);
	}
	log_msg((LOG_ERROR_LEVEL, "Failed: reason=%s", errmsg));
	exit(1);
  }

  for (i = 0; i < kwv->nused; i++) {
	if (streq(kwv->pairs[i]->name, "USERNAME") && username == NULL)
	  username = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "PASSWORD") && password == NULL)
	  password = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUXILIARY") && aux == NULL)
	  aux = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_JURISDICTION")
			 && jurisdiction == NULL)
	  jurisdiction = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_VERSION"))
	  ;
	else if (streq(kwv->pairs[i]->name, "AUTH_TRANSID")) {
	  kwv_add_pair(kwv_trans, kwv->pairs[i]);
	  log_msg((LOG_TRACE_LEVEL, "AUTH_TRANSID=\"%s\"", kwv->pairs[i]->val));
	}
	else if (strneq(kwv->pairs[i]->name,  AUTH_PROMPT_VAR_PREFIX,
					strlen(AUTH_PROMPT_VAR_PREFIX))
			 && is_digit_string(kwv->pairs[i]->name
								+ strlen(AUTH_PROMPT_VAR_PREFIX))) {
	  kwv_add_pair(kwv_trans, kwv->pairs[i]);
	  /* WARNING! THIS MAY LOG PASSWORDS! */
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "%s=\"%s\"", kwv->pairs[i]->name, kwv->pairs[i]->val));
	}
	else
	  log_msg((LOG_TRACE_LEVEL, "Parameter: '%s'", kwv->pairs[i]->name));
  }

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

  if ((hostname = conf_val(CONF_PAMD_HOST)) == NULL) {
	errmsg = "Can't find PAMD_HOST";
	goto fail;
  }

  /*
   * The port to use can be configured in case the admin would prefer not to
   * add an entry to /etc/services.
   */
  portname = NULL;
  if ((port = pam_get_pamd_port(portname, &errmsg)) == 0)
	goto fail;

  /*
   * XXX We might extend the protocol so that if a username is known
   * (either given or found from a cert), then we could pass that to
   * PAM and avoid having to prompt for it.
   */

  /* If this wasn't specified, dacs_authenticate will use the default. */
  lifetime = kwv_lookup_value(kwv, "CREDENTIALS_LIFETIME_SECS");

  mapped_username = NULL;
  if (local_pam_auth(kwv_trans, username, hostname, port,
					 &mapped_username, &lifetime, &xml_prompt,
					 NULL, NULL) == -1) {
	errmsg = "PAM authentication failed";
	goto fail;
  }

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

  if (xml_prompt == NULL) {
	Auth_reply_ok ok;

	ok.username = mapped_username;
	ok.lifetime = lifetime;
	ok.roles_reply = NULL;
	printf("%s\n", make_xml_auth_reply_ok(&ok));
  }
  else
	printf("%s\n", make_xml_auth_reply_prompt(xml_prompt));

  emit_xml_trailer(stdout);
  exit(0);
}

#endif
