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

/*
 * One-time passwords (OTPs)
 * This module is used in conjunction with the dacstoken utility.
 *
 * Inputs: USERNAME, from which state is retrieved and updated,
 * and a user's one-time password (PASSWORD).
 *
 * If PASSWORD does not match the expected password, then we look within
 * a configurable symmetric window size for the value in case we have become
 * unsynchronized with the client.
 */

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

#include "dacs.h"

static const char *log_module_name = "local_token_auth";

/*
 * Test if USERNAME's next token value (PASSWORD) is correct.
 * If AUX is present, it is a PIN value.
 * Return 0 if authentication succeeds, -1 otherwise.
 */
int
local_token_auth(char *username, char *password, char *aux, int accept_window)
{
  int st;
  Auth_token *token;
  Proc_lock *lock;

  if ((lock = proc_lock_create(PROC_LOCK_TOKEN)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not obtain lock"));
	return(-1);
  }

  st = -1;
  if ((token = auth_token_get("auth_token", username)) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Could not find account for user \"%s\"", username));
	goto done;
  }

  if (token->mode & TOKEN_MODE_DISABLED) {
	log_msg((LOG_ERROR_LEVEL,
			 "Account for user \"%s\" is disabled", username));
	goto done;
  }

  /*
   * If a password (an OTP) was given for a HOTP device, we must advance the
   * server's copy of the counter if authentication succeeds.
   * RFC 4226 S7.2 (note: wtf?):
   *   The HOTP client (hardware or software token) increments its counter
   *   and then calculates the next HOTP value HOTP client.  If the value
   *   received by the authentication server matches the value calculated by
   *   the client, then the HOTP value is validated.  In this case, the
   *   server increments the counter value by one.
   * S7.4:
   *   Although the server's counter value is only incremented after a
   *   successful HOTP authentication, the counter on the token is
   *   incremented every time a new HOTP is requested by the user.  Because
   *   of this, the counter values on the server and on the token might be
   *   out of synchronization.
   */
  if (password != NULL && *password != '\0'
	  && (st = auth_token_validate(token, password, aux, TOKEN_REQUIRE_PIN,
								   NULL, accept_window))
	  == 0) {
	log_msg((LOG_DEBUG_LEVEL, "Token verification succeeded"));

	if ((st = auth_token_update(token)) == -1)
	  log_msg((LOG_ERROR_LEVEL, "Account update failed"));
  }
  else
	log_msg((LOG_ERROR_LEVEL, "Token verification failed"));

 done:
  proc_lock_delete(lock);

  return(st);
}

#ifdef PROG
int
main(int argc, char **argv)
{
  int accept_window, emitted_dtd, i;
  char *errmsg, *jurisdiction, *username, *password, *aux;
  Auth_reply_ok ok;
  Kwv *kwv;

  emitted_dtd = 0;
  errmsg = "internal";
  username = password = aux = jurisdiction = 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... */
  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;
  }

  accept_window = -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, "ACCEPT_WINDOW")
             && accept_window == -1) {
      if (strnum(kwv->pairs[i]->val, STRNUM_I, &accept_window) == -1
		  || accept_window < 0) {
        log_msg((LOG_ALERT_LEVEL, "Invalid ACCEPT_WINDOW: \"%s\"",
                 kwv->pairs[i]->val));
        errmsg = "Invalid ACCEPT_WINDOW parameter";
        goto fail;
      }
    }
	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 (accept_window == -1)
	accept_window = TOKEN_HOTP_DEFAULT_ACCEPT_WINDOW;

  if (username == NULL || password == NULL || aux == NULL
	  || local_token_auth(username, password, aux, accept_window) == -1) {
	errmsg = "Username/Password/Aux incorrect";
    goto fail;
  }

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

  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
