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

/*
 * Web service for email-based self-administered account creation and management
 *
 * Overview:
 * When a user initiates an operation (such as requesting a password change),
 * a message is sent to an email address that is purportedly
 * associated with the user.  The message contains a "secure link" - a
 * cryptographically protected URL that cannot be easily forged or undetectably
 * modified, and which has a specified lifetime.
 * (This URL can be copied, of course, if the message is not kept private.)
 * When the message is received, by invoking the link the user
 * confirms its receipt and initiates the next phase of the "registration"
 * operation.  The web service invoked by the link validates its arguments,
 * and if all is well, concludes that the user has received the message
 * (and that the email address that was used is valid).
 *
 * By optionally displaying a randomly-generated string (e.g., a 5 digit number)
 * to the user when the operation is initiated and requiring the user to repeat
 * the string after invocation of the secure link, the web service may also
 * conclude (with some degree of confidence) that the user that received the
 * message is the user that initiated the operation (i.e., that a third party
 * did not invoke the link).  This feature can also discourage attacks,
 * especially if the string is displayed as an image or presented as a puzzle
 * ("your codeword is X + Y") that is sufficiently difficult to automate.
 *
 * Precautions:
 *   1) If the operation performed by the secure link is not idempotent,
 *      steps should be taken to prevent multiple invocations.
 *   2) It may be necessary to ensure that operations are performed in the
 *      order in which they were requested by a given user.
 *   3) It may be desirable to guard against attacks (e.g., to prevent creating
 *      many new accounts or password guessing by requesting password changes
 *      or account renaming), perhaps using the codeword feature.
 *   4) It may be desirable to offer resistance against misusing this mechanism
 *      to spam via email (although no user-specified input is conveyed within
 *      the registration message.
 *
 * Also note that self-administered account management makes an optional
 * password duration feature practical - an admin could require periodic
 * password changes for DACS-administered passwords (also requires associating
 * (hash, date) pairs with each account).
 *
 * An example workflow:
 * 1. Alice visits an account management page, selects an operation, and
 *    provides the mandatory arguments.  Operations are:
 *      a) Create new account  (OP == create):
 *      b) Change password     (OP == pwreset)
 *      c) Rename account      (OP == rename)
 *    Ideally, the mechanism's architecture should allow this list to be
 *    extensible.
 *
 * 2. Alice submits the operation to dacs_register with PHASE=init
 *    (or with no PHASE argument).
 *
 * 3. dacs_register (PHASE=init) validates its arguments and then sends
 *    a message to Alice's purported address (for 1c, the new email address
 *    is used), containing information, instructions, and a secure link;
 *    a new page is displayed that optionally includes a randomly generated
 *    code word
 *
 * 4. If the secure link is invoked with valid arguments (e.g., it has not
 *    expired, etc.), dacs_register:
 *    a) optionally, prompts Alice for a code word (if PHASE=codeword);
 *       when submitted, the form calls dacs_register again (with PHASE=finish)
 *    b) if PHASE=finish, validates the code word (if necessary)
 *    c) if ok, performs the requested operation
 *    d) redirects Alice to an operation-appropriate confirmation/error page
 *
 * Note that the entire workflow is stateless with respect to the server
 * (modulo the precautions);
 * that is, no record of a request is maintained on the server and no data is
 * updated until the very end of the workflow.  Consequently, if anything
 * happens to prevent a request from being performed, nothing needs to be
 * reversed or deleted.  All state is encapsulated in secure links that are
 * given to the requestor.
 *
 * A secure link usually has just one argument (ARG); other CGI arguments,
 * with the exception of CONFIRM_CODEWORD (if PHASE=finish), are ignored.
 * The value of ARG is a secure (encrypted + authenticated + digested)
 * query string, base-64 MIME encoded, which contains several arguments that
 * we do not want anyone to be able to add to, remove from, or change.
 *
 * There are four configurable handlers:
 *   o where to send the user after phase 1 (message has been sent)
 *     (REGISTER_EMAIL_SUCCESS_HANDLER)
 *   o where to send the user to prompt for the codeword (when enabled)
 *     (REGISTER_CODEWORD_HANDLER)
 *   o where to send the user after successful execution (completion)
 *     (REGISTER_SUCCESS_HANDLER)
 *   o where to send the user if an error occurs
 *     (REGISTER_ERROR_HANDLER)
 *
 * An optional challenge question/answer pair (or list thereof) can be
 * associated with an account.  The challenge can be used as an alternate
 * proof of identity to reset the user's password; can also be used as
 * a supplementary sign on factor.
 *
 * This mechanism could be adapted for purposes other than account
 * self-administration.  For example, at sign-on time a message containing
 * a secure URL could be sent to a user's registered email address.
 * Successful authentication might require the user to invoke the URL,
 * which would perform some action that could be confirmed by
 * dacs_authenticate, such as touching a file.
 *
 * An administrator mode is required to handle the case where
 * the user does not have a password AND cannot receive email at the
 * registered email address.
 *
 * Operations:
 *   OP=create  NEW_ACCOUNT, NEW_PASSWORD, CONFIRM_NEW_PASSWORD
 *   OP=pwreset ACCOUNT, NEW_PASSWORD, CONFIRM_NEW_PASSWORD 
 *   OP=rename  ACCOUNT, NEW_ACCOUNT, PASSWORD
 * Common arguments:
 *   CODEWORD, EXPIRES, PHASE
 *
 * These "enhanced" accounts should probably be separate from the simple
 * "passwd" items:
 *   username, email, password digest, Pw_state, Passwd_digest_alg, salt,
 *   question/answer list, date of last sign-on, date of creation
 * <!ELEMENT enhanced_password (challenge)* >
 * <!ATTLIST enhanced_password
 *   status       CDATA #REQUIRED
 *   hash_alg     CDATA #REQUIRED
 *   salt         CDATA #REQUIRED
 *   digest       CDATA #REQUIRED
 *   email        CDATA #REQUIRED
 *   last_signon  CDATA #REQUIRED
 *   created      CDATA #REQUIRED
 * >
 * <!ELEMENT challenge EMPTY>
 * <!ATTLIST challenge
 *   question     CDATA #REQUIRED
 *   answer       CDATA #REQUIRED
 * >
 *
 * See:
 * "Email-Based Identification and Authentication: An Alternative to PKI?",
 * Simson L. Garfinkel, IEEE Security & Privacy, Vol. 1, No. 6 (Nov/Dec 2003),
 * pp. 20-26.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: register.c 2844 2015-08-24 22:50:16Z brachman $";
#endif

#include "dacs_api.h"
#include "dacs.h"
#include "email.h"

static MAYBE_UNUSED char *log_module_name = "register";

typedef enum {
  OP_CREATE   = 0,
  OP_PWRESET  = 1,
  OP_RENAME   = 2,
  OP_UNKNOWN  = -1
} Register_op;

typedef enum {
  PHASE_INIT         = 0,
  PHASE_REQ_CODEWORD = 1,
  PHASE_FINISH       = 2,
  PHASE_UNKNOWN      = -1
} Register_phase;

typedef struct Register_args {
  char *op_str;
  Register_op op;
  Register_phase phase;
  char *ustamp;
  char *username;
  char *new_username;
  char *email;
  char *new_email;
  char *password;
  char *new_password;
  char *confirm_new_password;
  char *question;
  char *answer;
  char *new_question;
  char *new_answer;
  char *account;
  char *new_account;
  char *expires;
  char *map_expr;
  char *codeword;
  char *confirm_codeword;
} Register_args;

#define TESTING

#define DEFAULT_CODEWORD_TEMPLATE	"%5d"

/* Defaults overridden by configuration variables. */
#define REGISTER_EMAIL_FROM		"dacs_register-noreply"

enum {
  DEFAULT_LINK_LIFETIME_SECS = (24 * 60 * 60),
  DEFAULT_USE_CODEWORD       = 0,
  DEFAULT_USE_QUESTION       = 0,
  DEFAULT_ALLOW_NICKNAMES    = 0
};

typedef enum {
  /* A user-selected name is allowed, but defaults to email address. */
  NICKNAMES_ALLOWED   = 1,
  /* A user-selected name must be given. */
  NICKNAMES_REQUIRED  = 2,
  /* A user-selected name is not allowed, email address is used. */
  NICKNAMES_FORBIDDEN = 3,
  /* A user name is obtain by expression evaluation. */
  NICKNAMES_MAPPED    = 4
} Nickname_conf;

Register_args *
register_new(void)
{
  Register_args *args;

  args = ALLOC(Register_args);
  args->op_str = NULL;
  args->op = OP_UNKNOWN;
  args->phase = PHASE_INIT;
  args->ustamp = NULL;
  args->username = NULL;
  args->new_username = NULL;
  args->email = NULL;
  args->new_email = NULL;
  args->password = NULL;
  args->new_password = NULL;
  args->confirm_new_password = NULL;
  args->question = NULL;
  args->answer = NULL;
  args->new_question = NULL;
  args->new_answer = NULL;
  args->expires = NULL;
  args->account = NULL;
  args->new_account = NULL;
  args->map_expr = NULL;
  args->codeword = NULL;
  args->confirm_codeword = NULL;

  return(args);
}

Ds *
register_make_url(Register_op type)
{
  char *url;
  Ds *ds;
  Jurisdiction *j;

  if (get_jurisdiction_meta(NULL, &j) == -1)
	return(NULL);

  url = expand_dacs_url(j->jname, j->dacs_url);
  fprintf(stderr, "%s\n", url);

  return(NULL);
}

static char *
make_link_args(Register_args *args)
{
  char *phase;
  Ds *ds;

  ds = ds_init(NULL);
  switch (args->op) {
  case OP_CREATE:
	ds_asprintf(ds, "OP=%s&NEW_EMAIL=%s&NEW_PASSWORD=%s",
				args->op_str, args->new_email, args->new_password);
	break;

  case OP_PWRESET:
	ds_asprintf(ds, "OP=%s&EMAIL=%s&NEW_PASSWORD=%s",
				args->op_str, args->email, args->new_password);
	break;

  case OP_RENAME:
	ds_asprintf(ds, "OP=%s&EMAIL=%s&NEW_EMAIL=%s&PASSWORD=%s",
				args->op_str, args->email, args->new_email, args->password);
	break;

  default:
	return(NULL);
  }

  if (args->phase == PHASE_INIT)
	phase = "init";
  else if (args->phase == PHASE_REQ_CODEWORD)
	phase = "codeword";
  else if (args->phase == PHASE_FINISH)
	phase = "finish";
  else
	return(NULL);

  ds_asprintf(ds, "&PHASE=%s&EXPIRES=%s", phase, args->expires);
  if (args->codeword != NULL)
	ds_asprintf(ds, "&CODEWORD=%s", args->codeword);
  if (args->ustamp != NULL)
	ds_asprintf(ds, "&USTAMP=%s", args->ustamp);

  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG, "link_args: %s", ds_buf(ds)));
  return(ds_buf(ds));
}

static char *
make_secure_link(Crypt_keys *ck, char *link_args)
{
  unsigned int enc_len;
  unsigned char *enc_str;
  char *link, *query_arg_val;
  Ds ds;

  enc_len = crypto_encrypt_string(ck, (unsigned char *) link_args,
								  strlen(link_args) + 1, &enc_str);
  crypt_keys_free(ck);
  strba64(enc_str, enc_len, &query_arg_val);

  ds_init(&ds);
  current_uri_no_query(&ds);
  link = ds_xprintf("%s?ARG=%s", ds_buf(&ds), query_arg_val);

  return(link);
}

/*
 * Create a new account (ACCOUNT) in VFS repository H, digesting PASSWORD
 * using ALG.
 * The caller has already determined that ACCOUNT does not exist and PASSWORD
 * is satisfactory.
 * Return 0 if successful, -1 otherwise.
 */
static int
register_create(Vfs_handle *h, Digest_desc *dd, Register_args *args)
{
  int st;
  char *new_account, *new_password;

  new_account = args->new_account;
  new_password = args->new_password;

  log_msg((LOG_DEBUG_LEVEL, "Creating new account for \"%s\"", new_account));
  st = pw_add_entry(h, new_account, new_password, dd, PW_STATE_ENABLED, NULL);

  return(st);
}

/*
 * Change (reset) the password for an existing account (ACCOUNT) in VFS
 * repository H, digesting PASSWORD using ALG.
 * The caller has already determined that ACCOUNT exists and PASSWORD
 * is satisfactory.  Do not change anything else about the account.
 * Return 0 if successful, -1 otherwise.
 */
static int
register_pwreset(Vfs_handle *h, Digest_desc *dd, Register_args *args)
{
  int st;
  char *account, *new_password;

  account = args->account;
  new_password = args->new_password;

  log_msg((LOG_DEBUG_LEVEL, "Password reset for account \"%s\"", account));
  st = pw_reset_entry(h, account, new_password, dd, PW_STATE_UNCHANGED,
					  PW_OP_RESET, NULL);

  return(st);
}

/*
 * Change the account name for existing account OLD_ACCOUNT to the new name
 * NEW_ACCOUNT, both in vfs repository H, digesting PASSWORD using ALG.
 * The caller has already determined that OLD_ACCOUNT exists, NEW_ACCOUNT does
 * not exist, and PASSWORD is satisfactory.  Do not change anything else
 * about the account.
 * Return 0 if successful, -1 otherwise.
 */
static int
register_rename(Vfs_handle *h, Digest_desc *dd, Register_args *args)
{
  int st;
  char *old_account, *password, *new_account;

  old_account = args->account;
  password = args->password;
  new_account = args->new_account;

  log_msg((LOG_DEBUG_LEVEL, "Account rename from \"%s\" to \"%s\"",
		   old_account, new_account));
  /* XXX this operation should be atomic */
  st = pw_rename_entry(h, old_account, new_account);

  return(st);
}

static void
usage(void)
{

  fprintf(stderr, "Usage: dacs_register\n");

  exit(1);
}

/*
 * Decrypt, validate, and extract a secure link's argument (ARG).
 */
static Register_args *
get_args(Crypt_keys *ck, char *arg_str)
{
  unsigned int enc_len;
  char *phase_str, *query_arg_val, *s;
  unsigned char *enc_str;
  time_t expires_secs;
  Kwv *kwv;
  Register_args *args;

  log_msg((LOG_TRACE_LEVEL, "Decoding ARG: %s", arg_str));

  if (stra64b(arg_str, &enc_str, &enc_len) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "get_args: base-64 decoding failed"));
	return(NULL);
  }
  if (crypto_decrypt_string(ck, enc_str, enc_len,
							(unsigned char **) &query_arg_val, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "get_args: decryption failed"));
	crypt_keys_free(ck);
	return(NULL);
  }
  crypt_keys_free(ck);

  log_msg((LOG_TRACE_LEVEL, "query_arg_val: \"%s\"", query_arg_val));
  if ((kwv = cgiparse_string(query_arg_val, NULL, NULL)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "get_args: parse failed"));
	return(NULL);
  }
  log_msg((LOG_TRACE_LEVEL, "CGI parse ok"));

  args = register_new();
  if ((args->op_str = kwv_lookup_value(kwv, "OP")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "OP is missing"));
	return(NULL);
  }
  if (streq(args->op_str, "create"))
	args->op = OP_CREATE;
  else if (streq(args->op_str, "pwreset"))
	args->op = OP_PWRESET;
  else if (streq(args->op_str, "rename"))
	args->op = OP_RENAME;
  else {
	log_msg((LOG_ERROR_LEVEL, "OP is invalid: \"%s\"", args->op_str));
	return(NULL);
  }

  if ((phase_str = kwv_lookup_value(kwv, "PHASE")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "PHASE is missing"));
	return(NULL);
  }
  if (streq(phase_str, "init"))
	args->phase = PHASE_INIT;
  else if (streq(phase_str, "codeword"))
	args->phase = PHASE_REQ_CODEWORD;
  else if (streq(phase_str, "finish"))
	args->phase = PHASE_FINISH;
  else {
	log_msg((LOG_ERROR_LEVEL, "PHASE is invalid: \"%s\"", phase_str));
	return(NULL);
  }

  if ((args->expires = kwv_lookup_value(kwv, "EXPIRES")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "EXPIRES is missing"));
	return(NULL);
  }
  /* Check for expiration. */
  if (strnum(args->expires, STRNUM_TIME_T, &expires_secs) == -1) {
	log_msg((LOG_ERROR_LEVEL, "EXPIRES is invalid: \"%s\"", args->expires));
	return(NULL);
  }
  else {
	time_t now;

	time(&now);
	if (now > expires_secs) {
	  log_msg((LOG_ERROR_LEVEL, "Link has expired"));
	  return(NULL);
	}
  }

  args->username = kwv_lookup_value(kwv, "USERNAME");
  args->email = kwv_lookup_value(kwv, "EMAIL");
  args->new_email = kwv_lookup_value(kwv, "NEW_EMAIL");
  args->password = kwv_lookup_value(kwv, "PASSWORD");
  args->new_password = kwv_lookup_value(kwv, "NEW_PASSWORD");
  args->confirm_new_password = kwv_lookup_value(kwv, "CONFIRM_NEW_PASSWORD");
  args->codeword = kwv_lookup_value(kwv, "CODEWORD");
  args->confirm_codeword = kwv_lookup_value(kwv, "CONFIRM_CODEWORD");

  return(args);
}

int
dacsregister_main(int argc, char **argv, int do_init)
{
  int admin, i, xargc, n, need_codeword, st, use_question;
  unsigned int link_lifetime_secs, ncookies;
  char *arg_str, *link_args, *op_name, *op_str, *redirect_url, *to_addr;
  char *codeword_arg, *expires, *item_type, *codeword;
  char **xargv, *errmsg, *p, *remote_addr, *seqno_str, *ustamp;
  char *codeword_template, *error_handler, *email_success_handler;
  char *codeword_handler, *email_template, *success_handler;
  const char *digest_name;
  time_t now;
  Cookie *cookies;
  Credentials *cr, *credentials, *selected;
  Crypt_keys *ck;
  Digest_desc *dd;
  Html_header_conf *hc;
  Kwv *kwv;
  Nickname_conf nicknames;
  Proc_lock *lock;
  Register_args *args, *new_args;
  Register_phase next_phase;
  Vfs_handle *h;

  admin = 0;
  errmsg = "Internal error";
  h = NULL;
  hc = emit_html_header_conf(NULL);
  hc->no_cache = 1;
  args = NULL;
  to_addr = NULL;
  ustamp = NULL;
  error_handler = NULL;

  args = new_args = NULL;

  xargc = argc;
  xargv = argv;
  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	if (h != NULL) {
	  if (h->error_msg != NULL) {
		errmsg = strdup(h->error_msg);
		log_err((LOG_ERROR_LEVEL, "%s", errmsg));
	  }
	  vfs_close(h);
	}

	/*
	 * XXX Add a delay in case password guessing might be going on?
	 * The problem is that this can lead to denial of service...
	 */

	hc->title = ds_xprintf("DACS Registration for Jurisdiction %s",
						   non_null(conf_val(CONF_JURISDICTION_NAME)));

	if (error_handler != NULL) {
	  hc->redirect_url
		= ds_xprintf("%s?ACCOUNT=%s&amp;OPERATION=%s&amp;ERROR=%s",
					 error_handler,
					 non_null(to_addr), non_null(op_name),
					 non_null(errmsg));
	  emit_html_header_redirect(stdout, hc, NULL, non_null(errmsg));
	}
	else {
	  emit_html_header(stdout, hc);
	  printf("<p>dacs_register: fatal error.");
	  printf("<p>%s\n", non_null(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;
	}
  }

  /* XXX This course-grained lock prevents concurrent updates. */
  if ((lock = proc_lock_create(PROC_LOCK_REGISTER)) == NULL) {
    log_msg((LOG_ERROR_LEVEL, "Can't set lock"));
	errmsg = "Can't set lock";
	goto fail;
  }

  /*
   * Get the keys that may be needed to decrypt arguments passed in a
   * secure link or to create a secure link (which has an encrypted argument).
   */
  if ((ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) == NULL) {
	errmsg = "Cannot load jurisdiction keys";
	goto fail;
  }

  digest_name = NULL;
  if ((dd = passwd_get_digest_algorithm(digest_name)) == NULL) {
	errmsg = "Configuration error";
	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;
  }

  n = get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL);
  admin = is_dacs_admin(selected);
  log_msg((LOG_DEBUG_LEVEL, "admin=%d", admin));

  /*
   * HTML output depends on the current phase and whether it succeeds or
   * fails.
   */
  if (conf_val(CONF_CSS_PATH) != NULL)
	hc->css = ds_xprintf("%s/dacs_register.css", conf_val(CONF_CSS_PATH));
  else
	hc->css = CSS_DIR/**/"/dacs_register.css";
  hc->title = ds_xprintf("DACS Registration for Jurisdiction %s",
						 non_null(conf_val(CONF_JURISDICTION_NAME)));

  /* Initialize configurable variables. */

  if ((item_type = conf_val(CONF_REGISTER_ITEM_TYPE)) == NULL)
	item_type = ITEM_TYPE_PASSWDS;
  /* This is where the account information is obtained from. */
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }
  log_msg((LOG_TRACE_LEVEL, "Item type=\"%s\"", item_type));

  st = conf_val_uint(CONF_REGISTER_LINK_LIFETIME_SECS, &link_lifetime_secs);
  if (st == -1) {
	errmsg = "Invalid REGISTER_LINK_LIFETIME_SECS";
	goto fail;
  }
  if (st == 0)
	link_lifetime_secs = DEFAULT_LINK_LIFETIME_SECS;
  log_msg((LOG_TRACE_LEVEL, "link_lifetime_secs=\"%u\"", link_lifetime_secs));

  if (conf_val_eq(CONF_REGISTER_USE_CODEWORD, "yes"))
	need_codeword = 1;
  else
	need_codeword = DEFAULT_USE_CODEWORD;

  if (conf_val_eq(CONF_REGISTER_USE_QUESTION, "yes"))
	use_question = 1;
  else
	use_question = DEFAULT_USE_QUESTION;

  /*
   * If nicknames are allowed, then the username is a syntactically valid,
   * unique DACS username rather than the email address.
   * We need to be able to associate the username with the email address.
   */
  if ((p = conf_val(CONF_REGISTER_NICKNAMES)) != NULL) {
	if (strcaseeq(p, "allowed"))
	  nicknames = NICKNAMES_ALLOWED;
	else if (strcaseeq(p, "required"))
	  nicknames = NICKNAMES_REQUIRED;
	else if (strcaseeq(p, "forbidden"))
	  nicknames = NICKNAMES_FORBIDDEN;
	else if (strncaseeq(p, "map ", 4)) {
	  nicknames = NICKNAMES_MAPPED;
	  args->map_expr = p + 4;
	}
	else {
	  errmsg = "Invalid REGISTER_NICKNAMES";
	  goto fail;
	}
  }
  else {
	errmsg = "REGISTER_NICKNAMES must be configured";
	goto fail;
  }

  if ((codeword_template = conf_val(CONF_REGISTER_CODEWORD_TEMPLATE)) == NULL)
	codeword_template = DEFAULT_CODEWORD_TEMPLATE;
  log_msg((LOG_TRACE_LEVEL, "need_codeword=\"%d\", template=\"%s\"",
		   need_codeword, codeword_template));

  if ((email_template = conf_val(CONF_REGISTER_EMAIL_TEMPLATE_FILE)) == NULL) {
	errmsg = "REGISTER_EMAIL_TEMPLATE_FILE must be configured";
	goto fail;
  }
  log_msg((LOG_TRACE_LEVEL, "email_template=\"%s\"", email_template));

  /* Optional URL to redirect to to prompt for the request's codeword. */
  codeword_handler = conf_val(CONF_REGISTER_CODEWORD_HANDLER);
  /* Optional URL to redirect to if the request is executed successfully. */
  success_handler = conf_val(CONF_REGISTER_SUCCESS_HANDLER);
  /* Optional URL to redirect to after the first phase succeeds. */
  email_success_handler = conf_val(CONF_REGISTER_EMAIL_SUCCESS_HANDLER);
  /* Optional URL to redirect to if an error occurs. */
  error_handler = conf_val(CONF_REGISTER_ERROR_HANDLER);

  st = -1;
  if ((arg_str = kwv_lookup_value(kwv, "ARG")) != NULL) {
	/*
	 * If ARG is present, this is not the initial phase.  Proceed by validating
	 * and extracting the arguments encapsulated within ARG.
	 */
	if ((args = get_args(ck, arg_str)) == NULL) {
	  errmsg = "Request is invalid or has expired";
	  goto fail;
	}

	if (need_codeword && args->codeword == NULL) {
	  errmsg = "A CODEWORD argument is required";
	  goto fail;
	}

	if (args->phase == PHASE_REQ_CODEWORD) {
	  char *link;

	  /*
	   * Prompt for the codeword when the secure link has been invoked.
	   * This optional phase is performed only when enabled during PHASE_INIT.
	   */
	  *new_args = *args;
	  new_args->phase = PHASE_FINISH;
	  link_args = make_link_args(new_args);
	  link = make_secure_link(ck, link_args);

	  log_msg((LOG_TRACE_LEVEL, "Emitting codeword prompt..."));
	  if (codeword_handler != NULL) {
		Ds *ds;

		ds = ds_init(NULL);
		ds_asprintf(ds, "%s?ARG=%s", codeword_handler, link);
		hc->redirect_url = ds_buf(ds);
		emit_html_header_redirect(stdout, hc, NULL, "A codeword is required");
		goto done;
	  }

	  emit_html_header(stdout, hc);
	  printf("<p>\n");
	  printf("Please enter the codeword for this operation:<br>\n");
	  printf("<form name=\"registration_form\" method=\"POST\" action=\"/cgi-bin/dacs/dacs_register\">\n");
	  printf("<table>\n");
	  printf("<tr>\n");
	  printf("<td width=\"20%%\"><span class=\"argname\">Codeword</span></td>\n");
	  printf("<td width=\"80%%\"><input type=\"TEXT\" size=\"40\" name=\"CONFIRM_CODEWORD\"></td>\n");
	  printf("</tr>\n");
	  printf("<tr>\n");
	  printf("<td>&nbsp;</td>\n");
	  printf("<td width=\"80%%\"><input type=\"submit\" value=\" Submit \"></td>\n");
	  printf("</tr>\n");
	  printf("</table>\n");

	  printf("<input type=\"hidden\" name=\"ARG\" value=\"%s\">\n", link);
	  printf("</form>\n");
	  printf("</p>\n");
	}
	else if (args->phase == PHASE_FINISH) {
	  /*
	   * If a codeword is required, confirm that the user has given it and
	   * that it matches the expected value.
	   * If all is well, execute the requested operation.
	   */
	  if (need_codeword) {
		if (args->confirm_codeword == NULL || args->codeword == NULL
			|| !streq(args->codeword, args->confirm_codeword)) {
		  errmsg = "Codeword mismatch";
		  goto fail;
		}
	  }

	  if (args->op == OP_CREATE)
		st = register_create(h, dd, args);
	  else if (args->op == OP_PWRESET)
		st = register_pwreset(h, dd, args);
	  else if (args->op == OP_RENAME)
		st = register_rename(h, dd, args);
	  else
		st = -1;

	  if (st == -1) {
		errmsg = "An error occurred, operation failed";
		goto fail;
	  }

	  hc->redirect_url = success_handler;
	  emit_html_header_redirect(stdout, hc, NULL,
								"The request has been performed");
	}
  }
  else {
	char *boundary, *link, *subject;
	Ds *ds, *ds_out;
	Dsvec *headers, *mailer_args;
	Email_message *em;
	Kwv *kwv_trans;

	/*
	 * A user is initiating an operation, PHASE == PHASE_INIT (explicitly or
	 * implicitly).
	 */
	args = register_new();

	if ((op_str = kwv_lookup_value(kwv, "OP")) == NULL) {
	  errmsg = "An OP argument is required";
	  goto fail;
	}
	args->op_str = op_str;
	if (streq(op_str, "create"))
	  args->op = OP_CREATE;
	else if (streq(op_str, "pwreset"))
	  args->op = OP_PWRESET;
	else if (streq(op_str, "rename"))
	  args->op = OP_RENAME;
	else {
	  errmsg = "Invalid OP value";
	  goto fail;
	}
	log_msg((LOG_TRACE_LEVEL, "OP=%s", args->op_str));

	/* Get the arguments... */
	args->email = kwv_lookup_value(kwv, "EMAIL");
	args->new_email = kwv_lookup_value(kwv, "NEW_EMAIL");
	args->username = kwv_lookup_value(kwv, "USERNAME");
	args->new_username = kwv_lookup_value(kwv, "NEW_USERNAME");
	args->password = kwv_lookup_value(kwv, "PASSWORD");
	args->new_password = kwv_lookup_value(kwv, "NEW_PASSWORD");
	args->confirm_new_password = kwv_lookup_value(kwv, "CONFIRM_NEW_PASSWORD");
	args->question = kwv_lookup_value(kwv, "QUESTION");
	args->new_question = kwv_lookup_value(kwv, "NEW_QUESTION");
	args->answer = kwv_lookup_value(kwv, "ANSWER");
	args->new_answer = kwv_lookup_value(kwv, "NEW_ANSWER");

	if (nicknames == NICKNAMES_REQUIRED) {
	  if (args->username == NULL) {
		errmsg = "A USERNAME argument is required";
		goto fail;
	  }
	  args->account = args->username;
	}
	else if (nicknames == NICKNAMES_FORBIDDEN) {
	  if (args->username != NULL) {
		errmsg = "A USERNAME argument is not allowed";
		goto fail;
	  }
	  args->account = args->email;
	}

	if (args->op == OP_PWRESET || args->op == OP_RENAME) {
	  /* This must be an existing account. */
	  if (args->account == NULL) {
		errmsg = "An ACCOUNT argument is required";
		goto fail;
	  }
	  args->account = strtolower(args->account);
	  if (pw_check_username(args->account) == -1
		  || pw_exists_entry(h, args->account) != 1) {
		errmsg = "Invalid ACCOUNT argument";
		goto fail;
	  }
	  log_msg((LOG_TRACE_LEVEL, "ACCOUNT=%s", args->account));
	}

	if (args->op == OP_CREATE || args->op == OP_RENAME) {
	  if (args->new_account == NULL) {
		errmsg = "Missing NEW_ACCOUNT argument";
		goto fail;
	  }
	  /* Canonicalize it so that case doesn't matter. */
	  args->new_account = strtolower(args->new_account);

	  /* XXX might want to map it into something usable? */
	  if (pw_check_username(args->new_account) == -1) {
		errmsg = "Account name is unavailable";
		goto fail;
	  }
	  if (pw_exists_entry(h, args->new_account) == -1) {
		errmsg = "Account name is unavailable";
		goto fail;
	  }
	}
	log_msg((LOG_INFO_LEVEL, "%s request is from %s",
			 args->op_str, remote_addr));

	codeword = NULL;
	codeword_arg = "";
	if (need_codeword) {
	  /* XXX Create a codeword and include it in the secure link */
	  codeword
		= crypto_make_random_string_from_template(codeword_template);
	  codeword_arg = ds_xprintf("&CODEWORD=%s", codeword);
	  next_phase = PHASE_REQ_CODEWORD;
	}
	else
	  next_phase = PHASE_FINISH;

	time(&now);
	/* Expiration is relative to this jurisdiction's clock. */
	expires = ds_xprintf("%u", (unsigned int) (now + link_lifetime_secs));

	if (ustamp_get_seqno(NULL, &seqno_str) != -1)
	  ustamp = ds_xprintf("%s@%s",
						  seqno_str, dacs_current_jurisdiction());

	new_args = register_new();

	switch (args->op) {
	case OP_CREATE:
	case OP_PWRESET:
	  if (args->new_password == NULL) {
		errmsg = "Missing NEW_PASSWORD argument";
		goto fail;
	  }
	  if (args->confirm_new_password == NULL) {
		errmsg = "Missing CONFIRM_NEW_PASSWORD argument";
		goto fail;
	  }
	  if (!streq(args->new_password, args->confirm_new_password)) {
		errmsg = "Password and retyped password do not agree";
		goto fail;
	  }
	  strzap(args->confirm_new_password);
	  if (!admin
		  && !pw_is_passwd_acceptable(args->new_password,
									  conf_val(CONF_PASSWORD_CONSTRAINTS))) {
		errmsg = "New password is not acceptable";
		goto fail;
	  }
	  log_msg((LOG_TRACE_LEVEL, "New password is ok"));

	  if (args->op == OP_CREATE) {
		new_args->op_str = args->op_str;
		new_args->op = args->op;
		new_args->phase = next_phase;
		new_args->ustamp = ustamp;
		new_args->account = NULL;
		new_args->new_account = args->new_account;
		new_args->password = NULL;
		new_args->new_password = args->new_password;
		new_args->confirm_new_password = NULL;
		new_args->expires = expires;
		new_args->codeword = codeword;
		new_args->confirm_codeword = NULL;

		link_args = make_link_args(new_args);
		op_name = "create the new account";
	  }
	  else {
		new_args->op_str = args->op_str;
		new_args->op = args->op;
		new_args->phase = next_phase;
		new_args->ustamp = ustamp;
		new_args->account = args->account;
		new_args->new_account = NULL;
		new_args->password = NULL;
		new_args->new_password = args->new_password;
		new_args->confirm_new_password = NULL;
		new_args->expires = expires;
		new_args->codeword = codeword;
		new_args->confirm_codeword = NULL;

		link_args = make_link_args(new_args);	
		op_name = "reset the password for account";
	  }
	  break;

	case OP_RENAME:
	  if (args->password == NULL) {
		errmsg = "Missing PASSWORD argument";
		goto fail;
	  }
	  if (pw_check_passwd(h, args->account, args->password, dd) != 0) {
		errmsg = "Permission denied";
		goto fail;
	  }

	  new_args->op_str = args->op_str;
	  new_args->op = args->op;
	  new_args->phase = next_phase;
	  new_args->ustamp = ustamp;
	  new_args->account = args->account;
	  new_args->new_account = args->new_account;
	  new_args->password = args->password;
	  new_args->new_password = NULL;
	  new_args->confirm_new_password = NULL;
	  new_args->expires = expires;
	  new_args->codeword = codeword;
	  new_args->confirm_codeword = NULL;

	  link_args = make_link_args(new_args);
	  op_name = "rename account";

	  break;

	case OP_UNKNOWN:
	default:
	  errmsg = "Internal error";
	  goto fail;
	  /*NOTREACHED*/
	}

	if (args->op == OP_CREATE || args->op == OP_RENAME)
	  to_addr = args->new_account;
	else if (args->op == OP_PWRESET)
	  to_addr = args->account;
	else {
	  errmsg = "Internal error";
	  goto fail;
	}

	/* Construct a secure link, embed it in a message, and send. */
	link = make_secure_link(ck, link_args);

#ifdef TESTING
	printf("Click <a href=\"%s\">here</a> to continue.<br>\n", link);
	if (codeword != NULL)
	  printf("Please remember this codeword: %s<br>\n", codeword);
#endif

	kwv_trans = kwv_init(8);
	boundary = email_make_boundary(NULL, 0);
	kwv_add(kwv_trans, "boundary", boundary);
	kwv_add(kwv_trans, "account", to_addr);
	kwv_add(kwv_trans, "lifetime",
			make_approx_relative_date(link_lifetime_secs));
	kwv_add(kwv_trans, "operation", op_name);
	kwv_add(kwv_trans, "url", link);
	kwv_add(kwv_trans, "remote_addr", remote_addr);
	kwv_add(kwv_trans, "date", make_local_date_string(NULL, 1));
	if (ustamp != NULL)
	  kwv_add(kwv_trans, "ustamp", ustamp);

	mailer_args = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(mailer_args, "-t");

	headers = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(headers, email_new_header("To", to_addr));
	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
						 "register_email_from");
	if (p == NULL)
	  p = REGISTER_EMAIL_FROM;
	if (*p != '\0')
	  dsvec_add_ptr(headers, email_new_header("From", p));

	p = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
						 "register_email_subject");
	if (p == NULL)
	  p = ds_xprintf("Your account at %s", dacs_current_jurisdiction());
	if (*p != '\0')
	  dsvec_add_ptr(headers, email_new_header("Subject", p));

	em = email_create_transformed(SENDMAIL_PROG, mailer_args, headers,
								  "multipart/alternative",
								  email_template, kwv_trans, CANON_SIMPLE);
	if (em == NULL)
	  goto fail;

	if ((st = email_send(em)) == -1) {
	  printf("An error occurred - a message could not be sent to %s\n",
			 to_addr);
	  errmsg = "Email send failure";
	  goto fail;
	}

	/*
	 * The message has been sent.  Now what?
	 * The user may need to see the codeword, maybe some additional information,
	 * and probably a link for the continuation of the workflow.
	 */
	ds = ds_init(NULL);
	ds_asprintf(ds, "%s", email_success_handler);
	ds_asprintf(ds, "?ACCOUNT=%s", url_encode(to_addr, 0));
	ds_asprintf(ds, "&amp;LIFETIME=%s",
				url_encode(make_approx_relative_date(link_lifetime_secs), 0));
	ds_asprintf(ds, "&amp;OPERATION=%s", url_encode(op_name, 0));
	if (codeword != NULL)
	  ds_asprintf(ds, "&amp;CODEWORD=%s", url_encode(codeword, 0));
	hc->redirect_url = ds_buf(ds);
	emit_html_header_redirect(stdout, hc, NULL, "A message has been sent");
  }

  if (st != -1)
	st = vfs_close(h);

  if (st == -1) {
	errmsg = ds_xprintf("Operation \"%s\" failed", args->op_str);
	goto fail;
  }

 done:

  log_msg((LOG_DEBUG_LEVEL, "Operation \"%s\" succeeded", args->op_str));

  emit_html_trailer(stdout);

  exit(0);
}

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsregister_main(argc, argv, 1)) == 0)
	exit(0);

  exit(1);
}
