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

/*
 * Administration/Configuration support services
 * The implementation will try to adhere to the REST architecture.
 *
 * Eventually, this service/utility will subsume and replace many of the
 * existing DACS utilities.
 *
 * Object           Semantics
 * /                The root object; GET returns a list of subordinate objects
 * /acls            The root site ACL object; GET returns a list of ACLs
 * /acls/<name>     A (site) ACL object; GET returns it
 * As above but with /dacs_acls for the DACS default ACLs
 *
 *
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: admin.c 2586 2012-03-15 16:21:40Z brachman $";
#endif

#include "dacs.h"

typedef struct Root_res Root_res;

struct Root_res {
  char *name;
  char *label;
  int visible;
  int (*req)(Root_res *, Http_method, char **, Kwv *);
};

static int req_accounts(Root_res *, Http_method, char **, Kwv *);
static int req_acls(Root_res *, Http_method, char **, Kwv *);
static int req_activity(Root_res *, Http_method, char **, Kwv *);
static int req_conf(Root_res *, Http_method, char **, Kwv *);
static int req_federation(Root_res *, Http_method, char **, Kwv *);
static int req_groups(Root_res *, Http_method, char **, Kwv *);
static int req_jurisdiction(Root_res *, Http_method, char **, Kwv *);
static int req_jurisdictions(Root_res *, Http_method, char **, Kwv *);
static int req_logs(Root_res *, Http_method, char **, Kwv *);
static int req_revocations(Root_res *, Http_method, char **, Kwv *);
static int req_root(Root_res *, Http_method, char **, Kwv *);
static int req_tokens(Root_res *, Http_method, char **, Kwv *);
static int req_version(Root_res *, Http_method, char **, Kwv *);

/* Root resource names and handlers, in alphabetical order. */
static Root_res root_resources[] = {
  { "/",              "",                              0, req_root },
  { "/accounts",      "DACS User Accounts",            1, req_accounts },
  { "/acls",          "Custom Access Control Rules",   1, req_acls },
  { "/activity",      "User Activity",                 1, req_activity },
  { "/conf",          "Configuration Directives",      1, req_conf },
  { "/dacs_acls",     "Standard Access Control Rules", 1, req_acls },
  { "/federation",    "Federation Information",        1, req_federation },
  { "/groups",        "Group Definitions",             1, req_groups },
  { "/jurisdiction",  "",                              0, req_jurisdiction },
  { "/jurisdictions", "List of Jurisdictions",         1, req_jurisdictions },
  { "/logs",          "DACS Log File Entries",         1, req_logs },
  { "/revocations",   "Access and Login Revocations",  1, req_revocations },
  { "/tokens",        "Authorization Token Cache",     1, req_tokens },
  { "/version",       "Version Information",           1, req_version },
  { NULL,             NULL,                            0, NULL }
};

static int emit_xml = 0;

static char *log_module_name = "dacs_admin";

static void
html_init(FILE *fp, char *redirect)
{
  Html_header_conf *hc;

  hc = emit_html_header_conf(NULL);

  if (redirect != NULL) {
	hc->redirect_url = redirect;
	emit_html_header(fp, hc);
	return;
  }

  if (conf_val(CONF_CSS_PATH) != NULL)
	hc->css = ds_xprintf("%s/dacs_admin.css", conf_val(CONF_CSS_PATH));
  else
	hc->css = CSS_DIR/**/"/dacs_admin.css";
  hc->title = ds_xprintf("DACS Administration for %s",
						 dacs_current_jurisdiction());

  emit_html_header(fp, hc);

  printf("<script language=\"javascript\" type=\"text/javascript\">\n");
  printf("<!--\n");
  printf("function send_req(resource, obj, opname)\n");
  printf("{\n");
  printf("  if (opname != \"\") {\n");
  printf("    url = \"%s/\" + resource + \"/\" + obj + \"?\" + opname + \"=yes&FORMAT=HTML\";\n",
		 current_uri_script());
  printf("    window.open(url, '_self');\n");
  printf("  }\n");
  printf("}\n");
  printf("-->\n");
  printf("</script>\n");
}

static char *
html_span(char *class, char *text)
{
  char *s;

  if (text == NULL)
	s = ds_xprintf("<span class=\"%s\">", class);
  else
	s = ds_xprintf("<span class=\"%s\">%s</span>", class, text);

  return(s);
}

static char *
row_class(char *el_name, int n)
{

  if (n % 2)
	return(ds_xprintf("<%s class=\"%s_odd\">", el_name, el_name));
  else
	return(ds_xprintf("<%s class=\"%s_even\">", el_name, el_name));
}

static char *
build_uri(char *rel_path)
{
  Ds ds;

  ds_init(&ds);
  current_uri_no_query(&ds);

  if (rel_path != NULL && rel_path[0] != '\0')
	ds_asprintf(&ds, "%s", rel_path);

  return(ds_buf(&ds));
}

static char *
build_uri_with_path(char *abs_path)
{
  char *path, *prog, *query, *fragment;
  Ds ds;

  ds_init(&ds);
  if ((prog = current_uri_script()) == NULL)
	return(NULL);
  path = ds_xprintf("%s%s", prog, abs_path);

  return(path);
}

static char *
html_link(char *uri, char *label)
{
  char *str;

  str = ds_xprintf("<a href=\"%s\">%s</a>", uri, label);

  return(str);
}

static void
output_link(FILE *fp, char *uri, char *label)
{

  fprintf(fp, "%s", html_link(uri, label));
}

static char *
td_name_value(char *name, char *value, char *el)
{
  char *uri;
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "<td class=\"res_name\">");

  if (el == NULL)
	uri = build_uri(ds_xprintf(""));
  else if (*el == '\0')
	uri = build_uri(ds_xprintf("/%s", name));
  else
	uri = build_uri(ds_xprintf("/%s", el));
  ds_asprintf(&ds, "<a href=\"%s\">%s</a></td>", uri, name);

  ds_asprintf(&ds, "<td class=\"res_value\">%s</td>", value);

  return(ds_buf(&ds));
}

static char *
td_link(char *cname, char *label, char *linkto)
{
  char *str, *uri;

  if (linkto == NULL)
	uri = build_uri(ds_xprintf(""));
  else if (*linkto == '\0')
	uri = build_uri(ds_xprintf("/%s", label));
  else
	uri = build_uri(ds_xprintf("/%s", linkto));

  if (cname == NULL || *cname == '\0')
	str = ds_xprintf("<td><a href=\"%s\">%s</a></td>",
					 uri, html_encode(label));
  else
	str = ds_xprintf("<td class=\"%s\"><a href=\"%s\">%s</a></td>",
					 cname, uri, html_encode(label));

  return(str);
}

static Common_status common_status;

/*
 * Operations on the root object.
 */
static int
req_root(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *errmsg, *uri;

  rc = -1;
  errmsg = "Internal error";

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  int c;

	  html_init(stdout, NULL);
	  printf("<h1>Resources at %s</h1>\n", dacs_current_jurisdiction());

	  printf("<p>\n");
	  printf("<table>\n");
	  c = 1;
	  for (i = 0; root_resources[i].req != NULL; i++) {
		if (!root_resources[i].visible)
		  continue;
		printf("%s", row_class("tr", c++));
		uri = build_uri(ds_xprintf("/%s", root_resources[i].name + 1));
		printf("<td>");
		output_link(stdout, uri, root_resources[i].label);
		printf("</td>");

		printf("%s", td_link(NULL,
							 ds_xprintf("(%s)", root_resources[i].name + 1),
							 root_resources[i].name + 1));
		printf("</tr>\n");
	  }
	  printf("</table>\n");
	  printf("</p>\n");

	  printf("<p>\n");
	  printf("<span class=\"ident\">%s</ident>\n", dacs_version_string());
	  printf("</p>\n");
	}
	else {
	  /* This should emit WSDL or some other web services description */
	  init_common_status(&common_status, "dacs_admin", 0,
						 "Unsupported method");
	  return(-1);
	}
	break;

  case HTTP_HEAD_METHOD:
	emit_http_header_status(stdout, "200", "OK");
	break;

  case HTTP_PUT_METHOD:
  case HTTP_DELETE_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  emit_html_trailer(stdout);

  return(0);
}

static int
html_list_acls(Root_res *res, char **errmsg)
{
  int acl_count, i;
  char *buf, *uri;
  Acl_file **acls;
  Acl_rule *acl;
  Dsvec *acl_dsv;
  Vfs_handle *h;

  if ((h = vfs_open_item_type(res->name + 1)) == NULL) {
	*errmsg = ds_xprintf("Could not find item type \"%s\"", res->name + 1);
	return(-1);
  }

  if ((acl_dsv = get_acls(h)) == NULL) {
	*errmsg = "Error getting ACL list";
	vfs_close(h);
	return(-1);
  }
  acl_dsv = sort_acls(acl_dsv);
  acls = (Acl_file **) dsvec_base(acl_dsv);
  acl_count = dsvec_len(acl_dsv);

  printf("<h1>Access control rules (%s)</h1>\n", res->name);
  printf("<p>\n");
  for (i = 0; i < acl_count; i++) {
	if (i == 0)
	  printf("<ol class=\"acl_list\">\n");
	printf("%s", row_class("li", i));
	printf("<table>\n");
	printf("<tr>\n");
	printf("<td width=\"30%%\" align=\"left\">\n");
	uri = build_uri(ds_xprintf("/%s", acls[i]->aclname));
	output_link(stdout, uri, acls[i]->aclname);
	printf("</td>\n");

	printf("<td width=\"30%%\" align=\"center\">\n");
	if (acls[i]->status == ACL_STATUS_ENABLED)
	  printf(" (%s)", html_span("acl_status", "enabled"));
	else if (acls[i]->status == ACL_STATUS_DISABLED)
	  printf(" (%s)", html_span("acl_status", "disabled"));
	else
	  printf(" (%s)", html_span("acl_status", "?UNKNOWN?"));
	printf("</td>\n");

	printf("<td width=\"30%%\" align=\"right\">\n");
	printf("<form method=\"post\" action=\"%s/%s\">\n",
		   current_uri_no_query(NULL), acls[i]->aclname);
	printf("<select name=\"OP\"");
#ifdef NOTDEF
	printf("onchange='send_req(\"acls\", \"%s\", this.options[this.options.selectedIndex].value)'", acls[i]->aclname);
#endif
#ifdef NOTDEF
	printf("onChange=\"this.form.submit();\"");
#endif
	printf(">\n");

	printf("<option value=\"\" selected>-&nbsp;-&nbsp;-&nbsp;-&nbsp;-&nbsp;-&nbsp;-&nbsp;-&nbsp;</option>\n");
	if (acls[i]->status != ACL_STATUS_ENABLED)
	  printf("<option value=\"enabled\">Enable</option>\n");
	if (acls[i]->status == ACL_STATUS_ENABLED)
	  printf("<option value=\"disabled\">Disable</option>\n");
	printf("<option value=\"delete\">Delete</option>\n");
	printf("<option value=\"add_service\">Add service</option>\n");
	printf("<option value=\"edit_rule\">Edit rule</option>\n");
	printf("</select>\n");
	printf("<input type=\"hidden\" name=\"FORMAT\" value=\"HTML\">\n");
	printf("<input type=\"submit\" value=\"Submit\">\n");

	printf("</form>\n");
	printf("</td>\n");
	printf("</tr>\n");
	printf("</table>\n");

	if (vfs_get(h, acls[i]->path, (void *) &buf, NULL) == -1)
	  printf("<b>ACL read error!</b>");
	else {
	  if (parse_xml_acl(buf, &acl) == -1)
		printf("<b>ACL parse error!</b>");
	  else {
		Service *s;

		printf("\n<ol class=\"service_list\">\n");
		for (s = acl->services->service; s != NULL; s = s->next) {
		  printf("<li>\n");
		  if (s->rule_uri != NULL) {
			printf("%s ", html_span("service_keyword", "delegate"));
			if (s->url_pattern != NULL) {
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_pattern"),
					 html_span("service_url_pattern",
							   strquote(s->url_pattern, "\"")));
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "rule_uri"),
					 html_span("service_rule_uri", s->rule_uri));
			}
			else if (s->url_expr != NULL) {
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_expr"),
					 html_span("service_url_expr",
							   strquote(s->url_expr, "\"")));
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "rule_uri"),
					 html_span("service_rule_uri", s->rule_uri));
			}
			else {
			  /* Impossible? */
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_pattern"), "???");
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "rule_uri"),
					 html_span("service_rule_uri", s->rule_uri));
			}
		  }
		  else {
			if (s->url_pattern != NULL) {
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_pattern"),
					 html_span("service_url_pattern",
							   strquote(s->url_pattern, "\"")));
			}
			else if (s->url_expr != NULL) {
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_expr"),
					 html_span("service_url_expr",
							   strquote(s->url_expr, "\"")));
			}
			else {
			  /* Impossible? */
			  printf("%s=\"%s\" ",
					 html_span("service_keyword", "url_pattern"), "???");
			}
		  }
		  printf("</li>\n");
		}
		printf("</ol>\n");
	  }
	}

	printf("</li>\n");
  }
  if (i != 0)
	printf("</ol>\n");
  printf("</p>\n");

  vfs_close(h);

  return(0);
}

typedef enum {
  FEDERATION_FEDERATION     = 0,
  FEDERATION_DOMAIN         = 1,
  FEDERATION_FED_ID         = 2,
  FEDERATION_FED_PUBLIC_KEY = 3,
  FEDERATION_INVALID        = -1
} Federation_res_id;

typedef struct Federation_res Federation_res;
struct Federation_res {
  char *name;
  char *label;
  Federation_res_id id;
  char *(*req)(Federation_res *);
};

static char *req_federation_res(Federation_res *);

static Federation_res federation_res[] = {
  { "federation",     "FEDERATION_NAME",    FEDERATION_FEDERATION,
	req_federation_res },
  { "domain",         "FEDERATION_DOMAIN",  FEDERATION_DOMAIN,
	req_federation_res },
  { "fed_id",         "Unique Instance ID", FEDERATION_FED_ID,
	req_federation_res },
  { "fed_public_key", "Public Key",         FEDERATION_FED_PUBLIC_KEY,
	req_federation_res },
  { NULL,             NULL,                 FEDERATION_INVALID,
	NULL }
};

static char *
req_federation_res(Federation_res *res)
{
  char *str;
  Crypt_keys *ck;

  str = NULL;
  switch (res->id) {
  case FEDERATION_FEDERATION:
	str = conf_val(CONF_FEDERATION_NAME);
	break;

  case FEDERATION_DOMAIN:
	str = conf_val(CONF_FEDERATION_DOMAIN);
	break;

  case FEDERATION_FED_ID:
	if ((ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL
		|| ck->fed_id == NULL)
	  str = NULL;
	else
	  str = strdup(ck->fed_id);
	
	if (ck != NULL)
	  crypt_keys_free(ck);
	break;

  case FEDERATION_FED_PUBLIC_KEY:
	if ((ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL
		|| ck->public_key_pem == NULL)
	  str = NULL;
	else
	  str = strdup(ck->public_key_pem);

	if (ck != NULL)
	  crypt_keys_free(ck);
	break;

  case FEDERATION_INVALID:
  default:
	break;
  }

  return(str);
}

/*
 *
 */
static int
req_federation(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *errmsg, *uri;
  Federation_res *fr;

  rc = -1;
  errmsg = "Internal error";

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  int c;

	  html_init(stdout, NULL);
	  if (argv[1] == NULL) {
		char *str;

		printf("<h1>Federation (%s, %s)</h1>\n",
			   conf_val(CONF_FEDERATION_NAME),
			   conf_val(CONF_FEDERATION_DOMAIN));
		printf("<table>\n");
		for (fr = federation_res, c = 0; fr->name != NULL; fr++, c++) {
		  printf("%s\n", row_class("tr", c));
		  printf("%s</td>", td_link(NULL, fr->label, fr->name));

		  printf("<td class=\"res_value\">");
		  if ((str = req_federation_res(fr)) != NULL) {
			if (fr->id == FEDERATION_FED_PUBLIC_KEY)	/* kludgey */
			  printf("<pre class=\"public_key\">%s</pre>", str);
			else
			  printf("%s", str);
		  }
		  printf("</td>\n");
		  printf("</tr>\n");
		}
		printf("</table>\n");
	  }
	  else {
		char *str;

		for (fr = federation_res; fr->name != NULL; fr++) {
		  if (streq(argv[1], fr->name))
			break;
		}

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

		if ((str = req_federation_res(fr)) != NULL)
		  printf("%s\n", html_encode(str));
	  }
	}
	else {
	  /* This should emit WSDL or some other web services description */
	  init_common_status(&common_status, "dacs_admin", 0,
						 "Unsupported method");
	  return(-1);
	}
	break;

  case HTTP_HEAD_METHOD:
	emit_http_header_status(stdout, "200", "OK");
	break;

  case HTTP_PUT_METHOD:
  case HTTP_DELETE_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  emit_html_trailer(stdout);

  return(0);
}

static int
req_jurisdictions(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *errmsg, *uri;
  Jurisdiction *j;

  rc = -1;
  errmsg = "Internal error";

  if (load_jurisdictions() == -1) {
	return(-1);
  }
  j = jurisdictions;

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		html_init(stdout, NULL);
		printf("<h1>Jurisdiction List at %s</h1>\n",
			   dacs_current_jurisdiction());

		printf("<table>\n");
		for (i = 0; i < njurisdictions; i++) {
		  printf("%s\n", row_class("tr", i));
		  uri = build_uri_with_path(ds_xprintf("/jurisdiction/%s",
											   j[i].jname));
		  printf("<td>");
		  output_link(stdout, uri, j[i].jname);
		  printf("</td>\n");

		  printf("<td class=\"res_value\">");
		  printf("%s", html_encode(j[i].name));
		  printf("</td>\n");

		  printf("<td class=\"res_value\">");
		  printf("%s", html_encode(j[i].alt_name));
		  printf("</td>\n");

		  printf("<td>");
		  printf("[<a href=\"%s/dacs_admin\">admin</a>]",
				 j[i].dacs_url);
		  printf("</td>\n");
		  printf("</tr>\n");
		}
		printf("</table>\n");
	  }
	  else {		
		init_common_status(&common_status, "dacs_admin", 0,
						   "Not found");
		return(-1);
	  }
	}
	else {
	  /* This should emit WSDL or some other web services description */
	  init_common_status(&common_status, "dacs_admin", 0,
						 "Unsupported method");
	  return(-1);
	}
	break;

  case HTTP_HEAD_METHOD:
	emit_http_header_status(stdout, "200", "OK");
	break;

  case HTTP_PUT_METHOD:
  case HTTP_DELETE_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  return(0);
}

typedef enum {
  JURISDICTION_JNAME         = 0,
  JURISDICTION_NAME          = 1,
  JURISDICTION_ALT_NAME      = 2,
  JURISDICTION_DACS_URL      = 3,
  JURISDICTION_AUTHENTICATES = 4,
  JURISDICTION_PROMPTS       = 5,
  JURISDICTION_AUXILIARY     = 6,
  JURISDICTION_PUBLIC_KEY    = 7,
  JURISDICTION_INVALID       = -1
} Jurisdiction_res_id;

typedef struct Jurisdiction_res Jurisdiction_res;
struct Jurisdiction_res {
  char *name;
  char *label;
  Jurisdiction_res_id id;
  char *(*req)(Jurisdiction_res *, Jurisdiction *);
};

static char *req_jurisdiction_res(Jurisdiction_res *, Jurisdiction *);

static Jurisdiction_res jurisdiction_res[] = {
  { "jname",        "Jurisdiction Name", JURISDICTION_JNAME,
	req_jurisdiction_res },
  { "name",         "Full Name",         JURISDICTION_NAME,
	req_jurisdiction_res },
  { "alt_name",     "Full Alt Name",     JURISDICTION_ALT_NAME,
	req_jurisdiction_res },
  { "dacs_url",     "Service Prefix",    JURISDICTION_DACS_URL,
	req_jurisdiction_res },
  { "authenticates", "Auth?",            JURISDICTION_AUTHENTICATES,
	req_jurisdiction_res },
  { "prompts",      "Prompts?",          JURISDICTION_PROMPTS,
	req_jurisdiction_res },
  { "auxiliary",    "Aux",               JURISDICTION_AUXILIARY,
	req_jurisdiction_res },
  { "public_key",   "Public Key",        JURISDICTION_PUBLIC_KEY,
	req_jurisdiction_res },
  { NULL,           NULL,                JURISDICTION_INVALID,
	NULL }
};

static char *
req_jurisdiction_res(Jurisdiction_res *jr, Jurisdiction *j)
{
  char *str;
  Crypt_keys *ck;

  str = NULL;
  switch (jr->id) {
  case JURISDICTION_JNAME:
	str = j->jname;
	break;

  case JURISDICTION_NAME:
	str = j->name;
	break;

  case JURISDICTION_ALT_NAME:
	str = j->alt_name;
	break;

  case JURISDICTION_DACS_URL:
	str = expand_dacs_url(j->jname, j->dacs_url);
	break;

  case JURISDICTION_AUTHENTICATES:
	str = j->authenticates;
	break;

  case JURISDICTION_PROMPTS:
	str = j->prompts;
	break;

  case JURISDICTION_AUXILIARY:
	str = j->auxiliary;
	break;

  case JURISDICTION_PUBLIC_KEY:
#ifndef NOTDEF
	str = j->public_key_pem;
#else
	/*
	 * XXX should use get_jurisdiction_meta() to get the public key
	 * for j->jname
	 */
	ck = NULL;
	if (!streq(j->jname, conf_val(CONF_JURISDICTION_NAME))
		|| (ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) == NULL
		|| ck->public_key_pem == NULL) {
	  str = "<Unavailable>";
	  /* XXX error code */
	}
	else
	  str = strdup(ck->public_key_pem);

	if (ck != NULL)
	  crypt_keys_free(ck);
#endif

	break;

  case JURISDICTION_INVALID:
  default:
	break;
  }

  return(str);
}

static int
req_jurisdiction(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *errmsg, *uri;
  Jurisdiction *j;
  Jurisdiction_res *jr;

  rc = -1;
  errmsg = "Internal error";

  if (load_jurisdictions() == -1) {
	return(-1);
  }
  j = jurisdictions;

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  int jnum;

	  if (argv[1] == NULL) {
		log_msg((LOG_TRACE_LEVEL, "Not found"));
		emit_http_header_status(stdout, "404", "Not found");
		return(-1);
	  }

	  for (jnum = 0; jnum < njurisdictions; jnum++) {
		if (streq(argv[1], j[jnum].jname))
		  break;
	  }

	  if (jnum == njurisdictions) {
		log_msg((LOG_TRACE_LEVEL, "Not found: %s", argv[1]));
		emit_http_header_status(stdout, "404", "Not found");
		return(-1);
	  }

	  if (argv[2] == NULL) {
		char *str;

		/* Request is for a jurisdiction's resource listing. */
		html_init(stdout, NULL);
		printf("<h1>Jurisdiction %s at %s</h1>\n",
			   j[jnum].jname, dacs_current_jurisdiction());

		printf("<table>\n");
		i = 0;
		for (jr = jurisdiction_res; jr->name != NULL; jr++) {
		  printf("%s\n", row_class("tr", i++));
		  printf("<td>");
		  uri = build_uri(ds_xprintf("/%s", jr->name));
		  output_link(stdout, uri, jr->label);
		  printf("</td>");

		  printf("<td class=\"res_value\">");
		  if ((str = req_jurisdiction_res(jr, j + jnum)) != NULL) {
			if (jr->id == JURISDICTION_PUBLIC_KEY)
			  printf("<pre class=\"public_key\">%s</pre>", str);
			else
			  printf("%s", str);
		  }
		  else
			printf("&nbsp;");
		  printf("</td>\n");

		  printf("</tr>\n");
		}
		printf("</table>");

		emit_html_trailer(stdout);
	  }
	  else {
		/* Request is for a jurisdiction's resource. */
		char *str;
		Jurisdiction_res *jr;

		if (argv[3] != NULL)
		  return(-1);

		for (jr = jurisdiction_res; jr->name != NULL; jr++) {
		  if (streq(argv[2], jr->name))
			break;
		}

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

		html_init(stdout, NULL);

		if ((str = req_jurisdiction_res(jr, j + jnum)) != NULL)
		  printf("%s\n", html_encode(str));

		emit_html_trailer(stdout);
	  }
	}
	else {
	  /* This should emit WSDL or some other web services description */
	  init_common_status(&common_status, "dacs_admin", 0,
						 "Unsupported method");
	  return(-1);
	}
	break;

  case HTTP_HEAD_METHOD:
	emit_http_header_status(stdout, "200", "OK");
	break;

  case HTTP_PUT_METHOD:
  case HTTP_DELETE_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  return(0);
}

#define ADMIN_HASH_ALPHABET		"a-zA-Z0-9"
#define ADMIN_HASH_LENGTH		8

static char *
hash_str(char *str)
{
  unsigned int digest_len;
  unsigned char *digest;
  char *buf;

  digest = crypto_digest("SHA1", str, strlen(str), NULL, &digest_len);
  if (digest == NULL)
	return(NULL);

  strba64(digest, digest_len, &buf);
  free(digest);

  return(buf);
}

#ifdef NOTDEF
/*
 * Search through ACL_DSV for ACL names that begin with PREFIX, returning
 * names that are one component longer than PREFIX.
 * If PREFIX is NULL, match all prefixes, otherwise
 * PREFIX begins with a slash.
 * ACL_DSV is assumed to be sorted.
 */
static Dsvec *
filter_acls(Dsvec *acl_dsv, char *prefix)
{
  int i, n;
  char *aclname, *dn, *pre, *p, *q;
  Acl_ent *ent;
  Acl_file *af;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(Acl_ent *));
  ent = NULL;
  n = dsvec_len(acl_dsv);

  if (prefix == NULL)
	pre = NULL;
  else {
	pre = strdup(prefix);
	if (*pre == '/')
	  pre++;
	q = pre + strlen(pre) - 1;
	if (*q == '/')
	  *q = '\0';
  }

  for (i = 0; i < n; i++) {
	af = (Acl_file *) dsvec_ptr_index(acl_dsv, i);
	aclname = strdup(af->aclname);

	dn = NULL;
	if (pre == NULL || *pre == '\0') {
	  if ((p = strchr(aclname, (int) '/')) != NULL)
		*(p + 1) = '\0';
	  dn = strdup(aclname);
	}
	else if ((p = strprefix(aclname, pre)) != NULL
			 && (*p == '/' || *p == '\0')) {
	  if (*p == '/') {
		if ((q = strchr(p + 1, (int) '/')) != NULL)
		  *(q + 1) = '\0';
		dn = strdup(p + 1);
	  }
	}

	if (dn != NULL && (ent == NULL || !streq(dn, ent->display_name))) {
	  ent = ALLOC(Acl_ent);
	  ent->display_name = dn;
	  ent->href_path = strtrim(aclname, "/", 0);
	  ent->af = af;
	  dsvec_add_ptr(dsv, ent);
	}
  }

  return(dsv);
}
#endif

/*
 * Operations on a root ACL object or an ACL object.
 *
 * When adding or updating an ACL, the operation fails if the resulting
 * ACL fails to parse.
 */
static int
req_acls(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int acl_count, i, rc;
  int disable_only, enable_only, rules_only, services_only, status_only;
  int do_enable, is_enabled;
  char *buf, *enabled, *errmsg, *hname, *name, *p, *path, *sub_name, *uri;
  char *item_type;
  Acl_index *ad;
  Acl_map *am, *req_am;
  Acl_rule *acl;
  Vfs_handle *h;

  rc = -1;
  errmsg = NULL;
  h = NULL;

  item_type = res->name + 1;
  if ((ad = get_acl_index(item_type)) == NULL) {
	errmsg = ds_xprintf("Error loading ACL index for item type \"%s\"",
						item_type);
	emit_http_header_status(stdout, "404", "Not found");
	goto done;
  }

  acl_count = dsvec_len(ad->acl_map);

  rules_only = 0;
  services_only = 0;
  status_only = 0;
  enable_only = 0;
  disable_only = 0;

  req_am = NULL; 
  if ((hname = argv[1]) != NULL) {
	char *h;

	/* Operate on a specific ACL. */
	for (i = 0; i < acl_count; i++) {
	  am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);
	  h = hash_str(am->path);
	  if (streq(h, hname))
		break;
	}
	if (i == acl_count)
	  return(-1);
	req_am = am;

	if ((sub_name = argv[2]) != NULL) {
	  /* Operate on a sub-component of a specific ACL. */
	  if (streq(sub_name, "rules"))
		rules_only = 1;
	  else if (streq(sub_name, "services"))
		services_only = 1;
	  else if (streq(sub_name, "status"))
		status_only = 1;
	  else if (streq(sub_name, "enable"))
		enable_only = 1;
	  else if (streq(sub_name, "disable"))
		disable_only = 1;
	  else
		return(-1);

	  if (argv[3] != NULL)
		return(-1);
	}
  }

  switch (method) {
  case HTTP_GET_METHOD:
	/*
	 * .../acls
	 * .../acls/<hashof path>
	 * .../acls/<hashof path>/rules
	 * .../acls/<hashof path>/services
	 * .../acls/<hashof path>/status
	 * .../acls/<hashof path>/enabled
	 * .../acls/<hashof path>/disabled
	 */
	if (req_am == NULL) {
	  /* Generate a listing of ACLs. */
	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("<h1>Access control rules (%s) at %s</h1>\n",
			   item_type, dacs_current_jurisdiction());

		printf("<p>\n");
		printf("<table>\n");
		for (i = 0; i < acl_count; i++) {
		  printf("%s\n", row_class("tr", i));
		  printf("<td align=\"left\">\n");

		  am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);
		  uri = build_uri(ds_xprintf("/%s", hash_str(am->path)));
		  output_link(stdout, uri, am->path);
		  printf("</td>\n");

		  printf("<td align=\"left\">\n");
		  fprintf(stdout, "[%s, pri=%u]",
				  html_span("acl_status",
							(am->status == ACL_STATUS_ENABLED)
							? "enabled" : "disabled"),
				  am->priority);

		  printf("</td>\n</tr>\n");
		}
		printf("</table>\n");

		emit_html_trailer(stdout);

		rc = 0;
		goto done;
	  }
	}
	else {
	  char *acl_text;
	  Dsvec *dsv_path;

	  if (!status_only) {
		if ((acl = read_indexed_acl(item_type, req_am, &acl_text)) == NULL) {
		  errmsg = "Error reading ACL";
		  goto done;
		}
	  }

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);

		if (status_only)
		  printf("ACL is enabled.\n");
		else if (rules_only || services_only) {
		  if (rules_only)
			text_html(stdout, acl_rules_xml_text(NULL, acl->rules));
		  else
			text_html(stdout, acl_services_xml_text(NULL, acl->services));
		  printf("\n");
		}
		else
		  text_html(stdout, acl_text);

		emit_html_trailer(stdout);
	  }
	  rc = 0;
	}
	break;
#ifdef NOTDEF

  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
	/*
	 * Create a new ACL or modify an existing one.
	 * If we cannot find the name, assume it's being created.
	 */
	for (i = 0; i < acl_count; i++) {
	  if (streq(path, acls[i]->aclname))
		break;
	}

	if (i == acl_count) {
	  /* Not found - create it (disabled by default). */
	  log_msg((LOG_TRACE_LEVEL, "ACL not found: %s", path));
	  do_enable = 0;
	  if (args->enabled)
		do_enable = 1;
	  else if (args->disabled)
		do_enable = 0;

	  if (do_enable)
		name = acl_build_name(dsv_path, ACL_STATUS_ENABLED);
	  else
		name = acl_build_name(dsv_path, ACL_STATUS_DISABLED);

	  if ((p = args->acl) == NULL) {
		Entity_body *b;

		if ((b = cgiparse_get_entity_body()) != NULL)
		  p = b->body;
		else {
		  errmsg = "No ACL argument found";
		  goto done;
		}
	  }

	  if (parse_xml_acl(p, &acl) == -1) {
		errmsg = "ACL parse error";
		goto done;
	  }
	  log_msg((LOG_TRACE_LEVEL, "New ACL parses successfully"));

	  if (vfs_put(h, name, p, strlen(p)) == -1) {
		if (h->error_msg != NULL)
		  errmsg = ds_xprintf("Could not write ACL: %s", h->error_msg);
		else
		  errmsg = "Could not write ACL";
		goto done;
	  }

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("%s was created %s\n",
			   path, do_enable ? "(enabled)" : "(disabled)");
		emit_html_trailer(stdout);
	  }
	  else
		emit_http_header_status(stdout, "201", "Created");
	}
	else {
	  /* This is an operation on an existing ACL. */
	  log_msg((LOG_TRACE_LEVEL, "Op on existing ACL: \"%s\"",
			   acls[i]->aclname));

	  if (acls[i]->status == ACL_STATUS_ENABLED) {
		name = acl_build_name(rparts, ACL_STATUS_ENABLED);
		is_enabled = 1;
	  }
	  else if (acls[i]->status == ACL_STATUS_DISABLED) {
		name = acl_build_name(rparts, ACL_STATUS_DISABLED);
		is_enabled = 0;
	  }
	  else {
		errmsg = "Unknown ACL status";
		goto done;
	  }

	  if (args->rules_only)
		rules_only = 1;
	  if (args->services_only)
		services_only = 1;
	  if (args->enabled)
		enable_only = 1;
	  if (args->disabled)
		disable_only = 1;

	  if ((rules_only + services_only + enable_only + disable_only) > 1) {
		errmsg = "ACL operation failed, incompatible arguments";
		goto done;
	  }

	  if (enable_only && is_enabled)
		;
	  else if (disable_only && !is_enabled)
		;
	  else if (enable_only) {
		if (acl_enable(h, path) == -1) {
		  errmsg = "ACL enable failed";
		  goto done;
		}
	  }
	  else if (disable_only) {
		if (acl_disable(h, path) == -1) {
		  errmsg = "ACL disable failed";
		  goto done;
		}
	  }
	  else if (services_only || rules_only) {
		Ds *ds;

		if (parse_xml_acl(buf, &acl) == -1) {
		  errmsg = "ACL parse error";
		  goto done;
		}
		if ((p = args->acl) == NULL) {
		  Entity_body *b;

		  if ((b = cgiparse_get_entity_body()) != NULL)
			p = b->body;
		  else {
			errmsg = "No ACL argument found";
			goto done;
		  }
		}
		ds = ds_init(NULL);
		acl_start_xml_text(ds, acl);
		if (services_only) {
		  ds_asprintf(ds, "%s", p);
		  acl_rules_xml_text(ds, acl);
		}
		else {
		  acl_services_xml_text(ds, acl);
		  ds_asprintf(ds, "%s", p);
		}
		acl_end_xml_text(ds, acl);
		p = ds_buf(ds);
		if (parse_xml_acl(p, &acl) == -1) {
		  errmsg = "ACL parse error";
		  goto done;
		}
		log_msg((LOG_TRACE_LEVEL, "Updated ACL parses successfully"));
		if (vfs_put(h, name, p, strlen(p)) == -1) {
		  if (h->error_msg != NULL)
			errmsg = ds_xprintf("Could not write ACL: %s", h->error_msg);
		  else
			errmsg = "Could not write ACL";
		  goto done;
		}		
	  }
	  else {
		if ((p = args->acl) == NULL) {
		  Entity_body *b;

		  if ((b = cgiparse_get_entity_body()) != NULL)
			p = b->body;
		  else {
			errmsg = "No ACL argument found";
			goto done;
		  }
		}
		if (parse_xml_acl(p, &acl) == -1) {
		  errmsg = "ACL parse error";
		  goto done;
		}
		if (vfs_put(h, name, p, strlen(p)) == -1) {
		  if (h->error_msg != NULL)
			errmsg = ds_xprintf("Could not write ACL: %s", h->error_msg);
		  else
			errmsg = "Could not write ACL";
		  goto done;
		}
	  }
	}

	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  html_init(stdout,
				ds_xprintf("%s/acls", current_uri_script()));
	  rc = html_list_acls(res, &errmsg);
	  emit_html_trailer(stdout);
	}
	else
	  emit_http_header_status(stdout, "200", "OK");

	break;

  case HTTP_DELETE_METHOD:
	  for (i = 0; i < acl_count; i++) {
		if (streq(path, acls[i]->aclname))
		  break;
	  }

	  if (i == acl_count) {
		errmsg = "ACL not found";
		goto done;
	  }

	  if (acls[i]->status == ACL_STATUS_ENABLED)
		name = acl_build_name(rparts, ACL_STATUS_ENABLED);
	  else if (acls[i]->status == ACL_STATUS_DISABLED)
		name = acl_build_name(rparts, ACL_STATUS_DISABLED);
	  else {
		errmsg = "Unknown ACL status";
		goto done;
	  }

	  if (vfs_delete(h, name) == -1) {
		errmsg = "Delete failed";
		goto done;
	  }

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("%s was deleted\n", path);
		emit_html_trailer(stdout);
	  }
	  else
		emit_http_header_status(stdout, "200", "OK");

	  break;

  case HTTP_HEAD_METHOD:
	/* Does the ACL already exist? */
	for (i = 0; i < acl_count; i++) {
	  if (streq(path, acls[i]->aclname))
		break;
	}

	if (i == acl_count) {
	  log_msg((LOG_TRACE_LEVEL, "Not found: %s", path));
	  emit_http_header_status(stdout, "404", "Not found");
	}
	else {
	  int require_enabled, require_disabled;

	  if (acls[i]->status == ACL_STATUS_ENABLED)
		is_enabled = 1;
	  else if (acls[i]->status == ACL_STATUS_DISABLED)
		is_enabled = 0;
	  else {
		errmsg = "Unknown ACL status";
		goto done;
	  }

	  require_enabled = require_disabled = 0;
	  if (args->enabled)
		require_enabled = 1;
	  if (args->disabled)
		require_disabled = 1;
	  if (require_enabled && require_disabled) {
		errmsg = "ACL operation failed, incompatible arguments";
		goto done;
	  }

	  if ((require_enabled && is_enabled)
		  || (require_disabled && !is_enabled)
		  || (!require_enabled && !require_disabled)) {
		/* Is this supposed to set the size in the HTTP response-header? */
		emit_http_header_status(stdout, "200", "OK");
	  }
	  else {
		emit_http_header_status(stdout, "404", "Not found");
	  }
	}

	break;
#endif

  default:
	errmsg = "Unsupported method";
	goto done;
  }

  rc = 0;


 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (h != NULL)
	vfs_close(h);
  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

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

  dsv = (Dsvec *) names;
  dsvec_add_ptr(dsv, strdup(name));

  return(1);
}

static int
get_user_status(Vfs_handle *h, char *username)
{
  char *stored_digest;

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

  if (stored_digest == NULL || stored_digest[0] == '\0') {
	log_msg((LOG_ALERT_LEVEL, "No password stored for username '%s'?",
			 username));
	return(-1);
  }

  if (stored_digest[0] == '*')
	return(0);

  return(1);
}

static void
username_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
compar(const void *ap, const void *bp)
{
  char **a, **b;

  a = (char **) ap;
  b = (char **) bp;

  return(strcmp(*a, *b));
}

static int
get_users(Vfs_handle *h, char ***namev)
{
  int nusers;
  Dsvec *dsv;

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

  dsv = dsvec_init(NULL, sizeof(char *));
  h->list_sort = username_qsort;
  nusers = vfs_list(h, NULL, compar, list_add, (void ***) dsv);
  if (nusers == -1)
	return(-1);

  *namev = (char **) dsvec_base(dsv);

  return(nusers);
}

/*
 * Operations on DACS password/simple user accounts.
 */
static int
req_accounts(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int c, i, is_enabled, nusers, rc, st;
  int do_create, do_disable, do_enable;
  char *errmsg, **names, *p, *uri;
  char *digest_name, *password, *username;
  Dsvec *dsv, *roles;
  Passwd_digest_alg alg;
  Proc_lock *lock;
  Pw_state new_state;
  Vfs_handle *h;

  h = NULL;
  lock = NULL;
  rc = -1;
  errmsg = "Internal error";

  switch (method) {
  case HTTP_GET_METHOD:
	if (argv[1] == NULL) {
	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("<h1>DACS User Accounts at %s</h1>\n",
			   dacs_current_jurisdiction());

		printf("<p>\n");
		printf("<table>\n");
		c = 1;
		printf("%s", row_class("tr", c++));
		printf("%s", td_link(NULL, "Normal Accounts", ITEM_TYPE_PASSWDS));
		printf("%s", td_link(NULL, "(passwds)", ITEM_TYPE_PASSWDS));

		printf("%s", row_class("tr", c++));
		printf("%s", td_link(NULL, "Password-less Accounts", ITEM_TYPE_SIMPLE));
		printf("%s", td_link(NULL, "(simple)", ITEM_TYPE_SIMPLE));

		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}
	else if (argv[2] == NULL) {
	  /* List contents of item types "passwds" or "simple". */
	  if (!streq(argv[1], ITEM_TYPE_PASSWDS)
		  && !streq(argv[1], ITEM_TYPE_SIMPLE))
		return(-1);

	  if ((lock = proc_lock_create(PROC_LOCK_PASSWD)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Can't set lock"));
		return(-1);
	  }

	  if ((h = vfs_open_item_type(argv[1])) == NULL)
		goto fail;

	  if ((nusers = get_users(h, &names)) == -1)
		goto fail;

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		Pw_entry *pw;

		html_init(stdout, NULL);
		printf("<h1>DACS User Accounts at %s</h1>\n",
			   dacs_current_jurisdiction());

		c = 1;
		printf("<p>\n");
		printf("<table>\n");
		printf("%s\n", row_class("tr", c++));
		printf("<td class=\"res_heading\">Username</td>");
		printf("<td class=\"res_heading\">State</td>");
		printf("<td class=\"res_heading\">Digest Alg</td>");
		printf("<td class=\"res_heading\">Private Data</td>");
		printf("</tr>\n");

		for (i = 0; i < nusers; i++) {
		  printf("%s", row_class("tr", c++));
		  printf("<td class=\"users_name\">");
		  uri = build_uri(ds_xprintf("/%s", names[i]));
		  output_link(stdout, uri, names[i]);
		  printf("</td>");

		  if ((pw = pw_read_entry(h, names[i])) == NULL) {
			emit_html_trailer(stdout);
			goto fail;
		  }

		  printf("%s",
				 td_link("res_value", (pw->state == PW_ENABLED)
						 ? "enabled" : "disabled",
						 ds_xprintf("%s/state", names[i])));
		  printf("%s",
				 td_link("res_value", 
						 passwd_lookup_digest_name(pw->alg),
						 ds_xprintf("%s/digest", names[i])));

		  if (pw->private != NULL && ds_len(pw->private) > 0) {
			if (!strprintable(ds_buf(pw->private), 0, 1))
			  strba64(ds_ucbuf(pw->private), ds_len(pw->private), &p);
			else
			  p = ds_buf(pw->private);
			printf("%s", td_link("res_small_value", html_encode(p),
								 ds_xprintf("%s/private", names[i])));
		  }
		  else
			printf("<td class=\"res_small_value\">&nbsp;</td>");

		  printf("</tr>\n");
		}

		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}
	else {
	  int digest_only, private_only, state_only, username_only;
	  char *username;
	  Vfs_handle *h;

	  /* Request for a particular user. */

	  if (!streq(argv[1], ITEM_TYPE_PASSWDS)
		  && !streq(argv[1], ITEM_TYPE_SIMPLE))
		return(-1);

	  digest_only = private_only = state_only = username_only = 0;
	  if (argv[3] != NULL) {
		if (argv[4] != NULL)
		  return(-1);
		if (streq(argv[3], "username"))
		  username_only = 1;
		else if (streq(argv[3], "digest"))
		  digest_only = 1;
		else if (streq(argv[3], "private"))
		  private_only = 1;
		else if (streq(argv[3], "state"))
		  state_only = 1;
		else
		  return(-1);
	  }

	  if ((lock = proc_lock_create(PROC_LOCK_PASSWD)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Can't set lock"));
		return(-1);
	  }

	  if ((h = vfs_open_item_type(argv[1])) == NULL)
		goto fail;

	  username = argv[2];
	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		char *digest_name, *private_str, *state_str;
		Pw_entry *pw;

		html_init(stdout, NULL);
		if ((pw = pw_read_entry(h, username)) == NULL) {
		  emit_html_trailer(stdout);
		  goto fail;
		}

		digest_name = passwd_lookup_digest_name(pw->alg);
		if (pw->private != NULL && ds_len(pw->private) > 0) {
		  if (!strprintable(ds_buf(pw->private), 0, 1))
			strba64(ds_ucbuf(pw->private), ds_len(pw->private), &p);
		  else
			p = ds_buf(pw->private);
		  private_str = html_encode(p);
		}
		else
		  private_str = NULL;
		state_str = (pw->state == PW_ENABLED) ? "enabled" : "disabled";

		/* Print one component of the entry? */
		if (username_only) {
		  printf("%s\n", username);
		  break;
		}
		if (digest_only) {
		  printf("%s\n", digest_name);
		  break;
		}
		if (private_only) {
		  printf("%s\n", non_null(private_str));
		  break;
		}
		if (state_only) {
		  printf("%s\n", state_str);
		  break;
		}

		/* Print the entire entry. */
		printf("<h1>DACS User \"%s\" at %s</h1>\n",
			   username, dacs_current_jurisdiction());

		printf("<p>\n");
		printf("<table>\n");

		c = 1;
		printf("%s", row_class("tr", c++));
		printf("%s", td_link("users_name", username, "username"));
		printf("%s", td_link("users_state", state_str, "state"));
		printf("%s", td_link("users_digest", digest_name, "digest"));
		if (private_str != NULL)
		  printf("%s", td_link("res_small_value", private_str, "private"));
		else
		  printf("<td class=\"res_small_value\">&nbsp;</td>");

		printf("</tr>\n");
		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}
	break;

  case HTTP_DELETE_METHOD:
	/* Delete a particular user's "account". */
#ifdef NOTDEF
	if (*path == '\0') {
	  errmsg = "Invalid request, username is required";
	  goto done;
	}

	username = path;

	if (!is_valid_auth_username(username)) {
	  errmsg = ds_xprintf("Invalid request, username is invalid: %s",
						  username);
	  goto done;
	}

	for (i = 0; i < n; i++) {
	  if (streq(username, names[i]))
		break;
	}
	if (i == n) {
	  errmsg = "Username not found";
	  goto done;
	}

	/* XXX Might also delete any roles now. */
	if (vfs_delete(h, username) == -1) {
	  errmsg = "Delete failed";
	  goto done;
	}

	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  html_init(stdout, NULL);
	  printf("%s was deleted\n", username);
	  emit_html_trailer(stdout);
	}
	else
	  emit_http_header_status(stdout, "200", "OK");
#endif
	break;

  case HTTP_HEAD_METHOD:
#ifdef NOTDEF
	/*
	 * Test for the existence of a particular user's "account".
	 * Never return a message-body.
	 */
	username = path;

	for (i = 0; i < n; i++) {
	  if (streq(username, names[i]))
		break;
	}

	if (i == n)
	  emit_http_header_status(stdout, "404", "Not found");
	else {
	  int enabled_only, disabled_only;

	  if (!is_valid_auth_username(username)) {
		errmsg = ds_xprintf("Invalid request, username is invalid: %s",
							username);
		goto done;
	  }

	  if ((st = get_user_status(h, username)) == 1)
		is_enabled = 1;
	  else if (st == 0)
		is_enabled = 0;
	  else {
		errmsg = "Unknown user status";
		goto done;
	  }

	  enabled_only = disabled_only = 0;
	  if (args->enabled)
		enabled_only = 1;
	  else if (args->disabled)
		disabled_only = 1;
	  if ((!enabled_only && !disabled_only)
		  || (enabled_only && is_enabled) || (disabled_only && !is_enabled)) {
		if (test_emit_format(EMIT_FORMAT_HTML))
		  emit_http_header_status(stdout, "200", "OK");
		else {
		  /* Is this supposed to set the size in the HTTP response-header? */
		  emit_http_header_status(stdout, "200", "OK");
		}
	  }
	  else {
		if (test_emit_format(EMIT_FORMAT_HTML))
		  emit_http_header_status(stdout, "404", "Not found");
		else
		  emit_http_header_status(stdout, "404", "Not found");
	  }
	}
#endif
	break;

  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
#ifdef NOTDEF
	/* Create or update a user account. */
	if (*path == '\0') {
	  errmsg = "Invalid request, username is missing";
	  goto done;
	}

	username = path;

	if (!is_valid_auth_username(username)) {
	  errmsg = ds_xprintf("Invalid request, username is invalid: %s",
						  username);
	  goto done;
	}

	for (i = 0; i < n; i++) {
	  if (streq(username, names[i]))
		break;
	}

	if (i == n)
	  do_create = 1;
	else {
	  if ((st = get_user_status(h, path)) == -1) {
		errmsg = "Cannot get user status";
		goto done;
	  }
	  is_enabled = (st == 1);
	  do_create = 0;
	}

	do_enable = do_disable = 0;
	if (args->enabled)
	  do_enable = 1;
	else if (args->disabled)
	  do_disable = 1;
	if (do_enable && do_disable) {
	  errmsg = "Both \"enabled\" and \"disabled\" arguments are present";
	  goto done;
	}

	if ((password = args->password) == NULL) {
	  Entity_body *b;

	  if ((b = cgiparse_get_entity_body()) != NULL)
		password = b->body;
	}
	if (password != NULL) {
	  if (!pw_is_passwd_acceptable(password,
								   conf_val(CONF_PASSWORD_CONSTRAINTS))) {
		errmsg = "Password is unacceptable";
		goto done;
	  }
	} 
	else if (do_create) {
	  errmsg = "The PASSWORD argument is required";
	  goto done;
	}
	else if (!do_enable && !do_disable) {
	  errmsg = ds_xprintf("Invalid request for username '%s'", username);
	  goto done;
	}

	digest_name = NULL;
	if (passwd_get_digest_algorithm(&digest_name, &alg) == -1) {
	  errmsg = "Digest algorithm configuration error";
	  goto done;
	}

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

	if (do_create) {
	  /* Create a new user entry.  The default is to enable it. */
	  if (pw_add_entry(h, username, password, alg, new_state, NULL) == -1) {
		errmsg = "Error adding new user entry";
		goto done;
	  }
	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("User \"%s\" was created\n", username);
		emit_html_trailer(stdout);
	  }
	  else
		emit_http_header_status(stdout, "201", "Created");
	}
	else {
	  /* Update an existing user entry. */
	  if (password != NULL) {
		/* Set a new password and optionally the status. */
		if (pw_replace_entry(h, username, password, alg, new_state, PW_OP_NONE,
							 NULL) == -1) {
		  errmsg = "Error updating existing user entry";
		  goto done;
		}
	  }
	  else {
		/* Merely set the status. */
		if (do_enable)
		  rc = pw_disable_entry(h, username);
		else
		  rc = pw_enable_entry(h, username);
	  }

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("User \"%s\" was updated\n", username);
		emit_html_trailer(stdout);
	  }
	  else
		emit_http_header_status(stdout, "200", "OK");
	}
	break;
#endif

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);

 fail:
  if (h != NULL)
	vfs_close(h);
  if (lock != NULL)
	proc_lock_unset(lock);

  return(-1);
}

static void
emit_conf_table(FILE *fp, Dsvec *key_names, Kwv *kwv)
{
  int c, i, row;
  char *key, *uri;
  Kwv_iter *iter;
  Kwv_pair *pair;

  fprintf(fp, "<table class=\"conf_table\">\n");
  row = 1;
  c = 1;
  iter = kwv_iter_begin(kwv, "EVAL");
  while ((pair = kwv_iter_next(iter)) != NULL) {
	fprintf(fp, "%s\n", row_class("tr", row++));
	fprintf(fp, "<td class=\"conf_directive_name\">\n");
	uri = build_uri(ds_xprintf("/%s/%d", pair->name, c++));
	output_link(fp, uri, pair->name);
	fprintf(fp, "</td>\n");

	fprintf(fp, "<td class=\"conf_directive_value\">\n");
	fprintf(fp, "\"%s\"", html_encode(strquote(pair->val, "\"")));
	fprintf(fp, "</td>\n");

	fprintf(fp, "</tr>\n");
  }
  kwv_iter_end(iter);

  
  for (i = 0; i < dsvec_len(key_names); i++) {
	key = (char *) dsvec_ptr_index(key_names, i);
	if (streq(key, "EVAL"))
	  continue;

	iter = kwv_iter_begin(kwv, key);
	c = 1;
	while ((pair = kwv_iter_next(iter)) != NULL) {
	  fprintf(fp, "%s\n", row_class("tr", row++));
	  fprintf(fp, "<td class=\"conf_directive_name\">\n");
	  uri = build_uri(ds_xprintf("/%s/%d", pair->name, c++));
	  output_link(fp, uri, pair->name);
	  fprintf(fp, "</td>\n");

	  fprintf(fp, "<td class=\"conf_directive_value\">\n");
	  fprintf(fp, "\"%s\"", html_encode(strquote(pair->val, "\"")));
	  fprintf(fp, "</td>\n");

	  fprintf(fp, "</tr>\n");
	}
	kwv_iter_end(iter);
  }
  fprintf(fp, "</table>\n");
}

static int
req_conf_default(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *buf, *errmsg, *it, *p;
  Dsvec *sorted_conf_directives;
  Kwv_pair *pair, *v;
  Kwv_vartab *id, *tab, *vt;

  rc = -1;
  errmsg = NULL;

  if (dacs_conf == NULL) {
	errmsg = "No configuration file?!";
	goto done;
  }

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;
		Dsvec *keys;

		/* List all directives. */
		html_init(stdout, NULL);
		printf("<h1>Default Section Configuration at %s</h1>\n",
			   dacs_current_jurisdiction());

#ifdef NOTDEF
		if (dacs_conf->default_kwv == NULL) {
		  emit_html_trailer(stdout);
		  return(0);
		}

		keys = kwv_keys(dacs_conf->default_kwv);
		dsvec_sort(keys, compar);

		emit_conf_table(stdout, keys, dacs_conf->default_kwv);
#else
		printf("Sorry, the Default section is not available.\n");
#endif

		emit_html_trailer(stdout);
	  }
	  else {
		html_init(stdout, NULL);

		if (argv[2] == NULL) {
		  /* Show or list a specific directive. */
		}
		else if (argv[3] == NULL) {
		  printf("<h1>Default Section Configuration Value for %s at %s</h1>\n",
				 argv[2], dacs_current_jurisdiction());
		}
		else
		  return(-1);

		emit_html_trailer(stdout);
	  }
	}

	break;

  case HTTP_DELETE_METHOD:
  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
	/*NOTREACHED*/
  }

  rc = 0;

 done:
  if (rc == -1) {
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	init_common_status(&common_status, "dacs_admin", 0, errmsg);
  }

  return(rc);
}

static int
req_conf_jurisdiction(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *buf, *errmsg, *it, *p;
  Dsvec *sorted_conf_directives;
  Kwv_pair *pair, *v;
  Kwv_vartab *id, *tab, *vt;

  rc = -1;
  errmsg = NULL;

  if (dacs_conf == NULL) {
	errmsg = "No configuration file?!";
	goto done;
  }

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;
		Dsvec *keys;

		html_init(stdout, NULL);
		printf("<h1>Jurisdiction Section Configuration at %s</h1>\n",
			   dacs_current_jurisdiction());

#ifdef NOTDEF
		if (dacs_conf->jurisdiction_kwv == NULL) {
		  emit_html_trailer(stdout);
		  return(0);
		}

		keys = kwv_keys(dacs_conf->jurisdiction_kwv);
		dsvec_sort(keys, compar);

		emit_conf_table(stdout, keys, dacs_conf->jurisdiction_kwv);
#else
		printf("Sorry, the Jurisdiction section is not available.\n");
#endif

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] != NULL)
		return(-1);
	  else {
		html_init(stdout, NULL);
		printf("<h1>Jurisdiction Section Configuration Value for %s at %s</h1>\n",
			   argv[2], dacs_current_jurisdiction());

		emit_html_trailer(stdout);
	  }
	}

	break;

  case HTTP_DELETE_METHOD:
  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
	/*NOTREACHED*/
  }

  rc = 0;

 done:
  if (rc == -1) {
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	init_common_status(&common_status, "dacs_admin", 0, errmsg);
  }

  return(rc);
}

static int
req_conf_site(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *buf, *errmsg, *it, *p;
  Dsvec *sorted_conf_directives;
  Kwv_pair *pair, *v;
  Kwv_vartab *id, *tab, *vt;

  rc = -1;
  errmsg = NULL;

  if (dacs_conf == NULL) {
	errmsg = "No configuration file?!";
	goto done;
  }

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		int c;
		char *uri;
		Dsvec *keys;
		Kwv_iter *iter;
		Kwv_pair *pair, *v;

		html_init(stdout, NULL);
		printf("<h1>Site Section Configuration at %s</h1>\n",
			   dacs_current_jurisdiction());

#ifdef NOTDEF
		if (dacs_conf->site == NULL || dacs_conf->site->kwv_site == NULL) {
		  emit_html_trailer(stdout);
		  return(0);
		}

		keys = kwv_keys(dacs_conf->site->kwv_site);
		dsvec_sort(keys, compar);

		emit_conf_table(stdout, keys, dacs_conf->site->kwv_site);
#else
		printf("Sorry, the Site section is not available.\n");
#endif

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] != NULL)
		return(-1);
	  else {
		html_init(stdout, NULL);
		printf("<h1>Site Section Configuration Value for %s at %s</h1>\n",
			   argv[2], dacs_current_jurisdiction());

		emit_html_trailer(stdout);
	  }
	}

	break;

  case HTTP_DELETE_METHOD:
  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
	/*NOTREACHED*/
  }

  rc = 0;

 done:
  if (rc == -1) {
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	init_common_status(&common_status, "dacs_admin", 0, errmsg);
  }

  return(rc);
}

enum {
  AUTH_CLAUSE     = 0,
  ROLES_CLAUSE    = 1,
  TRANSFER_CLAUSE = 2
};

/*
 * Emit a partial (ROWP != NULL) or complete (ROWP == NULL) table
 * of directives from the clause of type CLAUSE and id ID_STR.
 * If DIRECTIVE is NULL, show all directives, otherwise only DIRECTIVE.
 * If UINT is non-NULL, it identifies an occurrence of DIRECTIVE in the
 * sorted order.
 */
static int
show_vartab(FILE *fp, char *clause, char *id_str, char *directive,
			char *uint, int *rowp)
{
  int c, emitted_title, end_table, i, *row, which_clause;
  unsigned int ui;
  char *id_key, *link_el, *uri;
  Kwv_pair *pair;
  Kwv_vartab *id, *tab, *vt;

  if (streq(clause, "transfer")) {
	which_clause = TRANSFER_CLAUSE;
	id_key = "TRANSFER_ID";
	link_el = "Transfer";
  }
  else if (streq(clause, "auth")) {
	which_clause = AUTH_CLAUSE;
	id_key = "AUTH_ID";
	link_el = "Auth";
  }
  else if (streq(clause, "roles")) {
	which_clause = ROLES_CLAUSE;
	id_key = "ROLES_ID";
	link_el = "Roles";
  }
  else
	return(-1);

  if (directive != NULL) {
	if (uint != NULL) {
	  if (strnum(uint, STRNUM_UI, &ui) == -1 || ui == 0)
		return(-1);
	}
	else
	  ui = 1;
  }
  else
	ui = 0;

  end_table = 0;
  if (rowp == NULL) {
	if (directive == NULL || ui == 0) {
	  fprintf(fp, "<h1>Configuration of %s Clause \"%s\" at %s</h1>\n",
			  link_el, id_str, dacs_current_jurisdiction());
	  fprintf(fp, "<table class=\"conf_table\">\n");
	  end_table = 1;
	}
	c = 1;
	row = &c;
  }
  else
	row = rowp;

  emitted_title = 0;
  i = 0;
  while (1) {
	switch (which_clause) {
	case TRANSFER_CLAUSE:
	  tab = conf_transfer_vartab(i++);
	  break;
	case AUTH_CLAUSE:
	  tab = conf_auth_vartab(i++);
	  break;
	case ROLES_CLAUSE:
	  tab = conf_roles_vartab(i++);
	  break;
	default:
	  return(-1);
	  /*NOTREACHED*/
	}

	if (tab == NULL)
	  break;

	if ((id = kwv_vartab_lookup(tab, id_key)) == NULL)
	  return(-1);

	if (id_str != NULL && !streq(id->pair->val, id_str))
	  continue;

	for (vt = tab; vt->name != NULL; vt++) {
	  int cnt, j;

	  if (vt->pair == NULL)
		continue;

	  cnt = 1;
	  /* Count the occurrences of this directive. */
	  for (pair = vt->pair; pair != NULL; pair = pair->next) {
		if (directive != NULL && streq(pair->name, directive) && ui == cnt) {
		  fprintf(fp, "\"%s\"\n", html_encode(strquote(pair->val, "\"")));
		  return(0);
		}
		cnt++;
	  }

	  if (!emitted_title && ui == 0) {
		fprintf(fp, "%s\n", row_class("tr", (*row)++));
		fprintf(fp, "<td class=\"res_name\" colspan=\"2\">\n");
		if (rowp == NULL)
		  fprintf(fp, "%s id=\"%s\"", link_el, id->pair->val);
		else {
		  uri = build_uri(ds_xprintf("/%s/%s", link_el,
									 id->pair->val));
		  fprintf(fp, "<a href=\"%s\">%s id=\"%s\"</a>",
				  uri, link_el, id->pair->val);
		}

		fprintf(fp, "</td>\n");
		fprintf(fp, "</tr>\n");
		emitted_title = 1;
	  }

	  j = 1;
	  for (pair = vt->pair; pair != NULL; pair = pair->next) {
		if (streq(pair->name, id_key))
		  continue;

		if (directive != NULL && !streq(pair->name, directive))
		  continue;

		fprintf(fp, "%s\n", row_class("tr", (*row)++));
		fprintf(fp, "<td class=\"conf_directive_name2\">\n");
		if (rowp != NULL) {
		  if (cnt > 1)
			uri = build_uri(ds_xprintf("/%s/%s/%s/%u",
									   link_el, id->pair->val, pair->name,
									   j++));
		  else
			uri = build_uri(ds_xprintf("/%s/%s/%s",
									   link_el, id->pair->val, pair->name));
		}
		else if (cnt > 1)
		  uri = build_uri(ds_xprintf("/%s/%u", pair->name, j++));
		else
		  uri = build_uri(ds_xprintf("/%s", pair->name));
		output_link(stdout, uri, pair->name);
		fprintf(fp, "</td>\n");

		fprintf(fp, "<td class=\"conf_directive_value\">\n");
		fprintf(fp, "\"%s\"", html_encode(strquote(pair->val, "\"")));
		fprintf(fp, "</td>\n");

		fprintf(fp, "</tr>\n");
	  }
	}
	emitted_title = 0;
  }

  if (end_table)
	printf("</table>\n");

  return(0);
}

static int
req_conf_current(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc, row;
  unsigned int c;
  char *buf, *errmsg, *it, *p;
  Dsvec *sorted_conf_directives;
  Kwv_pair *pair, *next_pair, *v;
  Kwv_vartab *id, *tab, *vt;

  rc = -1;
  errmsg = NULL;

  if (dacs_conf == NULL) {
	errmsg = "No configuration file?!";
	goto done;
  }

  switch (method) {
  case HTTP_GET_METHOD:
	sorted_conf_directives = conf_sort_directives(dacs_conf->conf_vartab);

	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;

		/* List current configuration directives and clauses. */
		html_init(stdout, NULL);
		printf("<h1>Current configuration at %s</h1>\n",
			   dacs_current_jurisdiction());

		row = 1;
		printf("<table class=\"conf_table\">\n");
		printf("%s\n", row_class("tr", row++));
		printf("<td class=\"conf_directive_name\">");
		printf("Jurisdiction uri</td>");
		printf("<td class=\"conf_directive_value\">");
		printf("\"%s\"</td></tr>\n",
			   html_encode(xml_attribute_value_encode(dacs_conf->uri, '\"')));
		if (dacs_conf->uri_expr != NULL) {
		  printf("%s\n", row_class("tr", row++));
		  printf("<td class=\"conf_directive_name\">");
		  printf("Jurisdiction expr</td>");
		  printf("<td class=\"conf_directive_value\">");
		  printf("%s</td></tr>\n",
				 html_encode(xml_attribute_value_encode(dacs_conf->uri_expr,
														'\"')));
		}

		c = 0;
		for (i = 0; i < dsvec_len(sorted_conf_directives); i++) {
		  printf("%s\n", row_class("tr", row++));
		  printf("<td class=\"conf_directive_name\">\n");

		  pair = (Kwv_pair *) dsvec_ptr_index(sorted_conf_directives, i);
		  next_pair = (Kwv_pair *) dsvec_ptr_index(sorted_conf_directives,
												   i + 1);
		  if (next_pair != NULL) {
			if (streq(pair->name, next_pair->name)) {
			  uri = build_uri(ds_xprintf("/%s/%d", pair->name, ++c));
			}
			else if (c) {
			  uri = build_uri(ds_xprintf("/%s/%d", pair->name, ++c));
			  c = 0;
			}
			else
			  uri = build_uri(ds_xprintf("/%s", pair->name));
		  }
		  else {
			if (c) {
			  uri = build_uri(ds_xprintf("/%s/%d", pair->name, ++c));
			  c = 0;
			}
			else
			  uri = build_uri(ds_xprintf("/%s", pair->name));
		  }

		  output_link(stdout, uri, pair->name);
		  printf("</td>\n");

		  printf("<td class=\"conf_directive_value\">\n");
		  printf("\"%s\"", html_encode(strquote(pair->val, "\"")));
		  printf("</td>\n");

		  printf("</tr>\n");
		}

		show_vartab(stdout, "auth", NULL, NULL, NULL, &row);
		show_vartab(stdout, "roles", NULL, NULL, NULL, &row);
		show_vartab(stdout, "transfer", NULL, NULL, NULL, &row);

		printf("</table>\n");

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] == NULL) {
		/*
		 * Display a given directive, which may be multivalued, or clause.
		 * Case 1:  <directive>
		 *     - show or list all occurrences of <directive>
		 * Case 2:  {Auth,Roles,Transfer}
		 *     - show clauses of the given type
		 * Anything else is an error.
		 */

		html_init(stdout, NULL);

		sorted_conf_directives = conf_sort_directives(dacs_conf->conf_vartab);

		if (streq(argv[1], "Auth")) {
		  printf("<h1>Configuration of Auth Clauses at %s</h1>\n",
				 dacs_current_jurisdiction());
		  show_vartab(stdout, "auth", NULL, NULL, NULL, NULL);
		}
		else if (streq(argv[1], "Roles")) {
		  printf("<h1>Configuration of Roles Clauses at %s</h1>\n",
				 dacs_current_jurisdiction());
		  show_vartab(stdout, "roles", NULL, NULL, NULL, NULL);
		}
		else if (streq(argv[1], "Transfer")) {
		  printf("<h1>Configuration of Transfer Clauses at %s</h1>\n",
				 dacs_current_jurisdiction());
		  show_vartab(stdout, "transfer", NULL, NULL, NULL, NULL);
		}
		else {
		  int n;
		  Kwv_pair *found;

		  /* Is the directive multivalued? */
		  n = 0;
		  for (i = 0; i < dsvec_len(sorted_conf_directives); i++) {
			pair = (Kwv_pair *) dsvec_ptr_index(sorted_conf_directives, i);
			if (streq(pair->name, argv[1])) {
			  found = pair;
			  n++;
			}
		  }

		  /*
		   * If there's only one, display its value; otherwise list all
		   * occurrences of the directive.
		   */
		  if (n == 1)
			printf("\"%s\"\n", html_encode(strquote(found->val, "\"")));
		  else {
			printf("<h1>Configuration of %s Directive at %s</h1>\n",
				   argv[1], dacs_current_jurisdiction());

			row = 1;
			printf("<p>\n");
			printf("<table class=\"conf_table\">\n");
			for (i = 0; i < dsvec_len(sorted_conf_directives); i++) {
			  pair = (Kwv_pair *) dsvec_ptr_index(sorted_conf_directives, i);
			  if (streq(pair->name, argv[1])) {
				printf("%s\n", row_class("tr", row));
				printf("%s", td_link("conf_directive_name", pair->name,
									 ds_xprintf("%d", row++)));
				printf("</td>\n");

				printf("<td class=\"conf_directive_value\">\n");
				printf("\"%s\"", html_encode(strquote(pair->val, "\"")));
				printf("</td>\n");

				printf("</tr>\n");
			  }
			}
			printf("</table>\n");
			printf("</p>\n");
		  }
		}

		emit_html_trailer(stdout);
	  }
	  else {
		unsigned int c, ui;

		/*
		 * Show an occurrence of a directive, show a particular clause,
		 * or show/list a directive of a particular clause:
		 *
		 * Case 1:  <directive>/<uint>
		 *     - occurrence <uint> of <directive>
		 * Case 2:  {Auth,Roles,Transfer}/<id>
		 *     - the given clause type with id=<id>
		 * Case 3:  {Auth,Roles,Transfer}/<id>/<directive>
		 *     - the value of <directive> or list of occurrences in the given
		 *       clause type with id=<id>
		 * Case 4:  {Auth,Roles,Transfer}/<id>/<directive>/<uint>
		 *     - occurrence <uint> of <directive> in the given clause type with
		 *       id=<id>
		 * Anything else is an error.
		 */
		html_init(stdout, NULL);
		
		row = 1;
		if (streq(argv[1], "Auth"))
		  show_vartab(stdout, "auth", argv[2], argv[3], argv[4], NULL);
		else if (streq(argv[1], "Roles"))
		  show_vartab(stdout, "roles", argv[2], argv[3], argv[4], NULL);
		else if (streq(argv[1], "Transfer"))
		  show_vartab(stdout, "transfer", argv[2], argv[3], argv[4], NULL);
		else { 
		  if (strnum(argv[2], STRNUM_UI, &ui) != -1 && ui != 0) {
			/* Show an occurrence of a directive. */
			c = 0;
			for (i = 0; i < dsvec_len(sorted_conf_directives); i++) {
			  pair = (Kwv_pair *) dsvec_ptr_index(sorted_conf_directives, i);
			  if (streq(pair->name, argv[1])) {
				if (++c == ui)
				  break;
			  }
			}

			if (c == ui)
			  printf("\"%s\"", html_encode(strquote(pair->val, "\"")));
			else
			  return(-1);
		  }
		  else
			return(-1);
		}

		emit_html_trailer(stdout);
	  }
	}

	break;

  case HTTP_DELETE_METHOD:
  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
	/*NOTREACHED*/
  }

  rc = 0;

 done:
  if (rc == -1) {
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	init_common_status(&common_status, "dacs_admin", 0, errmsg);
  }

  return(rc);
}

typedef enum {
  CONF_SITE           = 0,
  CONF_DEFAULT        = 1,
  CONF_JURISDICTION   = 2,
  CONF_CURRENT        = 3,
  CONF_INVALID        = -1
} Conf_res_id;

typedef struct Conf_res Conf_res;
struct Conf_res {
  char *name;
  char *label;
  Conf_res_id id;
  int (*req)(Root_res *res, Http_method method, char **argv, Kwv *kwv);
};

static Conf_res conf_res[] = {
  { "current",      "Directives currently in effect (evaluated)",
	CONF_CURRENT, req_conf_current },
  { "default",      "Default section currently in effect (unevaluated)",
	CONF_DEFAULT,      req_conf_default },
  { "jurisdiction", "Jurisdiction section currently in effect (unevaluated)",
	CONF_JURISDICTION, req_conf_jurisdiction },
  { "site",         "Site section currently in effect (unevaluated)",
	CONF_SITE,         req_conf_site },
  { NULL,           NULL,
	CONF_INVALID,      NULL }
};

static int
req_conf(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;

  rc = 0;
  if (argv[1] == NULL) {
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  html_init(stdout, NULL);
	  printf("<h1>Configuration Resources at %s</h1>\n",
			 dacs_current_jurisdiction());

	  printf("<p>\n");
	  printf("<table>\n");

	  for (i = 0; conf_res[i].name != NULL; i++) {
		printf("%s", row_class("tr", i + 1));
		printf("%s", td_link(NULL, conf_res[i].name, conf_res[i].name));
		printf("<td class=\"res_label\">%s</td>", conf_res[i].label);
		printf("</tr>\n");
	  }
	  printf("</table>\n");
	  printf("</p>\n");

	  emit_html_trailer(stdout);
	}
  }
  else {
	for (i = 0; conf_res[i].name != NULL; i++) {
	  if (streq(argv[1], conf_res[i].name)) {
		rc = conf_res[i].req(res, method, argv + 1, kwv);
		break;
	  }
	}

	if (conf_res[i].name == NULL)
	  rc = -1;

	return(rc);
  }

  return(rc);
}

enum {
  REVOCATION_LIST_MAX_LEN = 100
};

static int
req_revocations(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, nrevocations, rc;
  char *buf, *errmsg, *it, *p;
  Dsvec *revocations;
  Revocation *r;
  Vfs_handle *h;

  rc = -1;
  errmsg = "Internal error";

  switch (method) {
  case HTTP_GET_METHOD:
	if ((buf = get_revocations(ITEM_TYPE_REVOCATIONS)) == NULL) {
	  errmsg = ds_xprintf("Item type \"%s\" is unavailable",
						  ITEM_TYPE_REVOCATIONS);
	  goto done;
	}

	if ((revocations = parse_revocations(buf, 1)) == NULL) {
	  errmsg = "Error parsing revocations";
	  goto done;
	}
	nrevocations = dsvec_len(revocations);

	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;

		html_init(stdout, NULL);
		printf("<h1>Revocation List at %s</h1>\n",
			   dacs_current_jurisdiction());
		printf("<p>\n");
		printf("<table>\n");
		for (i = 0; i < nrevocations; i++) {
		  r = dsvec_ptr(revocations, i, Revocation *);

		  printf("%s\n", row_class("tr", i + 1));
		  if (r->type == AUTH_REVOKE_COMMENT)
			printf("<td class=\"revocation_comment\">");
		  else
			printf("<td class=\"revocation_expr\">");
		  uri = build_uri(ds_xprintf("/%d", i));
		  if (strlen(r->item) > REVOCATION_LIST_MAX_LEN)
			p = ds_xprintf("%s ...",
						   strndup(r->item, REVOCATION_LIST_MAX_LEN));
		  else
			p = r->item;
		  output_link(stdout, uri, html_encode(p));
		  printf("</td>");

		  printf("</tr>\n");
		}
		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] == NULL) {
		unsigned int ui;

		html_init(stdout, NULL);

		if (strnum(argv[1], STRNUM_UI, &ui) == -1)
		  return(-1);

		if ((r = dsvec_ptr(revocations, ui, Revocation *)) == NULL)
		  return(-1);

		printf("%s\n", html_encode(r->item));

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}

	break;

#ifdef NOTDEF
  case HTTP_HEAD_METHOD:
	/* Test for the existence of a revocation list. */
	if ((h = vfs_open_item_type(ITEM_TYPE_REVOCATIONS)) == NULL)
	  emit_http_header_status(stdout, "404", "Not found");
	else if (vfs_exists(h, NULL) == 1)
	  emit_http_header_status(stdout, "200", "OK");
	else
	  emit_http_header_status(stdout, "404", "Not found");
	break;

  case HTTP_DELETE_METHOD:
	if ((h = vfs_open_item_type(it)) == NULL)
	  emit_http_header_status(stdout, "404", "Not found");
	else {
	  /*
	   * At present, if "revocations" is configured, then check_revocation()
	   * insists that the file exist.  So if is deleted here, it will cause
	   * a problem requiring manual intervention because DACS will refuse
	   * to do anything! Instead of deleting it, just truncate the list.
	   */
#ifdef NOTDEF
	  if (vfs_delete(h, NULL) == -1) {
		errmsg = "Delete failed";
		goto done;
	  }
#else
	  if (vfs_put(h, NULL, NULL, 0) == -1) {
		errmsg = "Truncate failed";
		goto done;
	  }
#endif

	  if (test_emit_format(EMIT_FORMAT_HTML)) {
		html_init(stdout, NULL);
		printf("The revocations list was deleted\n");
		emit_html_trailer(stdout);
	  }
	  else
		emit_http_header_status(stdout, "200", "OK");
	}
	break;

  case HTTP_PUT_METHOD:
  case HTTP_POST_METHOD:
	/* Replace the entire revocation list with the argument. */
	if ((h = vfs_open_item_type(it)) == NULL) {
	  emit_http_header_status(stdout, "404", "Not found");
	  break;
	}
	if ((p = args->revocations) == NULL) {
	  errmsg = "No REVOCATIONS argument found";
	  goto done;
	}
	if (parse_revocations(strdup(p), 1) == NULL) {
	  errmsg = "Could not parse REVOCATIONS argument";
	  goto done;
	}
	if (vfs_put(h, NULL, p, strlen(p)) == -1) {
	  if (h->error_msg != NULL)
		errmsg = ds_xprintf("Could not write revocation list: %s",
							h->error_msg);
	  else
		errmsg = "Could not write new revocation list";
	  goto done;
	}

	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  html_init(stdout, NULL);
	  printf("Wrote new revocation list\n");
	  emit_html_trailer(stdout);
	}
	else
	  emit_http_header_status(stdout, "200", "OK");
	break;

#endif

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

static int
req_tokens(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int c, i, rc;
  char *buf, *errmsg, *it, *p, *uri;

  rc = 0;

  errmsg = NULL;
  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;
		Dsvec *dsv;

		/* List all cached access tokens. */
		html_init(stdout, NULL);
		printf("<h1>Access Token List at %s</h1>\n",
			   dacs_current_jurisdiction());

		if ((dsv = access_token_list()) == NULL) {
		  emit_html_trailer(stdout);
		  goto done;
		}

		printf("<p>\n");
		printf("<table>\n");
		for (i = 0; i < dsvec_len(dsv); i++) {
		  char *key, *val;
		  Kwv *kwv;

		  kwv = (Kwv *) dsvec_ptr_index(dsv, i);
		  key = kwv_lookup_value(kwv, "UNIQUE");

		  printf("%s\n", row_class("tr", i));
		  printf("<td class=\"token_resource\">");
		  uri = build_uri(ds_xprintf("/%s", key));
		  output_link(stdout, uri,
					  ds_xprintf("%s %s", kwv_lookup_value(kwv, "ISSUED_BY"),
								 kwv_lookup_value(kwv, "URL_PATH")));
		  printf("</td>");

		  printf("</tr>\n");
		}
		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] == NULL) {
		Kwv *kwv;
		Kwv_pair *pair;

		/* Show a particular access token. */
		if ((kwv = access_token_get(argv[1])) == NULL)
		  return(-1);

		html_init(stdout, NULL);
		printf("<h1>Access Token at %s</h1>\n",
			   dacs_current_jurisdiction());

		c = 1;
		printf("<p>\n");
		printf("<table>\n");
		printf("%s", row_class("tr", c++));
		printf("<td class=\"res_heading\">Issued By</td>");
		printf("<td class=\"res_heading\">URL Path</td>");
		printf("<td class=\"res_heading\">Identity</td>");
		printf("<td class=\"res_heading\">Expires</td>");
		printf("<td class=\"res_heading\">Limit</td>");
		printf("<td class=\"res_heading\">Constraint</td>");
		printf("<td class=\"res_heading\">Constraint Flags</td>");
		printf("</tr>\n");

		if ((pair = kwv_lookup(kwv, "ISSUED_BY")) != NULL)
		  printf("%s", td_link("token_resource", pair->val, "issued_by"));
		else
		  printf("<td>&nbsp;</td>");
		if ((pair = kwv_lookup(kwv, "URL_PATH")) != NULL)
		  printf("%s", td_link("token_resource", pair->val, "url_path"));
		else
		  printf("<td>&nbsp;</td>");
		if ((pair = kwv_lookup(kwv, "IDENTITY")) != NULL)
		  printf("%s", td_link("token_resource", pair->val, "identity"));
		else
		  printf("<td>&nbsp;</td>");
		if ((pair = kwv_lookup(kwv, "EXPIRES")) != NULL) {
		  char *s;
		  time_t expires;

		  if (strnum(pair->val, STRNUM_TIME_T, &expires) == -1)
			s = "??";
		  else
			s = make_short_local_date_string(localtime(&expires));
		  printf("%s", td_link("token_resource", s, "expires"));
		}
		else
		  printf("<td>&nbsp;</td>");
		if ((pair = kwv_lookup(kwv, "LIMIT")) != NULL)
		  printf("%s", td_link("token_resource", pair->val, "limit"));
		else
		  printf("<td>&nbsp;</td>");
		if ((pair = kwv_lookup(kwv, "ATTR_CONSTRAINT")) != NULL)
		  printf("%s", td_link("res_small_value", pair->val, "constraint"));
		else
		  printf("<td>&nbsp;</td>");

		printf("<td class=\"token_resource\">");
		c = 0;
		if ((pair = kwv_lookup(kwv, "ATTR_PERMIT_CHAINING")) != NULL) {
		  uri = build_uri(ds_xprintf("/permit_chaining"));
		  printf("<a href=\"%s\">PermitChaining</a>", uri);
		  c++;
		}
		if ((pair = kwv_lookup(kwv, "ATTR_PASS_CREDENTIALS")) != NULL) {
		  uri = build_uri(ds_xprintf("/pass_credentials"));
		  printf("%s<a href=\"%s\">PassCredentials</a>",
				 c ? ", " : "", uri);
		  c++;
		}
		if ((pair = kwv_lookup(kwv, "ATTR_PASS_HTTP_COOKIE")) != NULL) {
		  uri = build_uri(ds_xprintf("/pass_http_cookie"));
		  printf("%s<a href=\"%s\">PassHttpCookie</a>",
				 c ? ", " : "", uri);
		  c++;
		}
		if (!c)
		  printf("&nbsp;");
		printf("</td>");
		printf("</tr>\n");

		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else if (argv[3] == NULL) {
		Kwv *kwv;
		Kwv_pair *pair;

		/* Show a particular access token. */
		if ((kwv = access_token_get(argv[1])) == NULL)
		  return(-1);

		html_init(stdout, NULL);

		if (streq(argv[2], "issued_by")) {
		  if ((pair = kwv_lookup(kwv, "ISSUED_BY")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "url_path")) {
		  if ((pair = kwv_lookup(kwv, "URL_PATH")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "identity")) {
		  if ((pair = kwv_lookup(kwv, "IDENTITY")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "expires")) {
		  if ((pair = kwv_lookup(kwv, "EXPIRES")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "limit")) {
		  if ((pair = kwv_lookup(kwv, "LIMIT")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "constraint")) {
		  if ((pair = kwv_lookup(kwv, "ATTR_CONSTRAINT")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "permit_chaining")) {
		  if ((pair = kwv_lookup(kwv, "ATTR_PERMIT_CHAINING")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "pass_credentials")) {
		  if ((pair = kwv_lookup(kwv, "ATTR_PASS_CREDENTIALS")) != NULL)
			printf("%s", pair->val);
		}
		else if (streq(argv[2], "pass_http_cookie")) {
		  if ((pair = kwv_lookup(kwv, "ATTR_PASS_HTTP_COOKIE")) != NULL)
			printf("%s", pair->val);
		}
		else
		  return(-1);

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}

	break;

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

static int
req_groups(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc, row;
  char *errmsg, *p;
  Dsvec *dsv;
  Group_file *gf;

  rc = 0;

  errmsg = NULL;
  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *uri;

		if (dacs_list_groups(NULL, &dsv) == NULL)
		  return(-1);

		/* List all groups. */
		html_init(stdout, NULL);
		printf("<h1>Groups at %s</h1>\n", dacs_current_jurisdiction());

		row = 1;
		printf("<p>\n");
		printf("<table class=\"group_listing\">\n");
		printf("%s", row_class("tr", row++));
		printf("<td class=\"group_listing_heading1\">Defining Jurisdiction</td>");
		printf("<td class=\"group_listing_heading2\">Name</td>");
		printf("</tr>\n");

		for (i = 0; i < dsvec_len(dsv); i++) {
		  gf = (Group_file *) dsvec_ptr_index(dsv, i);
		  printf("%s", row_class("tr", row++));
		  p = gf->group_name.jurisdiction;
		  printf("%s", td_link(NULL, p, ds_xprintf("%s:", p)));
		  p = ds_xprintf("%s:%s",
						 gf->group_name.jurisdiction, gf->group_name.username);
		  printf("%s", td_link(NULL, gf->group_name.username, p));
		  printf("</tr>\n");
		}
		printf("</table>\n");
		printf("</p>\n");
	  }
	  else if (argv[2] != NULL)
		return(-1);
	  else {
		char *uri;
		DACS_name dacs_name;

		if (parse_dacs_name(argv[1], &dacs_name) == DACS_JURISDICTION_NAME) {
		  if (dacs_list_groups(dacs_name.jurisdiction, &dsv) == NULL)
			return(-1);

		  /* List all of a jurisdiction's groups. */
		  html_init(stdout, NULL);
		  printf("<h1>Groups Defined by Jurisdiction %s at %s</h1>\n",
				 dacs_name.jurisdiction, dacs_current_jurisdiction());

		  printf("<p>\n");
		  printf("<table>\n");
		  for (i = 0; i < dsvec_len(dsv); i++) {
			gf = (Group_file *) dsvec_ptr_index(dsv, i);
			printf("%s", row_class("tr", i + 1));
			printf("<td>");
			uri = build_uri_with_path(ds_xprintf("/groups/%s:%s",
												 gf->group_name.jurisdiction,
												 gf->group_name.username));
			output_link(stdout, uri, ds_xprintf("%s:%s",
												gf->group_name.jurisdiction,
												gf->group_name.username));
												
			printf("</td>\n");
			printf("</tr>\n");
		  }
		  printf("</table>\n");
		  printf("</p>\n");
		}
		else {
		  char *buf;
		  DACS_name dacs_name;

		  /* Display a group definition. */

		  p = ds_xprintf("%%%s", argv[1]);
		  if (parse_dacs_name(p, &dacs_name) == DACS_GROUP_NAME) {
			if (dacs_get_group_definition(dacs_name.jurisdiction,
										  dacs_name.username, &buf) == -1)
			  return(-1);

			html_init(stdout, NULL);
			printf("<p>\n");
			printf("<pre class=\"group_definition\">\n");
			printf("%s\n", html_encode(buf));
			printf("</pre>\n");
			printf("</p>\n");
		  }
		  else
			return(-1);
		}
	  }
	  emit_html_trailer(stdout);
	}

	break;

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

/*
 * XXX parsing log entries is complicated by the ability to specify
 * (and change) the format; will need to emit a distinguished log entry that
 * describes the entries that follow, until EOF or another distinguished entry
 */
static int
req_logs(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *errmsg, *p;
  Vfs_handle *h;

  rc = 0;

  errmsg = NULL;
  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		char *buf;

		html_init(stdout, NULL);
		printf("<h1>DACS Log File Entries at %s</h1>\n",
			   dacs_current_jurisdiction());

		if (dacs_logfile_path != NULL) {
		  if (load_file(dacs_logfile_path, &buf, NULL) == -1)
			return(-1);
		  printf("<p>\n");
		  printf("<pre class=\"log_listing\">\n");
		  printf("%s\n", buf);
		  printf("</pre>\n");
		  printf("</p>\n");
		}
		else
		  printf("No logging information is available.\n");

		emit_html_trailer(stdout);
	  }
	  else
		return(-1);
	}

	break;

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

/*
 * /activity -- list
 *   /activity/all      -- all events
 *   /activity/auth     -- only auth events
 *   /activity/signout  -- only signout events
 *   /activity/access   -- only access events
 *   /activity/{auth,signout,access}/<ident>  -- only events by <ident>
 *   /activity/{auth,signout,access}/<unique>  -- only events by <unique>
 */
static int
req_activity(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc, sort_by_date, sort_by_ident, sort_by_type, sort_by_uri;
  int row, sort_by_remote;
  int show_access, show_auth, show_signout;
  char *buf, *errmsg, *p, *show_jurisdiction, *show_unique, *show_username;
  DACS_name dacs_name;
  DACS_name_type nt;

  rc = 0;

  show_auth = show_signout = show_access = 0;
  sort_by_date = sort_by_ident = sort_by_type
	= sort_by_uri = sort_by_remote = 0;
  show_jurisdiction = show_unique = show_username = NULL;

  if (argv[1] != NULL) {
	if (streq(argv[1], "auth")) {
	  show_auth = 1;
	}
	else if (streq(argv[1], "signout")) {
	  show_signout = 1;
	}
	else if (streq(argv[1], "access")) {
	  show_access = 1;
	}
	else if (streq(argv[1], "all"))
	  show_auth = show_signout = show_access = 1;
	else
	  return(-1);

	if (argv[2] != NULL) {
	  if (argv[3] != NULL)
		return(-1);

	  if ((nt = parse_dacs_name(argv[2], &dacs_name)) == DACS_USER_NAME)
		show_username = argv[2];
	  else if (nt == DACS_JURISDICTION_NAME)
		show_jurisdiction = argv[2];
	  else
		show_unique = (char *) stra64b(argv[2], NULL, NULL);
	}
  }

  sort_by_date = 1;
  if ((p = kwv_lookup_value(kwv, "ORDER")) != NULL) {
	if (streq(p, "bydate"))
	  sort_by_date = 1;
	else if (streq(p, "byident"))
	  sort_by_ident = 1;
	else if (streq(p, "byuri")) {
	  sort_by_uri = 1;
	}
	else if (streq(p, "byremote")) {
	  sort_by_remote = 1;
	}
	else
	  return(-1);
  }

  errmsg = NULL;

  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  row = 1;

	  if (argv[1] == NULL) {
		html_init(stdout, NULL);
		printf("<h1>User Activity at %s</h1>\n", dacs_current_jurisdiction());
		printf("<p>\n");
		printf("<table>\n");
		printf("%s", row_class("tr", row++));
		printf("%s", td_link(NULL, "All events", "all"));
		printf("%s", row_class("tr", row++));
		printf("%s", td_link(NULL, "Authentication events", "auth"));
		printf("%s", row_class("tr", row++));
		printf("%s", td_link(NULL, "Signouts", "signout"));
		printf("%s", row_class("tr", row++));
		printf("%s", td_link(NULL, "Access Events", "access"));
		printf("</tr>\n");
		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
		goto done;
	  }

	  if (sort_by_date) {
		Dsvec *dsv;
		User_info *ui;

		/*
		 * By default, or "bydate", dump the records in the order in which
		 * they were written.
		 */
		html_init(stdout, NULL);
		printf("<h1>User Activity at %s</h1>\n", dacs_current_jurisdiction());
		if ((dsv = user_info_load(NULL)) == NULL)
		  return(-1);

		if (show_auth) {
		  int did_title = 0;

		  for (i = 0; i < dsvec_len(dsv); i++) {
			unsigned int as;
			char *s;
			time_t at, et;
			User_auth *ua;

			ui = (User_info *) dsvec_ptr_index(dsv, i);
			ua = &ui->info.auth;
			if (ui->which != USERINFO_AUTH)
			  continue;
			if (show_username != NULL
				&& !name_eq(show_username, ua->ident, DACS_NAME_CMP_CONFIG))
			  continue;
			if (show_unique != NULL && !streq(show_unique, ua->unique))
			  continue;
			if (show_jurisdiction != NULL) {
			  char *f;
			  DACS_name dn;

			  nt = parse_dacs_name(ua->ident, &dn);
			  if (nt != DACS_USER_NAME)
				return(-1);
			  if ((f = dacs_name.federation) == NULL)
				f = conf_val(CONF_FEDERATION_NAME);
			  if (!name_eq(f, dn.federation, DACS_NAME_CMP_CONFIG))
				continue;
			  if (!name_eq(dacs_name.jurisdiction, dn.jurisdiction,
						   DACS_NAME_CMP_CONFIG))
				continue;
			}

			if (!did_title) {
			  printf("<p>\n");
			  printf("<table class=\"admin_table\">\n");
			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\" colspan=\"9\">Authentication Events</td>");
			  printf("</tr>\n");

			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\">Ident</td>");
			  printf("<td class=\"admin_heading\">IP Addr</td>");
			  printf("<td class=\"admin_heading\">AuthTime</td>");
			  printf("<td class=\"admin_heading\">Expires</td>");
			  printf("<td class=\"admin_heading\">AuthStyle</td>");
			  printf("<td class=\"admin_heading\">ValidFor</td>");
			  printf("<td class=\"admin_heading\">Unique</td>");
			  printf("<td class=\"admin_heading\">ImportedBy</td>");
			  printf("</tr>\n");
			  did_title = 1;
			}

			printf("%s", row_class("tr", row++));
			if (show_username == NULL) {
			  char *uri;

			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
												   argv[1], ua->ident));
			  printf("<td>");
			  output_link(stdout, uri, ua->ident);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", ua->ident);

			printf("<td>%s</td>", ua->ip);
			s = "??";
			if (strnum(ua->auth_time, STRNUM_TIME_T, &at) != -1)
			  s = make_short_local_date_string(localtime(&at));
			printf("<td class=\"admin_heading_r\">%s</td>", s);
			s = "??";
			if (strnum(ua->expires, STRNUM_TIME_T, &et) != -1)
			  s = make_short_local_date_string(localtime(&et));
			printf("<td class=\"admin_heading_r\">%s</td>", s);
			s = "??";
			if (strnum(ua->auth_style, STRNUM_UI, &as) != -1)
			  s = auth_style_to_string(as);
			printf("<td>%s</td>", s);
			printf("<td>%s</td>", ua->valid_for);

			if (show_unique == NULL) {
			  char *uri;

			  strba64((unsigned char *) ua->unique, strlen(ua->unique) + 1, &s);
			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
												   argv[1], s));
			  printf("<td>");
			  output_link(stdout, uri, ua->unique);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", ua->unique);

			if (ua->imported_by != NULL)
			  printf("<td>%s</td>", ua->imported_by);
			else
			  printf("<td>&nbsp;</td>");
			printf("</tr>\n");
		  }

		  printf("</table>\n");
		  printf("</p>\n");
		}

		if (show_signout) {
		  int did_title = 0;

		  for (i = 0; i < dsvec_len(dsv); i++) {
			char *s;
			time_t et;
			User_signout *us;

			ui = (User_info *) dsvec_ptr_index(dsv, i);
			us = &ui->info.signout;
			if (ui->which != USERINFO_SIGNOUT)
			  continue;
			if (show_username != NULL
				&& !name_eq(show_username, us->ident, DACS_NAME_CMP_CONFIG))
			  continue;
			if (show_unique != NULL && !streq(show_unique, us->unique))
			  continue;
			if (show_jurisdiction != NULL) {
			  char *f;
			  DACS_name dn;

			  nt = parse_dacs_name(us->ident, &dn);
			  if (nt != DACS_USER_NAME)
				return(-1);
			  if ((f = dacs_name.federation) == NULL)
				f = conf_val(CONF_FEDERATION_NAME);
			  if (!name_eq(f, dn.federation, DACS_NAME_CMP_CONFIG))
				continue;
			  if (!name_eq(dacs_name.jurisdiction, dn.jurisdiction,
						   DACS_NAME_CMP_CONFIG))
				continue;
			}

			if (!did_title) {
			  printf("<p>\n");
			  printf("<table class=\"admin_table\">\n");
			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\" colspan=\"9\">Signout Events</td>");
			  printf("</tr>\n");

			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\">Ident</td>");
			  printf("<td class=\"admin_heading\">IP</td>");
			  printf("<td class=\"admin_heading\">Date</td>");
			  printf("<td class=\"admin_heading\">Unique</td>");
			  printf("</tr>\n");
			  did_title = 1;
			}

			printf("%s", row_class("tr", row++));
			if (show_username == NULL) {
			  char *uri;

			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
												   argv[1], us->ident));
			  printf("<td>");
			  output_link(stdout, uri, us->ident);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", us->ident);

			printf("<td>%s</td>", us->ip);
			s = "??";
			if (strnum(us->date, STRNUM_TIME_T, &et) != -1)
			  s = make_short_local_date_string(localtime(&et));
			printf("<td class=\"admin_heading_r\">%s</td>", s);
			if (show_unique == NULL) {
			  char *uri;

			  strba64((unsigned char *) us->unique, strlen(us->unique) + 1, &s);
			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
												   argv[1], s));
			  printf("<td>");
			  output_link(stdout, uri, us->unique);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", us->unique);
			printf("</tr>\n");
		  }
		  printf("</table>\n");
		  printf("</p>\n");
		}

		if (show_access) {
		  int did_title = 0;

		  for (i = 0; i < dsvec_len(dsv); i++) {
			char *s;
			time_t et;
			User_access *uacs;

			ui = (User_info *) dsvec_ptr_index(dsv, i);
			uacs = &ui->info.acs;
			if (ui->which != USERINFO_ACCESS)
			  continue;
			if (show_username != NULL
				&& !name_eq(show_username, uacs->ident, DACS_NAME_CMP_CONFIG))
			  continue;
			if (show_unique != NULL && !streq(show_unique, uacs->unique))
			  continue;
			if (show_jurisdiction != NULL) {
			  char *f;
			  DACS_name dn;

			  nt = parse_dacs_name(uacs->ident, &dn);
			  if (nt != DACS_USER_NAME)
				return(-1);
			  if ((f = dacs_name.federation) == NULL)
				f = conf_val(CONF_FEDERATION_NAME);
			  if (!name_eq(f, dn.federation, DACS_NAME_CMP_CONFIG))
				continue;
			  if (!name_eq(dacs_name.jurisdiction, dn.jurisdiction,
						   DACS_NAME_CMP_CONFIG))
				continue;
			}

			if (!did_title) {
			  printf("<p>\n");
			  printf("<table class=\"admin_table\">\n");
			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\" colspan=\"9\">Access Events</td>");
			  printf("</tr>\n");

			  printf("%s", row_class("tr", row++));
			  printf("<td class=\"admin_heading\">Ident</td>");
			  printf("<td class=\"admin_heading\">IP</td>");
			  printf("<td class=\"admin_heading\">Date</td>");
			  printf("<td class=\"admin_heading\">Host</td>");
			  printf("<td class=\"admin_heading\">Addr</td>");
			  printf("<td class=\"admin_heading\">Unique</td>");
			  printf("<td class=\"admin_heading\">URI</td>");
			  printf("</tr>\n");
			  did_title = 1;
			}

			printf("%s", row_class("tr", row++));
			if (show_username == NULL) {
			  char *uri;

			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
												   argv[1], uacs->ident));
			  printf("<td>");
			  output_link(stdout, uri, uacs->ident);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", uacs->ident);
			printf("<td>%s</td>", uacs->ip);
			s = "??";
			if (strnum(uacs->date, STRNUM_TIME_T, &et) != -1)
			  s = make_short_local_date_string(localtime(&et));
			printf("<td class=\"admin_heading_r\">%s</td>", s);
			if (uacs->host != NULL)
			  printf("<td>%s</td>", uacs->host);
			else
			  printf("<td>&nbsp;</td>");
			if (uacs->addr != NULL)
			  printf("<td>%s</td>", uacs->addr);
			else
			  printf("<td>&nbsp;</td>");

			if (show_unique == NULL) {
			  char *uri;

			  strba64((unsigned char *) uacs->unique, strlen(uacs->unique) + 1,
					  &s);
			  uri = build_uri_with_path(ds_xprintf("/activity/%s/%s",
													 argv[1], s));
			  printf("<td>");
			  output_link(stdout, uri, uacs->unique);
			  printf("</td>");
			}
			else
			  printf("<td>%s</td>", uacs->unique);
			printf("<td>%s</td>", url_decode(uacs->uri, NULL, NULL));
			printf("</tr>\n");
		  }
		  printf("</table>\n");
		  printf("</p>\n");
		}

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] != NULL)
		return(-1);
	  else {
		if (streq(argv[1], "bytype")) {
		  /* Sort and list all records by type. */
		}
		else if (streq(argv[1], "byname")) {
		  /* Sort and list all records by user identity. */
		}
		else if (streq(argv[1], "byuri")) {
		  /* Sort and list access records by resource name. */
		}
		else
		  return(-1);

		html_init(stdout, NULL);
		printf("<h1>User Activity at %s</h1>\n",
			   dacs_current_jurisdiction());

		printf("<p>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	}

	break;

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

/*
 * List or get version information.
 * This is always read-only.
 */
static int
req_version(Root_res *res, Http_method method, char **argv, Kwv *kwv)
{
  int i, rc;
  char *buf, *errmsg, *it, *p, *versions;
  Dsvec *dsv;

  rc = 0;
  versions = dacs_component_versions_string();
  dsv = keyword_scan(NULL);

  errmsg = NULL;
  switch (method) {
  case HTTP_GET_METHOD:
	if (test_emit_format(EMIT_FORMAT_HTML)) {
	  if (argv[1] == NULL) {
		int c;

		/* List all version info. */
		html_init(stdout, NULL);
		printf("<h1>Version Information for %s</h1>\n",
			   dacs_current_jurisdiction());

		printf("<p>\n");
		c = 1;
		printf("<table>\n");
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("number", DACS_VERSION_NUMBER, ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("release", DACS_VERSION_RELEASE, ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("date", DACS_VERSION_DATE, ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("revid", DACS_VERSION_REVID, ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("build_platform", dacs_build_os_string(), ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("exec_platform", dacs_runtime_os_string(), ""));
		printf("%s%s</tr>\n",
			   row_class("tr", c++),
			   td_name_value("other", versions, ""));

		if (dsv != NULL) {
		  for (i = 0; i < dsvec_len(dsv); i++) {
			p = (char *) dsvec_ptr_index(dsv, i);
			printf("%s%s</tr>\n",
				   row_class("tr", c++),
				   td_name_value("file_revid", p, ""));
		  }
		}

		printf("</table>\n");
		printf("</p>\n");

		emit_html_trailer(stdout);
	  }
	  else if (argv[2] == NULL) {
		int c;

		/* Display particular version info, or a list. */
		html_init(stdout, NULL);
		c = 1;
		if (streq(argv[1], "number"))
		  printf("%s\n", DACS_VERSION_NUMBER);
		else if (streq(argv[1], "release"))
		  printf("%s\n", DACS_VERSION_RELEASE);
		else if (streq(argv[1], "date"))
		  printf("%s\n", DACS_VERSION_DATE);
		else if (streq(argv[1], "revid"))
		  printf("%s\n", DACS_VERSION_REVID);
		else if (streq(argv[1], "build_platform"))
		  printf("%s\n", dacs_build_os_string());
		else if (streq(argv[1], "exec_platform"))
		  printf("%s\n", dacs_runtime_os_string());
		else if (streq(argv[1], "other"))
		  printf("%s\n", versions);
		else if (streq(argv[1], "file_revid")) {
		  if (dsv != NULL && dsvec_len(dsv) > 0) {
			printf("<h1>Version at %s</h1>\n",
				   dacs_current_jurisdiction());

			printf("<p>\n");
			printf("<table>\n");
			for (i = 0; i < dsvec_len(dsv); i++) {
			  p = (char *) dsvec_ptr_index(dsv, i);
			  printf("%s%s</tr>\n",
					 row_class("tr", c++),
					 td_name_value("file_revid", p, hash_str(p)));
			}
			printf("</p></table>\n");
		  }
		}
		else {
		  rc = -1;
		}

		emit_html_trailer(stdout);
	  }
	  else {
		int c;

		/* Display a particular piece of version info. */
		if (!streq(argv[1], "file_revid") || argv[3] != NULL)
		  return(-1);

		html_init(stdout, NULL);

		c = 1;
		if (dsv != NULL) {
		  for (i = 0; i < dsvec_len(dsv); i++) {
			p = (char *) dsvec_ptr_index(dsv, i);
			if (streq(argv[2], hash_str(p))) {
			  printf("%s\n", html_encode(p));
			  break;
			}
		  }
		  if (i == dsvec_len(dsv))
			rc = -1;
		}
		else
		  rc = -1;

		emit_html_trailer(stdout);
	  }
	}

	break;

  default:
	init_common_status(&common_status, "dacs_admin", 0, "Unsupported method");
	return(-1);
  }

  rc = 0;

 done:
  if (errmsg != NULL)
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

  if (rc == -1)
	init_common_status(&common_status, "dacs_admin", 0, errmsg);

  return(rc);
}

int
main(int argc, char **argv)
{
  int i, st, xargc;
  char *errcode, *errmsg, **xargv;
  char *m, *p, **admin_argv, *path_info, *rname;
  Dsvec *admin_vec, *path_vec;
  Html_header_conf *html_conf;
  Http_method method;
  Kwv *kwv;
  Root_res *res;

  errmsg = "Internal error";
  errcode = "500";
  common_status.context = common_status.code = common_status.message = NULL;
  method = HTTP_UNKNOWN_METHOD;

  /*
   * If the HTTP method is POST, then a METHOD argument can be provided to
   * identify the operation to be performed on the object (tunneling).
   * For any other HTTP method, a METHOD argument is ignored because the
   * HTTP method identifies the operation.
   * FORMAT argument selects output content type, with default HTML.
   *
   * HTTP method or METHOD argument:
   *   HEAD:   test URL
   *   GET:    retrieve object
   *   PUT:    create or replace an object
   *   DELETE: delete an object
   *   POST:   create an object, or append to an object
   * http://rest.blueoxen.net/cgi-bin/wiki.pl?MinimumMethods
   * http://rest.blueoxen.net/cgi-bin/wiki.pl?HttpMethods
   *
   * General format:
   *   /dacs_admin/<object-class>[/<object-name>][/<object-elements>]...
   *
   * <object-class>: /
   * HEAD     200 OK/<error>
   * GET      list of <object-class>
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /federation
   * HEAD     200 OK/<error>
   * GET      list of all federation sub-components, including jurisdictions
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /federation/<element>
   * HEAD     200 OK/<error>
   * GET      named sub-component
   * PUT      <error> (eventually, replace existing element)
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /jurisdictions
   * HEAD     200 OK/<error>
   * GET      list of jurisdictions
   * PUT      <error>
   * DELETE   <error>
   * POST     push jurisdiction info?/<error>
   *
   * <object-class>: /jurisdiction/<jurisdiction-name>
   * HEAD     200 OK/<error>
   * GET      the named jurisdiction/<error>
   * PUT      create/replace named jurisdiction in body
   * DELETE   delete named jurisdiction
   * POST     <error>
   *
   * <object-class>: /jurisdiction/<jurisdiction-name>/<element>
   * HEAD     200 OK/<error>
   * GET      named sub-component of the named jurisdiction/<error>
   * PUT      create/replace named sub-component of jurisdiction in body
   * DELETE   delete named jurisdiction sub-component
   * POST     <error>
   *
   * <object-class>: /users
   * HEAD     200 OK/<error>
   * GET      list of users
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /users/<user-name>
   * HEAD     200 OK/<error>
   * GET      sub-components of the named user/<error>
   * PUT      create/replace sub-components of named user in body
   * DELETE   delete named user
   * POST     <error>
   *
   * <object-class>: /users/<user-name>/{enabled,disabled}
   * HEAD     200 OK/<error>
   * GET      test the state of the named user (true or false), or
   *          list its sub-component elements, or <error>
   * PUT      set the state of the named user, or replace all of its
   *          sub-component(s) with the sub-component(s) found in the body;
   *          a PASSWORD argument may specify a password, a STATE argument
   *          may specify an enabled or disabled state.
   * DELETE   <error>
   * POST     <error>
   *
   *
   * <object-class>: /conf
   * HEAD     200 OK/<error>
   * GET      list of configuration elements
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/conf
   * HEAD     200 OK/<error>
   * GET      list of "active" configuration directives
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/conf/<directive-name>
   * HEAD     200 OK/<error>
   * GET      value(s) of specified "active" configuration directive
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/site
   * HEAD     200 OK/<error>
   * GET      list of "raw" site configuration directives
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/site/<directive-name>
   * HEAD     200 OK/<error>
   * GET      value(s) of specified "raw" site configuration directive
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/default
   * HEAD     200 OK/<error>
   * GET      list of "raw" default configuration directives
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/default/<directive-name>
   * HEAD     200 OK/<error>
   * GET      value(s) of specified "raw" default configuration directive
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/jurisdiction
   * HEAD     200 OK/<error>
   * GET      list of "raw" jurisdiction configuration directives
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/jurisdiction/<directive-name>
   * HEAD     200 OK/<error>
   * GET      value(s) of specified "raw" jurisdiction configuration directive
   * PUT      <error>
   * DELETE   <error>
   * POST     <error>
   *
   * <object-class>: /conf/<*>/{Auth,Roles,Transfer}
   * <object-class>: /conf/<*>/{Auth,Roles,Transfer}/<id>
   * <object-class>: /conf/<*>/{Auth,Roles,Transfer}/<id>/<directive-name>
   *
   *          <object-class>: /acls
   * HEAD     200 OK/<error>
   * GET      list of ACLs
   * PUT      <error>
   * DELETE   <error>
   * POST     create ACL in body with new, generated name
   *
   *          <object-class>: /acls/<acl-name>
   * HEAD     200 OK/<error>
   * GET      the named ACL/<error>
   * PUT      create/replace named ACL in body
   * DELETE   delete named ACL
   * POST     <error>
   *
   *         <object-class>: /acls/<acl-name>/{enabled,disabled,rules,services}
   * HEAD     200 OK/<error>
   * GET      test the state of the named ACL (true or false), or
   *          list its sub-component elements, or <error>
   * PUT      set the state of the named ACL, or replace all of its
   *          sub-component(s) with the sub-component(s) found in the body
   * DELETE   <error>
   * POST     add to the named ACL's sub-component(s) with the
   *          sub-component(s) found in the body; an INDEX argument
   *          can specify the index that the first new sub-component must
   *          have and can be a positive integer or the words "first" or "last"
   *          and if it is number greater than the post-addition length,
   *          "last" is assumed.
   *
   *          <object-class>: /acls/<acl-name>/{rules,services}/<index>
   * HEAD     200 OK/<error>
   * GET      return the named ACL sub-component or <error>
   * PUT      replace the sub-component with the sub-component found in the
   *          body
   * DELETE   delete the sub-component
   * POST     <error>
   *
   * Same for dacs_acls
   * Similar for /revocations
   *
   */
  xargc = argc;
  xargv = argv;
  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (emit_xml) {
	  if (common_status.message == NULL)
		init_common_status(&common_status, "dacs_admin", errcode, errmsg);
	  else
		errmsg = common_status.message;

	  if (method != HTTP_HEAD_METHOD) {
		emit_xml_header(stdout, "dacs_admin");
		printf("<%s>\n", make_xml_root_element("dacs_admin"));
		printf("%s", make_xml_common_status(&common_status));
		printf("</dacs_admin>\n");
		emit_xml_trailer(stdout);
	  }
	  else
		emit_html_header_status_line(stdout, errcode, errmsg);
	}
	else {
	  emit_html_header_status_line(stdout, errcode, 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;
	}
  }

  html_conf = emit_html_header_conf(NULL);
  html_conf->no_cache = 1;
  emit_html_header_conf(html_conf);

  emit_xml = test_emit_xml_format();

  m = getenv("REQUEST_METHOD");
  if (m == NULL) {
    errmsg = "No REQUEST_METHOD available?";
    goto fail;
  }
  if ((method = http_string_to_method(m)) == HTTP_UNKNOWN_METHOD) {
	errmsg = "Unsupported REQUEST_METHOD";
	errcode = "400";
	goto fail;
  }

  if ((path_info = getenv("PATH_INFO")) == NULL)
    path_info = "/";

  log_msg((LOG_TRACE_LEVEL, "method=\"%s\", path_info=\"%s\"", m, path_info));
  log_msg((LOG_TRACE_LEVEL, "emit_xml=%d", emit_xml));

  if ((path_vec = strsplitd(path_info, "/", 0, 1)) == NULL) {
	errmsg = "Invalid PATH_INFO";
	errcode = "400";
	goto fail;
  }

  rname = (char *) dsvec_ptr_index(path_vec, 0);
  for (res = root_resources; res->name != NULL; res++) {
	if (streq(res->name, rname))
	  break;
  }

  if (res->name == NULL) {
	errmsg = ds_xprintf("No resource matches '%s'", path_info);
	errcode = "400";
	goto fail;
  }

  admin_vec = dsvec_init(NULL, sizeof(char *));
  for (i = 0; i < dsvec_len(path_vec); i++) {
	p = (char *) dsvec_ptr_index(path_vec, i);
	dsvec_add_ptr(admin_vec, p + 1);
  }
  dsvec_add_ptr(admin_vec, NULL);

  admin_argv = (char **) dsvec_base(admin_vec);
  if ((st = res->req(res, method, admin_argv, kwv)) == -1) {
	errmsg = "Request failed";
	errcode = "405";	/* "Method not allowed" */
	goto fail;
  }

  exit(0);
}
