/*
 * 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.
 ***************************************************************************/

/*
 * Manage local jurisdictional authentication passwords for DACS.
 * As a utility, this should be runnable only by the administrator.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: dacspasswd.c 2610 2012-10-19 18:02:36Z brachman $";
#endif

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

static MAYBE_UNUSED char *log_module_name = "dacspasswd";

#ifndef PROG

/* A passwordless account is allowed only if this is zero. */
static int require_password = 1;

static char *item_type = ITEM_TYPE_PASSWDS;

Pw_entry *
pw_init_entry(Pw_entry *opw, char *username)
{
  Pw_entry *pw;

  if (opw == NULL)
	pw = ALLOC(Pw_entry);
  else
	pw = opw;

  pw->username = username;
  pw->state = PW_UNCHANGED;
  pw->alg = PASSWD_ALG_NONE;
  pw->salt = NULL;
  pw->digest = NULL;
  pw->stored_digest = NULL;
  pw->entry = NULL;
  pw->private = NULL;

  return(pw);
}

char *
pw_make_entry(Pw_entry *pw)
{
  char *disabled_prefix;

  if (pw->state == PW_DISABLED)
	disabled_prefix = ds_xprintf("%c", (int) PASSWD_DISABLED_CHAR);
  else
	disabled_prefix = "";

  pw->entry = pw->stored_digest = ds_xprintf("%s%d%c%s%c%s",
											 disabled_prefix, pw->alg,
											 (int) PASSWD_SEP_CHAR, pw->salt,
											 (int) PASSWD_SEP_CHAR,
											 pw->digest);

  if (pw->private != NULL) {
	char *private;

	/*
	 * If the 'private' field is non-NULL, it must be properly
	 * encoded as text such that PASSWD_SEP_CHAR does not appear in it.
	 */
	strba64((unsigned char *) ds_buf(pw->private), ds_len(pw->private),
			&private);
	pw->entry = ds_xprintf("%s%c%s", pw->stored_digest,
						   (int) PASSWD_SEP_CHAR, private);
  }

  return(pw->stored_digest);
}

Pw_entry *
pw_parse_entry(char *username, char *stored_digest)
{
  int is_disabled, n;
  char **argv, *sd;
  Mkargv conf = { 0, 0, NULL, NULL, NULL };
  Pw_entry *pw;

  /*
   * A "*" (PASSWD_DISABLED_CHAR) represents a disabled entry that won't
   * match any password.  The resulting entry can only be managed by an
   * ADMIN_IDENTITY.
   */
  if (*stored_digest == PASSWD_DISABLED_CHAR) {
	is_disabled = 1;
	sd = stored_digest + 1;
  }
  else {
	is_disabled = 0;
	sd = stored_digest;
  }

  conf.ifs = ds_xprintf("%c", (int) PASSWD_SEP_CHAR);
  n = mkargv(sd, &conf, &argv);
  if (n != 3 && n != 4) {
	log_msg((LOG_ERROR_LEVEL, "Password field parse failed"));
	return(NULL);
  }

  if (!is_digit_string(argv[0])) {
	log_msg((LOG_ERROR_LEVEL, "Invalid stored algorithm number"));
	return(NULL);
  }

  pw = pw_init_entry(NULL, username);
  pw->state = is_disabled ? PW_DISABLED : PW_ENABLED;
  pw->alg = (Passwd_digest_alg) atoi(argv[0]);
  pw->salt = argv[1];
  pw->digest = argv[2];
  pw->stored_digest = strdup(sd);
  pw->entry = strdup(stored_digest);

  if (n == 4) {
	unsigned int slen;
	char *p;
	unsigned char *s;

	if ((p = strrchr(pw->stored_digest, (int) PASSWD_SEP_CHAR)) == NULL) {
	  /* Huh? */
	  log_msg((LOG_ERROR_LEVEL, "Password entry seems to be corrupted?"));
	  return(NULL);
	}
	*p = '\0';

	if (stra64b(argv[3], &s, &slen) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error decoding private data"));
	  return(NULL);
	}
	pw->private = ds_setn(NULL, s, slen);
  }
  else
	pw->private = NULL;

  return(pw);
}

/*
 * Return 1 if an entry for USERNAME exists, 0 if it does not, and
 * -1 if cannot tell because an error occurred.
 */
int
pw_exists_entry(Vfs_handle *h, char *username)
{
  int st;

  if ((st = vfs_exists(h, username)) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot check entry for username \"%s\"",
			 username));
	return(-1);
  }

  return(st);
}

/*
 * Read the entry for USERNAME.
 * If it does not exist or there is an error, return NULL.
 */
Pw_entry *
pw_read_entry(Vfs_handle *h, char *username)
{
  int st;
  char *stored_entry;
  Pw_entry *pw;

  if ((st = pw_exists_entry(h, username)) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot check entry for username \"%s\"",
			 username));
	return(NULL);
  }

  if (st == 0)
	return(NULL);

  if (vfs_get(h, username, (void **) &stored_entry, NULL) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(NULL);
  }

  if (stored_entry == NULL || stored_entry[0] == '\0') {
	log_msg((LOG_ALERT_LEVEL, "Invalid entry for username \"%s\"?", username));
	return(NULL);
  }

  if ((pw = pw_parse_entry(username, stored_entry)) == NULL)
	return(NULL);

  return(pw);
}

char *
pw_getdigest_entry(Vfs_handle *h, char *username)
{
  Pw_entry *pw;

  if ((pw = pw_read_entry(h, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(NULL);
  }

  return(pw->stored_digest);
}

Ds *
pw_getdata_entry(Vfs_handle *h, char *username)
{
  Pw_entry *pw;

  if ((pw = pw_read_entry(h, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(NULL);
  }

  if (pw->private == NULL)
	return(ds_set(NULL, ""));

  return(pw->private);
}

int
pw_check_passwd(Vfs_handle *h, char *username, char *given_passwd,
				Passwd_digest_alg alg)
{
  int st;
  char *passwd;
  Pw_entry *pw;

  if (username == NULL)
	return(-1);

  passwd = "";
  if (require_password) {
	if (given_passwd == NULL || given_passwd[0] == '\0')
	  return(-1);
	passwd = given_passwd;
  }

  if ((pw = pw_read_entry(h, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(-1);
  }

  if ((st = passwd_check_digest(alg, passwd, pw)) != 1) {
	log_msg((LOG_NOTICE_LEVEL,
			 "Password comparison failed for username \"%s\"", username));
	st = -1;
  }
  else {
	log_msg((LOG_DEBUG_LEVEL, "Password comparison ok for username \"%s\"",
			 username));
	st = 0;
  }

  return(st);
}

int
pw_delete_entry(Vfs_handle *h, char *username)
{
  int st;

  st = vfs_delete(h, username);

  return(st);
}

/*
 * Add a new entry, which must not already exist.
 */
int
pw_add_entry(Vfs_handle *h, char *username, char *given_passwd,
			 Passwd_digest_alg alg, Pw_state new_state, Ds *data)
{
  int is_disabled, st;
  char *digest;
  Pw_entry pw;

  if ((st = pw_exists_entry(h, username)) == -1)
	return(-1);

  if (st == 1) {
	log_msg((LOG_ERROR_LEVEL, "Entry exists, cannot add username \"%s\"",
			 username));
	return(-1);
  }

  pw_init_entry(&pw, username);
  if (new_state == PW_DISABLED) {
	pw.state = PW_DISABLED;
	is_disabled = 1;
  }
  else {
	pw.state = PW_ENABLED;
	is_disabled = 0;
  }

  pw.private = data;

  digest = passwd_make_digest(alg, given_passwd, &pw);

  if (vfs_put(h, username, pw.entry, strlen(pw.entry) + 1) == -1)
	return(-1);

  log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
		   "DACS account added for \"%s\"%s",
		   username, is_disabled ? " (disabled)" : ""));

  return(st);
}

/*
 * Add a new entry or replace an existing one.
 */
int
pw_replace_entry(Vfs_handle *h, char *username, char *given_passwd,
				 Passwd_digest_alg alg, Pw_state new_state,
				 Pw_op data_op, Ds *data)
{
  int exists, is_disabled;
  char *digest;
  Pw_entry *pw;

  if ((exists = pw_exists_entry(h, username)) == -1)
	return(-1);

  if (exists) {
	if ((pw = pw_read_entry(h, username)) == NULL)
	  return(-1);
	if (data_op == PW_OP_DELETE_DATA && pw->private != NULL) {
	  ds_free(pw->private);
	  pw->private = NULL;
	}
  }
  else
	pw = pw_init_entry(NULL, username);

  if (data_op == PW_OP_SET_DATA) {
	if (pw->private != NULL)
	  ds_free(pw->private);
	pw->private = data;
  }

  if (new_state == PW_DISABLED ||
	  (new_state == PW_UNCHANGED && pw->state == PW_DISABLED)) {
	pw->state = PW_DISABLED;
	is_disabled = 1;
  }
  else {
	pw->state = PW_ENABLED;
	is_disabled = 0;
  }

  digest = passwd_make_digest(alg, given_passwd, pw);

  if (vfs_put(h, username, pw->entry, strlen(pw->entry) + 1) == -1)
	return(-1);

  log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
		   "DACS account %s for \"%s\"%s",
		   exists ? "replaced" : "added",
		   username, is_disabled ? " (disabled)" : ""));

  return(0);
}

/*
 * Replace an existing entry for OLD_USERNAME with one for NEW_USERNAME that
 * does not yet exist
 * (i.e., read OLD_USERNAME, write NEW_USERNAME, delete OLD_USERNAME).
 *
 * XXX This really should be done atomically, but the DACS VFS subsystem
 * does not provide atomic transactions (in fact, without proper hardware
 * support, they are not even possible to implement correctly on most systems).
 * A possible workaround is to begin by:
 *  o adding a vfs_flush operation that makes a best effort try to
 *    make sure that all data written so far has been sync'ed
 *  o if necessary, modifying the old record with a note containing
 *    NEW_USERNAME and changing its state to RENAME_PENDING, then flushing
 *    the record
 *  o adding the NEW_USERNAME record (and flushing)
 *  o deleting the old record (and flushing)
 * If a RENAME_PENDING record is found during manual or automatic account
 * maintenance,
 * then if the noted new record exists, the old one can be deleted; if the
 * new record does not exist, the rename operation can be repeated.
 */
int
pw_rename_entry(Vfs_handle *h, char *old_username, char *new_username)
{
  int is_disabled;
  Pw_entry *pw;

  if (pw_exists_entry(h, old_username) != 1)
	return(-1);
  if (pw_exists_entry(h, new_username) != 0)
	return(-1);

#ifdef NOTDEF
  if ((pw = pw_read_entry(h, old_username)) == NULL)
	return(-1);

  if (data_op == PW_OP_DELETE_DATA && pw->private != NULL) {
	ds_free(pw->private);
	pw->private = NULL;
  }

  if (data_op == PW_OP_SET_DATA) {
	if (pw->private != NULL)
	  ds_free(pw->private);
	pw->private = data;
  }

  if (new_state == PW_DISABLED ||
	  (new_state == PW_UNCHANGED && pw->state == PW_DISABLED)) {
	pw->state = PW_DISABLED;
	is_disabled = 1;
  }
  else {
	pw->state = PW_ENABLED;
	is_disabled = 0;
  }

  pw->username = new_username;
  pw_make_entry(pw);
#else
  is_disabled = 0;
#endif

  if (vfs_rename(h, old_username, new_username) == -1)
	return(-1);

  log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
		   "DACS account renamed from \"%s\" to \"%s\"%s",
		   old_username, new_username, is_disabled ? " (disabled)" : ""));

  return(0);
}

/*
 * Update an existing entry.
 */
int
pw_reset_entry(Vfs_handle *h, char *username, char *given_passwd,
			   Passwd_digest_alg alg, Pw_state new_state,
			   Pw_op data_op, Ds *data)
{
  int is_disabled;
  char *digest;
  Pw_entry *pw;

  if ((pw = pw_read_entry(h, username)) == NULL)
	return(-1);

  if ((data_op == PW_OP_DELETE_DATA || data_op == PW_OP_SET_DATA)
	  && pw->private != NULL) {
	ds_free(pw->private);
	pw->private = NULL;
  }

  if (data_op == PW_OP_SET_DATA)
	pw->private = data;

  if (new_state == PW_DISABLED
	  || (new_state == PW_UNCHANGED && pw->state == PW_DISABLED)) {
	pw->state = PW_DISABLED;
	is_disabled = 1;
  }
  else {
	pw->state = PW_ENABLED;
	is_disabled = 0;
  }

  digest = passwd_make_digest(alg, given_passwd, pw);

  if (vfs_put(h, username, pw->entry, strlen(pw->entry) + 1) == -1)
	return(-1);

  log_msg(((Log_level) (LOG_WARN_LEVEL | LOG_AUDIT_FLAG),
		   "DACS account updated for \"%s\"%s",
		   username, is_disabled ? " (disabled)" : ""));

  return(0);
}

/*
 * Modify an existing entry.
 */
int
pw_modify_entry(Vfs_handle *h, char *username, int ops, Ds *data)
{
  int st;
  char *digest;
  Pw_entry *pw;

  if ((pw = pw_read_entry(h, username)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(-1);
  }

  if (ops & PW_OP_DISABLE) {
	if (pw->state == PW_DISABLED) {
	  log_msg((LOG_NOTICE_LEVEL, "Attempt to disable a disabled entry"));
	  return(0);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Disabling DACS account for \"%s\"", username));

	pw->state = PW_DISABLED;
  }
  else if (ops & PW_OP_ENABLE) {
	if (pw->state == PW_ENABLED) {
	  log_msg((LOG_NOTICE_LEVEL, "Attempt to enable an enabled entry"));
	  return(0);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Enabling DACS account for \"%s\"", username));

	pw->state = PW_ENABLED;
  }

  if (ops & PW_OP_SET_DATA) {
	if (data == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No data provided to set-data operation"));
	  return(-1);
	}
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Setting data for DACS account \"%s\"", username));


	pw->private = data;
  }
  else if (ops & PW_OP_DELETE_DATA) {
	if (pw->private)
	  ds_free(pw->private);
	log_msg(((Log_level) (LOG_INFO_LEVEL | LOG_AUDIT_FLAG),
			 "Deleting data for DACS account \"%s\"", username));

	pw->private = NULL;
  }

  digest = pw_make_entry(pw);
  st = vfs_put(h, username, pw->entry, strlen(pw->entry) + 1);

  return(st);
}

int
pw_disable_entry(Vfs_handle *h, char *username)
{
  int st;

  st = pw_modify_entry(h, username, PW_OP_DISABLE, NULL);

  return(st);
}

int
pw_enable_entry(Vfs_handle *h, char *username)
{
  int st;

  st = pw_modify_entry(h, username, PW_OP_ENABLE, NULL);

  return(st);
}

/*
 * Parse a PASSWORD_CONSTRAINTS directive.
 */
int
pw_parse_constraints(char *constraint, Pw_constraints *conf)
{
  int i;
  unsigned int val;
  char *endptr, *errmsg, *p;
  Dsvec *dsv;

  conf->minlen = -1;
  conf->mixed_case = -1;
  conf->punct = -1;
  conf->digits = -1;

  for (p = constraint; p != NULL && (*p == ' ' || *p == '\t'); p++)
	;

  if (p == NULL || *p == '\0' || strcaseeq(p, "none"))
	goto done;

  if ((dsv = strsplit_re(p, "[ \t]*,[ \t]*", 0, 0, &errmsg)) == NULL)
	return(-1);

  for (i = 0; i < dsvec_len(dsv); i++) {
	p = dsvec_ptr(dsv, i, char *);
	/* printf("%s\n", p); */
	val = strtoul(p, &endptr, 10);
	if (endptr == p || *(endptr + 1) != '\0')
	  return(-1);

	switch ((int) *endptr) {
	case 'C':
	  if (conf->mixed_case != -1)
		return(-1);
	  conf->mixed_case = (int) val;
	  break;

	case 'D':
	  if (conf->digits != -1)
		return(-1);
	  conf->digits = (int) val;
	  break;

	case 'L':
	  if (conf->minlen != -1)
		return(-1);
	  conf->minlen = (int) val;
	  break;

	case 'P':
	  if (conf->punct != -1)
		return(-1);
	  conf->punct = (int) val;
	  break;

	default:
	  return(-1);
	  /*NOTREACHED*/
	}
  }

 done:
  /* Initialize defaults as required. */
  if (conf->minlen == -1)
	conf->minlen = PASSWD_MINIMUM_LENGTH;
  if (conf->minlen < 6)
	conf->minlen = 6;		/* Guard against a potentially grave mistake.  */

  if (conf->mixed_case == -1)
	conf->mixed_case = PASSWD_NEEDS_MIXED_CASE;

  if (conf->punct == -1)
	conf->punct = PASSWD_NEEDS_PUNCTUATION;

  if (conf->digits == -1)
	  conf->digits = PASSWD_NEEDS_DIGITS;

  return(0);
}

/*
 * Do some simple checks to reject stoopid passwords.
 * Return 1 if PASSWD is ok, 0 otherwise.
 */
int
pw_is_passwd_acceptable(char *passwd, char *constraints)
{
  Pw_constraints conf;
  size_t len;

  len = strlen(passwd);

  if (len == 0 && !require_password)
	return(1);

  if (pw_parse_constraints(constraints, &conf) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Invalid constraints string"));
	return(0);
  }

  if (len < conf.minlen)
	return(0);

  if (conf.mixed_case > 0) {
	if (strchrscount(passwd, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") < conf.mixed_case)
	  return(0);
	if (strchrscount(passwd, "abcdefghijklmnopqrstuvwxyz") < conf.mixed_case)
	  return(0);
  }

  if (conf.punct > 0) {
	if (strchrscount(passwd, ",<.>/?'\";:\\|}]{[=+-_)(*&^%$#@!`~")
		< conf.punct)
	  return(0);
  }

  if (conf.digits > 0) {
	if (strchrscount(passwd, "0123456789") < conf.digits)
	  return(0);
  }

  return(1);
}

char *
pw_prompt_new_password(char *username, char *msg, char **errmsg)
{
  char *m, *passwd, *passwd2, *prompt;
  Ds ds1, ds2;

  if ((m = msg) == NULL)
	m = "password";

  prompt = ds_xprintf("New %s for %s? ", m, username);
  ds_init(&ds1);
  if ((passwd = ds_prompt(&ds1, prompt, DS_PROMPT_NOECHO)) == NULL) {
	*errmsg = ds_xprintf("No %s?", m);
	ds_free(&ds1);
	return(NULL);
  }

  if (!pw_is_passwd_acceptable(passwd,
							   conf_val(CONF_PASSWORD_CONSTRAINTS))) {
	fprintf(stderr, "Warning:  This %s is insecure.\n", m);
	fprintf(stderr, "It does not meet the configured requirements.\n");
	fprintf(stderr, "You may proceed but consider using a better %s.\n", m);
  }

  prompt = ds_xprintf("Re-type new %s for %s? ", m, username);
  ds_init(&ds2);
  if ((passwd2 = ds_prompt(&ds2, prompt, DS_PROMPT_NOECHO)) == NULL) {
	*errmsg = ds_xprintf("No %s?", m);
	ds_free(&ds1);
	ds_free(&ds2);
	return(NULL);
  }

  if (!streq(passwd, passwd2)) {
	*errmsg = ds_xprintf("The two %s strings are not identical", m);
	ds_free(&ds1);
	ds_free(&ds2);
	return(NULL);
  }

  ds_free(&ds2);
  return(passwd);
}

static void
list_qsort(void *base, size_t nmemb, size_t size,
		   int (*compar)(const void *, const void *))
{
  void *b;
  Dsvec *dsv;

  dsv = (Dsvec *) base;
  b = (void *) dsvec_base(dsv);

  qsort(b, nmemb, size, compar);
}

static int
list_add(char *naming_context, char *name, void ***names)
{
  char *p;
  Dsvec *dsv;

  dsv = (Dsvec *) names;

  if (naming_context != NULL && (p = strprefix(name, naming_context)) != NULL)
    dsvec_add_ptr(dsv, strdup(p + 1));
  else
    dsvec_add_ptr(dsv, strdup(name));

  return(1);
}

static int
list_compar(const void *a, const void *b)
{
  char **p, **q;

  p = (char **) a;
  q = (char **) b;

  return(strcmp(*p, *q));
}

static void
list_display(FILE *fp, Pw_entry *pw)
{

  if (test_emit_format(EMIT_FORMAT_HTML))
	printf("<b>%s</b>%s<br/>\n", pw->username,
		   (pw->state == PW_DISABLED) ? "<tt>*</tt>" : "");
  else
	printf("%s%s\n", pw->username,
		   (pw->state == PW_DISABLED) ? "*" : "");
}

Dsvec *
pw_list_entries(Vfs_handle *h)
{
  int n;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(char *));
  h->list_sort = list_qsort;
  if ((n = vfs_list(h, NULL, list_compar, list_add, (void ***) dsv)) == -1)
	return(NULL);

  return(dsv);
}

static int
list_entries(Vfs_handle *h, char *username)
{
  int i;
  Dsvec *dsv;
  Pw_entry *pw;

  if (username != NULL) {
	if ((pw = pw_read_entry(h, username)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Lookup of username \"%s\" failed", username));
	  return(-1);
	}

	list_display(stdout, pw);
	return(0);
  }

  if ((dsv = pw_list_entries(h)) == NULL)
	return(-1);

  if (test_emit_format(EMIT_FORMAT_HTML))
	emit_html_header(stdout, NULL);

  for (i = 0; i < dsvec_len(dsv); i++) {
	char *name;

	name = (char *) dsvec_ptr_index(dsv, i);
	if ((pw = pw_read_entry(h, name)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Lookup of username \"%s\" failed", name));
	  return(-1);
	}

	list_display(stdout, pw);
  }

  return(dsvec_len(dsv));
}

/*
 * Perform test(s) OP on the entry for USERNAME.
 */
int
pw_test_entry(Vfs_handle *h, Pw_op op, char *username)
{
  int exists;
  Pw_entry *pw;

  if ((exists = pw_exists_entry(h, username)) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Lookup of username \"%s\" failed", username));
	return(-1);
  }

  switch (op) {
  case PW_OP_TEST_EXISTS:
	return(exists);
	/*NOTREACHED*/

  case PW_OP_TEST_DISABLED:
	if (exists && (pw = pw_read_entry(h, username)) == NULL)
	  return(-1);
	if (!exists || pw->state != PW_DISABLED)
	  return(0);
	return(1);
	/*NOTREACHED*/

  case PW_OP_TEST_ENABLED:
	if (exists && (pw = pw_read_entry(h, username)) == NULL)
	  return(-1);
	if (!exists || pw->state != PW_ENABLED)
	  return(0);
	return(1);
	/*NOTREACHED*/

  case PW_OP_TEST_PRIVATE:
	if (exists && (pw = pw_read_entry(h, username)) == NULL)
	  return(-1);
	if (!exists || pw->private == NULL || ds_len(pw->private) == 0)
	  return(0);
	return(1);
	/*NOTREACHED*/

  default:
	log_msg((LOG_ERROR_LEVEL, "Urecognized test operation"));
	return(-1);
	/*NOTREACHED*/
  }

  /*NOTREACHED*/
  return(-1);
}

static void
dacs_usage(void)
{

  fprintf(stderr, "Usage: dacspasswd %s [op] [flags] [--] [username]\n",
		  standard_command_line_usage);
  fprintf(stderr, "Where op is:\n");
  fprintf(stderr, "-a|-add:       add a new entry (default)\n");
  fprintf(stderr, "-d|-del|-delete: delete an entry\n");
  fprintf(stderr, "-l|-list:      list all entries\n");
  fprintf(stderr, "-s|-set:       set an existing entry's password\n");
  fprintf(stderr, "-dis|-disable: disable an entry\n");
  fprintf(stderr, "-en|-ena|-enable: enable an entry\n");
  fprintf(stderr, "-g|-get:       get an account's digest string\n");
  fprintf(stderr, "-p password:   specify the password (possibly insecure)\n");
  fprintf(stderr, "-pdd:          delete account private data\n");
  fprintf(stderr, "-pdg:          get account private data\n");
  fprintf(stderr, "-pds data:     set account private data\n");
  fprintf(stderr, "-pdsf file:    set account private data from file\n");
  fprintf(stderr, "-pf file:      read password from file (- is stdin)\n");
  fprintf(stderr, "-simple:       work with simple accounts\n");
  fprintf(stderr, "-test type:    perform a test on an account\n");
  fprintf(stderr, "-vfs vfs_uri:  add a VFS definition\n");

  exit(1);
}

int
pw_check_username(char *username)
{

  if (!is_valid_auth_username(username)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid username: \"%s\"", username));
	return(-1);
  }

  /*
   * We don't want "Bob" and "bob" because that doesn't work well
   * with the NAME_COMPARE directive.
   */
  if (!streq(username, strtolower(username))) {
	log_msg((LOG_ERROR_LEVEL, "Username must be lowercase: \"%s\"", username));
	return(-1);
  }

  return(0);
}

/*
 * dacspasswd(1) - command-line interface
 */
static int
dacspasswd_command(int argc, char **argv)
{
  int do_disable, do_enable, do_test, i, no_stdin, st;
  char *errmsg;
  char *passwd, *username;
  Crypt_keys *keys;
  Ds *data;
  Passwd_digest_alg alg;
  Pw_op op, op_data;
  Vfs_handle *h;

  errmsg = "Internal error";
  h = NULL;
  passwd = NULL;
  data = NULL;
  op_data = PW_OP_NONE;
  do_disable = 0;
  do_enable = 0;
  do_test = 0;
  no_stdin = 0;

  /* This is solely to ensure that the user is an administrator. */
  if ((keys = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL) {
	errmsg = "Permission denied";
	goto fail;
  }
  crypt_keys_free(keys);
	  
  op = PW_OP_NONE;
  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-a") || streq(argv[i], "-add")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_ADD;
	}
	else if (streq(argv[i], "-d") || streq(argv[i], "-del")
			 || streq(argv[i], "-delete")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_DELETE;
	}
	else if (streq(argv[i], "-dis") || streq(argv[i], "-disable"))
	  do_disable = 1;
	else if (streq(argv[i], "-en") || streq(argv[i], "-ena")
			 || streq(argv[i], "-enable"))
	  do_enable = 1;
	else if (streq(argv[i], "-g") || streq(argv[i], "-get")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_GET_DIGEST;
	}
	else if (streq(argv[i], "-l") || streq(argv[i], "-list")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_LIST;
	}
	else if (streq(argv[i], "-s") || streq(argv[i], "-set")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_RESET;
	}
	else if (streq(argv[i], "-u")
			 || streq(argv[i], "-up") || streq(argv[i], "-update")) {
	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }
	  op = PW_OP_UPDATE;
	}
	else if (streq(argv[i], "-pds")) {
	  if (op_data != PW_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Private data required after -sd flag";
		goto fail;
	  }

	  op_data = PW_OP_SET_DATA;
	  data = ds_set(NULL, argv[i]);
	}
	else if (streq(argv[i], "-pdd")) {
	  if (op_data != PW_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }

	  op_data = PW_OP_DELETE_DATA;
	}
	else if (streq(argv[i], "-pdg")) {
	  if (op_data != PW_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }

	  op = op_data = PW_OP_GET_DATA;
	}
	else if (streq(argv[i], "-pdsf")) {
	  if (op_data != PW_OP_NONE) {
		errmsg = "Multiple private data specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Private data file is missing";
		goto fail;
	  }

	  if (streq(argv[i], "-")) {
		if (no_stdin) {
		  errmsg = "The standard input has already been read";
		  goto fail;
		}
		no_stdin = 1;
		data = ds_load_file(NULL, NULL);
	  }
	  else
		data = ds_load_file(NULL, argv[i]);
	  if (data == NULL) {
		errmsg = ds_xprintf("Error reading private data from \"%s\"\n",
							argv[i]);
		goto fail;
	  }
	  op_data = PW_OP_SET_DATA;
	}
	else if (streq(argv[i], "-p")) {
	  if (passwd != NULL) {
		errmsg = "Multiple password specifications";
		goto fail;
	  }
	  if (argv[++i] == NULL) {
		errmsg = "Password required after -p flag";
		goto fail;
	  }

	  passwd = strdup(argv[i]);
	  strzap(argv[i]);
	}
	else if (streq(argv[i], "-pf")) {
	  if (argv[++i] == NULL) {
		errmsg = "Password file is missing";
		goto fail;
	  }

	  if (streq(argv[i], "-")) {
		if (no_stdin) {
		  errmsg = "The standard input has already been read";
		  goto fail;
		}
		no_stdin = 1;
		passwd = get_passwd(GET_PASSWD_STDIN, NULL);
	  }
	  else
		passwd = get_passwd(GET_PASSWD_FILE, argv[i]);
	  if (passwd == NULL) {
		errmsg = ds_xprintf("Error reading password from \"%s\"\n", argv[i]);
		goto fail;
	  }
	}
	else if (streq(argv[i], "-simple")) {
	  require_password = 0;
	  item_type = ITEM_TYPE_SIMPLE;
	}
	else if (streq(argv[i], "-test")) {
	  if (argv[++i] == NULL) {
		errmsg = "Test type is missing";
		goto fail;
	  }

	  if (op != PW_OP_NONE) {
		errmsg = "Only one operation can be specified";
		goto fail;
	  }

	  if (do_test) {
		errmsg = "Only one test is allowed";
		goto fail;
	  }

	  do_test = 1;
	  if (streq(argv[i], "exists") || streq(argv[i], "ex"))
		op = PW_OP_TEST_EXISTS;
	  else if (streq(argv[i], "disabled") || streq(argv[i], "dis"))
		op = PW_OP_TEST_DISABLED;
	  else if (streq(argv[i], "enabled") || streq(argv[i], "ena")
			   || streq(argv[i], "en"))
		op = PW_OP_TEST_ENABLED;
	  else if (streq(argv[i], "data"))
		op = PW_OP_TEST_PRIVATE;
	  else {
		errmsg = "Unrecognized test type";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-vfs")) {
	  Vfs_directive *vd;

	  if (argv[++i] == NULL) {
		errmsg = "VFS argument is missing";
		goto fail;
	  }

	  /* A VFS spec */
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		errmsg = "Invalid vfs_uri";
		goto fail;
	  }
	  add_vfs_uri(var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf"), argv[i]);
	}
	else if (streq(argv[i], "--")) {
	  /* End of flag arguments */
	  i++;
	  break;
	}
	else if (argv[i][0] == '-') {
	  errmsg = NULL;
	  goto fail;
	}
	else
	  break;
  }

  if (do_disable && do_enable) {
	errmsg = "-disable and -enable are mutually exclusive";
	goto fail;
  }

  /*
   * If no "exclusive" operation was requested, but either enable/disable
   * and/or one of the data set/delete operations is specified, then the reset
   * operation is implied.
   * Failing that, the add operation is the default.
   */
  if (op == PW_OP_NONE) {
	if (do_disable)
	  op |= PW_OP_DISABLE | PW_OP_MODIFY;
	else if (do_enable)
	  op |= PW_OP_ENABLE | PW_OP_MODIFY;

	if (op_data == PW_OP_SET_DATA || op_data == PW_OP_DELETE_DATA)
	  op |= op_data | PW_OP_MODIFY;

	if (op == PW_OP_NONE)
	  op = PW_OP_ADD;
  }

  /* Check for improper combinations of disable/enable */
  if ((do_disable || do_enable)
	  && (op == PW_OP_LIST || op == PW_OP_GET_DATA || do_test)) {
	errmsg = "Account enable/disable requires add, set, or update";
	goto fail;
  }

  /* Check for improper combinations of a data operation */
  if (op_data != PW_OP_NONE && op_data != PW_OP_GET_DATA) {
	if (op == PW_OP_LIST || do_test
		|| (op_data == PW_OP_DELETE_DATA
			&& (op != PW_OP_RESET && op != PW_OP_UPDATE))) {
	  errmsg = "Account private data operation requires add, set, or update";
	  goto fail;
	}
  }

  username = NULL;
  if (op == PW_OP_LIST) {
	if (i == argc)
	  username = NULL;
	else if (i == (argc - 1))
	  username = argv[argc - 1];
	else {
	  errmsg = NULL;
	  goto fail;
	}
  }
  else {
	if (i != (argc - 1)) {
	  errmsg = NULL;
	  goto fail;
	}
	username = argv[argc - 1];
  }

  if (username != NULL && pw_check_username(username) == -1) {
	errmsg = "Invalid USERNAME argument";
	goto fail;
  }

  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }

  st = 0;
  if (op == PW_OP_LIST) {
	if (list_entries(h, username) == -1) {
	  errmsg = "Error listing entries";
	  goto fail;
	}
  }
  else if (do_test) {
	if ((st = pw_test_entry(h, op, username)) == -1) {
	  st = 2;
	  errmsg = "Error testing entry";
	  goto fail;
	}
	/*
	 * Reverse the sense of the return value because exit code 0 means "true"
	 * and anything else means "false" or "error".
	 */
	if (st != -1)
	  st = !st;
  }
  else if (op == PW_OP_GET_DATA) {
	char *p;
	Ds *ds;

	if ((ds = pw_getdata_entry(h, username)) == NULL) {
	  errmsg = "Error getting private data";
	  goto fail;
	}

	if ((p = ds_buf(ds)) != NULL && *p != '\0')
	  printf("%s", ds_buf(ds));
  }
  else if (op & PW_OP_GET_DIGEST) {
	char *digest_str;

	if ((digest_str = pw_getdigest_entry(h, username)) == NULL) {
	  st = -1;
	  errmsg = "Error getting digest";
	  goto fail;
	}
	printf("%s\n", digest_str);
  }
  else if (op & PW_OP_MODIFY) {
	if ((st = pw_modify_entry(h, username, op, data)) == -1) {
	  errmsg = "Error modifying entry";
	  goto fail;
	}
  }
  else if (op == PW_OP_DELETE) {
	if ((st = pw_delete_entry(h, username)) == -1) {
	  errmsg = "Error deleting entry";
	  goto fail;
	}
  }
  else if (op == PW_OP_DISABLE) {
	if ((st = pw_disable_entry(h, username)) == -1) {
	  errmsg = "Error disabling entry";
	  goto fail;
	}
  }
  else if (op == PW_OP_ENABLE) {
	if ((st = pw_enable_entry(h, username)) == -1) {
	  errmsg = "Error enabling entry";
	  goto fail;
	}
  }
  else if (op == PW_OP_ADD || op == PW_OP_RESET || op == PW_OP_UPDATE) {
	char *digest_name;
	Pw_state new_state;

	digest_name = NULL;
	if (passwd_get_digest_algorithm(&digest_name, &alg) == -1) {
	  errmsg = "An unrecognized digest method is configured";
	  goto fail;
	}

	if (!require_password)
	  passwd = "";
	else if (passwd == NULL
			 && (passwd = pw_prompt_new_password(username, NULL, &errmsg))
			 == NULL)
	  goto fail;

	if (do_disable)
	  new_state = PW_DISABLED;
	else if (do_enable)
	  new_state = PW_ENABLED;
	else
	  new_state = PW_UNCHANGED;

	if (op == PW_OP_ADD)
	  st = pw_add_entry(h, username, passwd, alg, new_state, data);
	else if (op == PW_OP_UPDATE)
	  st = pw_replace_entry(h, username, passwd, alg, new_state,
							op_data, data);
	else
	  st = pw_reset_entry(h, username, passwd, alg, new_state, op_data, data);
	strzap(passwd);
	if (st == -1) {
	  errmsg = "Error adding new entry";
	  goto fail;
	}
  }

  if (vfs_close(h) == -1) {
	h = NULL;
	errmsg = "vfs_close() failed";
	goto fail;
  }

  return(st);

 fail:
  if (h != NULL) {
	if (h->error_msg != NULL)
	  fprintf(stderr, "dacspasswd: %s\n", h->error_msg);
	vfs_close(h);
  }
  if (errmsg != NULL)
	fprintf(stderr, "dacspasswd: %s\n", errmsg);
  dacs_usage();
  /*NOTREACHED*/

  return(-1);
}

int
dacspasswd_main(int argc, char **argv, int do_init, void *main_out)
{
  int admin, st;
  char *errmsg, *remote_addr;
  char *passwd, *operation, *new_passwd, *confirm_new_passwd;
  char *account, *digest_name, *username;
  Cookie *cookies;
  Credentials *credentials, *selected;
  unsigned int ncookies;
  DACS_app_type app_type;
  Kwv *kwv;
  Pw_op op;
  Passwd_digest_alg alg;
  Proc_lock *lock;
  Vfs_handle *h;

  errmsg = "Internal error";
  h = NULL;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	app_type = DACS_UTILITY;
	log_module_name = "dacspasswd";
  }
  else {
	app_type = DACS_WEB_SERVICE;
	log_module_name = "dacs_passwd";
  }

  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	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 delay? */

	if (test_emit_xml_format()) {
	  Common_status status;

	  emit_xml_header(stdout, "dacs_passwd");
      printf("<%s>\n", make_xml_root_element("dacs_passwd"));
      init_common_status(&status, NULL, NULL, errmsg);
      fprintf(stdout, "%s", make_xml_common_status(&status));
      printf("</dacs_passwd>\n");
      emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_HTML)) {
	  emit_html_header_status_line(stdout, "400", errmsg);
	  printf("Request failed: %s\n", errmsg);
	  emit_html_trailer(stdout);
	}
	else {
	  fprintf(stderr, "%s\n", errmsg);
	  dacs_usage();
	  /*NOTREACHED*/
	}

	return(-1);
  }

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

  /* Here is where the command version splits from the web service version. */
  if (app_type == DACS_UTILITY) {
	if ((st = dacspasswd_command(argc, argv)) == -1)
	  return(-1);

	return(st);
  }

  /* The following is for the web service only. */

  operation = kwv_lookup_value(kwv, "OPERATION");
  if (operation == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing OPERATION argument"));
	errmsg = "No OPERATION argument was given";
	goto fail;
  }
  if (strcaseeq(operation, "ADD"))
	op = PW_OP_ADD;
  else if (strcaseeq(operation, "DELETE"))
	op = PW_OP_DELETE;
  else if (strcaseeq(operation, "LIST"))
	op = PW_OP_LIST;
  else if (strcaseeq(operation, "SET"))
	op = PW_OP_RESET;
  else if (strcaseeq(operation, "DISABLE"))
	op = PW_OP_DISABLE;
  else if (strcaseeq(operation, "ENABLE"))
	op = PW_OP_ENABLE;
  else {
	log_msg((LOG_ERROR_LEVEL, "Unrecognized OPERATION argument"));
	errmsg = "Invalid OPERATION argument";
	goto fail;
  }

  account = kwv_lookup_value(kwv, "ACCOUNT");
  username = kwv_lookup_value(kwv, "USERNAME");
  if (op != PW_OP_LIST) {
	if (username == NULL || *username == '\0') {
	  log_msg((LOG_ERROR_LEVEL, "Missing USERNAME argument"));
	  errmsg = "Missing USERNAME argument";
	  goto fail;
	}
  }

  if (username != NULL && pw_check_username(username) == -1) {
	errmsg = "Invalid USERNAME argument";
	goto fail;
  }

  digest_name = NULL;
  if (passwd_get_digest_algorithm(&digest_name, &alg) == -1) {
	errmsg = "Configuration error";
	goto fail;
  }

  passwd = kwv_lookup_value(kwv, "PASSWORD");
  new_passwd = kwv_lookup_value(kwv, "NEW_PASSWORD");
  confirm_new_passwd = kwv_lookup_value(kwv, "CONFIRM_NEW_PASSWORD");

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

  if (get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL) < 1) {
	errmsg = "No credentials";
	goto fail;
  }

  admin = is_dacs_admin(selected);

  if (account != NULL) {
	if (strcaseeq(account, "PASSWD")) {
	  require_password = 1;
	  item_type = ITEM_TYPE_PASSWDS;
	}
	else if (strcaseeq(account, "SIMPLE")) {
	  if (!admin) {
		log_msg((LOG_ERROR_LEVEL, "Request to perform admin function denied"));
		goto fail;
	  }
	  require_password = 0;
	  item_type = ITEM_TYPE_SIMPLE;
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Invalid ACCOUNT argument"));
	  goto fail;
	}
  }
	
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	goto fail;
  }

  /*
   * This can also be addressed by access control rules, redundantly or
   * more specifically.
   */
  if (!admin && op != PW_OP_LIST && op != PW_OP_RESET) {
	log_msg((LOG_ERROR_LEVEL, "Request to perform admin function denied"));
	goto fail;
  }

  if (!admin && op != PW_OP_LIST
	  && !conf_val_eq(CONF_PASSWORD_OPS_NEED_PASSWORD, "no")) {
	if (passwd == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Current password is required for username \"%s\"", username));
	  errmsg = "Missing or invalid PASSWORD argument";
	  goto fail;
	}
	if (pw_check_passwd(h, username, passwd, alg) != 0) {
	  errmsg = "Invalid password";
	  goto fail;
	}
  }

  if (op == PW_OP_RESET || op == PW_OP_ADD) {
	if (new_passwd != NULL && confirm_new_passwd != NULL) {
	  if (!streq(new_passwd, confirm_new_passwd)) {
		log_msg((LOG_ERROR_LEVEL, "Passwords do not match"));
		errmsg = "Password and retyped password do not agree";
		goto fail;
	  }
	  strzap(confirm_new_passwd);
	  if (!admin
		  && !pw_is_passwd_acceptable(new_passwd,
									  conf_val(CONF_PASSWORD_CONSTRAINTS))) {
		errmsg = "New password is not acceptable";
		goto fail;
	  }
	}
	else {
	  if (new_passwd == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Missing NEW_PASSWORD argument"));
		errmsg = "Missing or invalid NEW_PASSWORD argument";
	  }
	  else if (confirm_new_passwd == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Missing CONFIRM_NEW_PASSWORD argument"));
		errmsg = "Missing or invalid CONFIRM_NEW_PASSWORD argument";
	  }
	  goto fail;
	}
  }

  switch (op) {
  case PW_OP_ADD:
	st = pw_replace_entry(h, username, new_passwd, alg, 0, PW_OP_NONE, NULL);
	break;
  case PW_OP_RESET:
	st = pw_reset_entry(h, username, new_passwd, alg, 0, PW_OP_NONE, NULL);
	break;
  case PW_OP_DELETE:
	st = pw_delete_entry(h, username);
	break;
  case PW_OP_LIST:
	st = list_entries(h, username);
	break;
  case PW_OP_DISABLE:
	st = pw_disable_entry(h, username);
	break;
  case PW_OP_ENABLE:
	st = pw_enable_entry(h, username);
	break;
  default:
	goto fail;
	/*NOTREACHED*/
	break;
  }

  if (st == -1) {
	errmsg = "Operation failed";
	goto fail;
  }

  if (vfs_close(h) == -1) {
	h = NULL;
	errmsg = "vfs_close() failed";
	goto fail;
  }

  if (test_emit_format(EMIT_FORMAT_HTML)) {
	emit_html_header(stdout, NULL);
	if (op != PW_OP_LIST)
	  printf("\nOperation succeeded\n");
	emit_html_trailer(stdout);
  }
  else if (test_emit_xml_format()) {
	Common_status status;

	emit_xml_header(stdout, "dacs_passwd");
	printf("<%s>\n", make_xml_root_element("dacs_passwd"));
	init_common_status(&status, NULL, "0", "Request succeeded");
	printf("%s", make_xml_common_status(&status));
	printf("</dacs_passwd>\n");
	emit_xml_trailer(stdout);
  }

  return(0);
}

#else

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

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

  exit((rc == -1) ? 2 : rc);
}
#endif
