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

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * Configuration file parsing and support
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: conf.c 2510 2011-09-23 17:06:22Z brachman $";
#endif

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

static MAYBE_UNUSED char *log_module_name = "conf";

#ifndef PROG

/*
 * We use GCC's macro processing stringification and concatenation features
 * to describe these configuration tables concisely.
 * Configuration files use strings for configuration directives
 * (e.g., JURISDICTION_NAME) but DACS uses integer identifiers internally
 * (e.g., CONF_JURISDICTION_NAME).  The main configuration table (conf_vartab)
 * and sub-configuration tables (e.g., auth_vartab) map between integer
 * ids and strings.
 */

#define CONF_DIRECTIVE(NAME, TYPE)	\
  { CONF_##NAME, #NAME, TYPE, NULL }

#define CONF_SUBDIRECTIVE(SUBTAB, NAME, TYPE)		\
  { CONF_##SUBTAB##_##NAME, #NAME, TYPE, NULL }

#define CONF_DSTR(SUBTAB, STR)	CONF_##SUBTAB##_##STR##_EVAL

#define CONF_SUBDIRECTIVE_EVAL(SUBTAB, INTNAME, STRNAME, TYPE)		\
  { CONF_DSTR(SUBTAB, INTNAME), STRNAME, TYPE, NULL }

#define CONF_NULL_DIRECTIVE()	\
  { KWV_VARTAB_END_ID, NULL, KWV_NULL, NULL }

Kwv_vartab conf_vartab[] = {
  CONF_DIRECTIVE(PASSWORD_OPS_NEED_PASSWORD,              KWV_OPTIONAL1),
  CONF_DIRECTIVE(JURISDICTION_NAME,                       KWV_REQUIRED1),
  CONF_DIRECTIVE(FEDERATION_DOMAIN,                       KWV_REQUIRED1),
  CONF_DIRECTIVE(PASSWORD_SALT_PREFIX,                    KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_ERROR_HANDLER,                      KWV_STACK),
  CONF_DIRECTIVE(HTTP_PROG,                               KWV_REQUIRED1),
  CONF_DIRECTIVE(VERIFY_IP,                               KWV_REQUIRED1),
  CONF_DIRECTIVE(AUTH_SUCCESS_HANDLER,                    KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_CREDENTIALS_DEFAULT_LIFETIME_SECS,  KWV_REQUIRED1),
  CONF_DIRECTIVE(FEDERATION_NAME,                         KWV_REQUIRED1),
  CONF_DIRECTIVE(DTD_BASE_URL,                            KWV_OPTIONAL1),
  CONF_DIRECTIVE(XSD_BASE_URL,                            KWV_OPTIONAL1),
  CONF_DIRECTIVE(PASSWORD_DIGEST,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(SSL_PROG,                                KWV_OPTIONAL1),
  CONF_DIRECTIVE(SSL_PROG_CA_CRT,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(SIGNOUT_HANDLER,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_MAPPER_LOG_FILE,              KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_MAPPER_LOGGING,               KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_MAPPER_RULES_FILE,            KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_MAPPER_DEFAULT_ACTION,        KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_PROG_URI,                     KWV_OPTIONAL1),
  CONF_DIRECTIVE(PROXY_EXEC_DOCUMENT_ROOT,                KWV_OPTIONAL1),
  CONF_DIRECTIVE(TRACE_LEVEL,                             KWV_OPTIONAL1),
  CONF_DIRECTIVE(VERBOSE_LEVEL,                           KWV_OPTIONAL1),
  CONF_DIRECTIVE(ALLOW_HTTP_COOKIE,                       KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_FAIL_DELAY_SECS,                    KWV_OPTIONAL1),
  CONF_DIRECTIVE(TEMP_DIRECTORY,                 	      KWV_OPTIONAL1),
  CONF_DIRECTIVE(PERMIT_CHAINING,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_ERROR_HANDLER,                       KWV_STACK),
  CONF_DIRECTIVE(SECURE_MODE,                             KWV_OPTIONAL1),
  CONF_DIRECTIVE(COOKIE_PATH,                             KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_POST_BUFFER_LIMIT,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_AUTHENTICATED_ONLY,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_ACCESS_TOKEN_ENABLE,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_ACCESS_TOKEN_LIFETIME_LIMIT,         KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_ACCESS_TOKEN_LIFETIME_SECS,          KWV_OPTIONAL1),
  CONF_DIRECTIVE(NAME_COMPARE,                            KWV_OPTIONAL1),
  CONF_DIRECTIVE(LOG_FILE,                                KWV_OPTIONAL1),
  CONF_DIRECTIVE(LOG_FILTER,                              KWV_STACK),
  CONF_DIRECTIVE(LOG_LEVEL,                               KWV_OPTIONAL1),
  CONF_DIRECTIVE(LOG_SENSITIVE,                           KWV_OPTIONAL1),
  CONF_DIRECTIVE(LOGINGEN_FILE,                           KWV_OPTIONAL1),
  CONF_DIRECTIVE(LOGINGEN_PROG,                           KWV_OPTIONAL1),
  CONF_DIRECTIVE(SSL_PROG_CLIENT_CRT,                     KWV_OPTIONAL1),
  CONF_DIRECTIVE(SSL_PROG_ARGS,                           KWV_OPTIONAL1),
  CONF_DIRECTIVE(ADMIN_IDENTITY,                          KWV_OPTIONAL),
  CONF_DIRECTIVE(NOTICES_ACCEPT_HANDLER,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(NOTICES_DECLINE_HANDLER,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(NOTICES_ACK_HANDLER,                     KWV_OPTIONAL1),
  CONF_DIRECTIVE(NOTICES_SECURE_HANDLER,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(NOTICES_WORKFLOW_LIFETIME_SECS,          KWV_OPTIONAL1),
  CONF_DIRECTIVE(NOTICES_NAT_NAME_PREFIX,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(VFS,                                     KWV_STACK),
  CONF_DIRECTIVE(AUTH_AGENT_ALLOW_ADMIN_IDENTITY,         KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_SUCCESS,                            KWV_OPTIONAL),
  CONF_DIRECTIVE(COOKIE_HTTPONLY,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_FAIL,                               KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_SUCCESS,                             KWV_OPTIONAL),
  CONF_DIRECTIVE(LOG_FORMAT,                              KWV_OPTIONAL1),
  CONF_DIRECTIVE(COMPAT_MODE,                             KWV_OPTIONAL1),
  CONF_DIRECTIVE(STATUS_LINE,                             KWV_OPTIONAL1),
  CONF_DIRECTIVE(COOKIE_NO_DOMAIN,                        KWV_OPTIONAL1),
  CONF_DIRECTIVE(CSS_PATH,                                KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_TRANSFER_EXPORT,                    KWV_OPTIONAL),
  CONF_DIRECTIVE(AUTH_TRANSFER_TOKEN_LIFETIME_SECS,       KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACCEPT_ALIEN_CREDENTIALS,                KWV_OPTIONAL1),
  CONF_DIRECTIVE(HTTP_AUTH,                               KWV_STACK),
  CONF_DIRECTIVE(HTTP_AUTH_ENABLE,                        KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_PRE_AUTH,                            KWV_OPTIONAL),
  CONF_DIRECTIVE(ACS_FAIL,                                KWV_OPTIONAL1),
  CONF_DIRECTIVE(RLINK,                                   KWV_OPTIONAL),
  CONF_DIRECTIVE(PASSWORD_CONSTRAINTS,                    KWV_OPTIONAL1),
  CONF_DIRECTIVE(ROLE_STRING_MAX_LENGTH,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(VERIFY_UA,                               KWV_OPTIONAL1),
  CONF_DIRECTIVE(UNAUTH_ROLES,                            KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_CREDENTIALS_LIMIT,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(PAMD_HOST,                               KWV_OPTIONAL1),
  CONF_DIRECTIVE(PAMD_PORT,                               KWV_OPTIONAL1),
  CONF_DIRECTIVE(AUTH_SINGLE_COOKIE,                      KWV_OPTIONAL1),
  CONF_DIRECTIVE(UPROXY_APPROVED,                         KWV_STACK),
  CONF_DIRECTIVE(ACS_EMIT_APPROVAL,                       KWV_OPTIONAL1),
  CONF_DIRECTIVE(EVAL,                                    KWV_SCAN),
  CONF_DIRECTIVE(AUTH_CREDENTIALS_ADMIN_LIFETIME_SECS,    KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_DIGEST,                         KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_USERNAME_SELECTOR,              KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_MEX_URL,                        KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_TOKEN_ISSUER,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_AUTH_TYPE,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_IP_PRIVACY_URL,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_IP_PRIVACY_VERSION,             KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_URL,                        KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_KEYFILE,                    KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_KEYFILE_PASSWORD,           KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_CERTFILE,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_STS_CACERTFILE,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_LIFETIME_SECS,             KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_DATETIME_EXPIRES,          KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_IMAGE_BASE_URL,            KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_OUTPUTDIR,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARDID_BASE_URL,                KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARDID_SUFFIX,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_TOKEN_LIFETIME_SECS,            KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_DEFS_URL,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_FILL_URL,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_CARD_VERSION,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_AUDIENCE,                       KWV_OPTIONAL),
  CONF_DIRECTIVE(INFOCARD_STRONG_RP_IDENTITY,             KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_ISSUER_INFO_ENTRY,              KWV_OPTIONAL),
  CONF_DIRECTIVE(INFOCARD_STS_PASSWORD_METHOD,            KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_REQUIRE_APPLIES_TO,             KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_AUDIENCE_RESTRICTION,           KWV_OPTIONAL),
  CONF_DIRECTIVE(INFOCARD_STS_RP_ENDPOINT,                KWV_OPTIONAL),
  CONF_DIRECTIVE(INFOCARD_TOKEN_MAX_LENGTH,               KWV_OPTIONAL1),
  CONF_DIRECTIVE(INFOCARD_TOKEN_DRIFT_SECS,               KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_POST_EXCEPTION_MODE,                 KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_TRACK_ACTIVITY,                      KWV_OPTIONAL1),
  CONF_DIRECTIVE(ACS_INACTIVITY_LIMIT_SECS,               KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_USE_CODEWORD,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_CODEWORD_TEMPLATE,              KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_EMAIL_TEMPLATE_FILE,            KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_SUCCESS_HANDLER,                KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_EMAIL_SUCCESS_HANDLER,          KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_ERROR_HANDLER,                  KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_CODEWORD_HANDLER,               KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_LINK_LIFETIME_SECS,             KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_ITEM_TYPE,                      KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_NICKNAMES,                      KWV_OPTIONAL1),
  CONF_DIRECTIVE(REGISTER_USE_QUESTION,                   KWV_OPTIONAL1),
  CONF_DIRECTIVE(TOKEN_REQUIRES_PIN,                      KWV_OPTIONAL1),
  CONF_DIRECTIVE(TOKEN_HOTP_ACCEPT_WINDOW,                KWV_OPTIONAL1),
  CONF_NULL_DIRECTIVE()
};

Kwv_vartab auth_vartab[] = {
  CONF_SUBDIRECTIVE(AUTH, URL,                       KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, STYLE,              		 KWV_REQUIRED1),
  CONF_SUBDIRECTIVE(AUTH, CONTROL,             		 KWV_REQUIRED1),
  CONF_SUBDIRECTIVE(AUTH, FLAGS,           		     KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, PREDICATE,       		     KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, OPTION,             		 KWV_OPTIONAL),
  CONF_SUBDIRECTIVE_EVAL(AUTH, OPTION, "OPTION*",    KWV_OPTIONAL),
  CONF_SUBDIRECTIVE_EVAL(AUTH, URL,    "URL*",       KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, INIT,   "INIT*",      KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, EXIT,   "EXIT*",      KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, CREDENTIALS_LIFETIME_SECS, KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_USERNAME_URL,       	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, LDAP_USERNAME_URL, "LDAP_USERNAME_URL*",
						 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_ADMIN_URL,       	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_ADMIN_PASSWORD,     	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_SEARCH_ROOT_DN,     	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_SEARCH_FILTER,      	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, LDAP_SEARCH_FILTER, "LDAP_SEARCH_FILTER*",
						 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, LDAP_ROLES_SELECTOR, "LDAP_ROLES_SELECTOR*",
						 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(AUTH, LDAP_USERNAME_EXPR, "LDAP_USERNAME_EXPR*",
						 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_BIND_METHOD,      	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, LDAP_TIMEOUT_SECS,       	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, CERT_DUMP_CLIENT,   		 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, CERT_OPENSSL_PATH,   		 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, CERT_CA_PATH, 	  		 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, CERT_NAME_ATTR,   		 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, EXPR,   		             KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(AUTH, PASSWORD_AUDIT,            KWV_OPTIONAL),
  CONF_SUBDIRECTIVE(AUTH, AUTH_ID,                   KWV_RESERVED),
  CONF_SUBDIRECTIVE(AUTH, EVAL,                      KWV_SCAN),
#ifdef NOTDEF
  CONF_SUBDIRECTIVE(AUTH, VFS_ITEM_TYPE,             KWV_OPTIONAL),
#endif
  CONF_NULL_DIRECTIVE()
};

Kwv_vartab roles_vartab[] = {
  CONF_SUBDIRECTIVE(ROLES, URL,                    	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(ROLES, URL,    "URL*",  	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(ROLES, EXPR,                     KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(ROLES, INIT,   "INIT*",     KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(ROLES, EXIT,   "EXIT*",     KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(ROLES, PREDICATE,                KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(ROLES, OPTION,                	 KWV_OPTIONAL),
  CONF_SUBDIRECTIVE_EVAL(ROLES, OPTION, "OPTION*",   KWV_OPTIONAL),
  CONF_SUBDIRECTIVE(ROLES, ROLES_ID,                 KWV_RESERVED),
  CONF_SUBDIRECTIVE(ROLES, EVAL,                     KWV_SCAN),
  CONF_NULL_DIRECTIVE()
};

Kwv_vartab transfer_vartab[] = {
  CONF_SUBDIRECTIVE(TRANSFER, IMPORT_FROM,               KWV_REQUIRED),
  CONF_SUBDIRECTIVE(TRANSFER, REFEDERATE,                KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, PREDICATE,            	 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, CREDENTIALS_LIFETIME_SECS, KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, IMPORT_ROLES,              KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, IMPORT_URL,                KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, SUCCESS_URL,               KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, ERROR_URL,                 KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(TRANSFER, ROLES, "ROLES*",      KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE_EVAL(TRANSFER, EXIT,  "EXIT*",       KWV_OPTIONAL1),
  CONF_SUBDIRECTIVE(TRANSFER, TRANSFER_ID,               KWV_RESERVED),
  CONF_SUBDIRECTIVE(TRANSFER, EVAL,                      KWV_SCAN),
  CONF_NULL_DIRECTIVE()
};

static char *predefined_item_types[] = {
  ITEM_TYPE_NAMES,
  NULL
};

static Kwv_conf kwv_conf = {
  " \t", "", NULL, KWV_CONF_DEFAULT, NULL, 30, NULL, NULL
};

typedef enum Parse_xml_conf_state_code {
  CONF_PARSE_CONFIGURATION         = 1,
  CONF_PARSE_JURISDICTION_CONF     = 2,
  CONF_PARSE_DEFAULT_CONF          = 3,
  CONF_PARSE_AUTH_CONF             = 4,
  CONF_PARSE_ROLES_CONF            = 5,
  CONF_PARSE_TRANSFER_CONF         = 6
} Parse_xml_conf_state_code;

typedef struct Parse_xml_conf_state {
  Parse_xml_conf_state_code code;
  union {
	Configuration *configuration;
	Jurisdiction_conf *jurisdiction_conf;
	Auth_conf *auth_conf;
	Roles_conf *roles_conf;
	Transfer_conf *transfer_conf;
  } object;
} Parse_xml_conf_state;


static Parse_xml_conf_state *
parse_xml_make_conf_state(Parse_xml_conf_state_code code, void *object)
{
  Parse_xml_conf_state *s;

  s = ALLOC(Parse_xml_conf_state);
  s->code = code;

  switch (code) {
  case CONF_PARSE_CONFIGURATION:
	s->object.configuration = (Configuration *) object;
	break;
  case CONF_PARSE_JURISDICTION_CONF:
	s->object.jurisdiction_conf = (Jurisdiction_conf *) object;
	break;
  case CONF_PARSE_AUTH_CONF:
	s->object.auth_conf = (Auth_conf *) object;
	break;
  case CONF_PARSE_ROLES_CONF:
	s->object.roles_conf = (Roles_conf *) object;
	break;
  case CONF_PARSE_TRANSFER_CONF:
	s->object.transfer_conf = (Transfer_conf *) object;
	break;
  case CONF_PARSE_DEFAULT_CONF:
	s->object.configuration = (Configuration *) object;
	break;
  default:
	/* XXX ??? */
	return(NULL);
  }

  return(s);
}

static Parse_attr_tab conf_jurisdiction_attr_tab[] = {
  { "uri",      NULL, ATTR_IMPLIED, NULL, 0 },
  { "uri_expr", NULL, ATTR_IMPLIED, NULL, 0 },
  { NULL,       NULL, ATTR_END,     NULL, 0 }
};

static Parse_attr_tab conf_auth_attr_tab[] = {
  { "id",  NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,  NULL, ATTR_END,      NULL, 0 }
};

static Parse_attr_tab conf_roles_attr_tab[] = {
  { "id",  NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,  NULL, ATTR_END,      NULL, 0 }
};

static Parse_attr_tab conf_transfer_attr_tab[] = {
  { "id",  NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,  NULL, ATTR_END,      NULL, 0 }
};

static void
parse_xml_conf_element_start(void *data, const char *element,
							 const char **attr)
{
  char *el, *errmsg;
  Parse_xml_conf_state *state;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  if (parse_xml_is_error(NULL))
	return;

  if (streq(el, "Configuration")) {
	Configuration *c;

	if (parse_xml_is_not_empty())
	  return;
	c = ALLOC(Configuration);
	c->jurisdiction_conf_head = NULL;
	c->default_auth_conf = NULL;
	c->default_roles_conf = NULL;
	c->default_transfer_conf = NULL;
	ds_init(&c->default_conf_string);

	parse_xml_push(parse_xml_make_conf_state(CONF_PARSE_CONFIGURATION,
											 (void *) c));
	if (parse_xml_attr(NULL, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else if (streq(el, "Jurisdiction")) {
	Configuration *c;
	Jurisdiction_conf *new_conf;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != CONF_PARSE_CONFIGURATION)
	  return;
	c = state->object.configuration;

	new_conf = ALLOC(Jurisdiction_conf);
	new_conf->uri_expr = NULL;
	new_conf->uri = NULL;
	new_conf->parsed_uri = NULL;
	ds_init(&new_conf->string);
	new_conf->auth_conf = NULL;
	new_conf->roles_conf = NULL;
	new_conf->transfer_conf = NULL;

	new_conf->next = c->jurisdiction_conf_head;
	c->jurisdiction_conf_head = new_conf;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_JURISDICTION_CONF,
											 (void *) new_conf));
	conf_jurisdiction_attr_tab[0].value = &new_conf->uri;
	conf_jurisdiction_attr_tab[1].value = &new_conf->uri_expr;
	if (parse_xml_attr(conf_jurisdiction_attr_tab, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
	else {
	  if ((new_conf->uri == NULL && new_conf->uri_expr == NULL)
		  || (new_conf->uri != NULL && new_conf->uri_expr != NULL))
		parse_xml_set_error("The uri or uri_expr attribute (but not both) must be given");
	  else if (new_conf->uri != NULL) {
		new_conf->parsed_uri = uri_init(NULL);
		if (uri_parse_hier_part(new_conf->parsed_uri, new_conf->uri, 1)
			== NULL)
		  parse_xml_set_error("Invalid uri attribute value");
	  }
	}
  }
  else if (streq(el, "Auth")) {
	Auth_conf *auth;
	Jurisdiction_conf *j;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| (state->code != CONF_PARSE_JURISDICTION_CONF
			&& state->code != CONF_PARSE_DEFAULT_CONF))
	  return;
	j = state->object.jurisdiction_conf;

	auth = ALLOC(Auth_conf);
	ds_init(&auth->string);
	auth->next = NULL;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_AUTH_CONF,
											 (void *) auth));
	conf_auth_attr_tab[0].value = &auth->id;
	if (parse_xml_attr(conf_auth_attr_tab, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else if (streq(el, "Roles")) {
	Roles_conf *roles;
	Jurisdiction_conf *j;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| (state->code != CONF_PARSE_JURISDICTION_CONF
			&& state->code != CONF_PARSE_DEFAULT_CONF))
	  return;
	j = state->object.jurisdiction_conf;

	roles = ALLOC(Roles_conf);
	ds_init(&roles->string);
	roles->next = NULL;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_ROLES_CONF,
											 (void *) roles));
	conf_roles_attr_tab[0].value = &roles->id;
	if (parse_xml_attr(conf_roles_attr_tab, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else if (streq(el, "Transfer")) {
	Transfer_conf *transfer;
	Jurisdiction_conf *j;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| (state->code != CONF_PARSE_JURISDICTION_CONF
			&& state->code != CONF_PARSE_DEFAULT_CONF))
	  return;
	j = state->object.jurisdiction_conf;

	transfer = ALLOC(Transfer_conf);
	ds_init(&transfer->string);
	transfer->next = NULL;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_TRANSFER_CONF,
											 (void *) transfer));
	conf_transfer_attr_tab[0].value = &transfer->id;
	if (parse_xml_attr(conf_transfer_attr_tab, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else if (streq(el, "Default")) {
	Configuration *c;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != CONF_PARSE_CONFIGURATION)
	  return;
	c = state->object.configuration;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_DEFAULT_CONF,
											 (void *) c));
	if (c->jurisdiction_conf_head != NULL)
	  parse_xml_set_error("Default section must precede Jurisdiction sections");
	else if (ds_len(&c->default_conf_string) != 0)
	  parse_xml_set_error("Multiple Default sections not allowed");
	else if (parse_xml_attr(NULL, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else {
	parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
  }
}

static void
parse_xml_conf_element_end(void *data, const char *element)
{
  char *el, *msg;
  Configuration **c;
  Parse_xml_conf_state *state;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  if (parse_xml_is_error(NULL))
	return;

  msg = "";
  if (streq(el, "Configuration")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Configuration section";
	  goto err;
	}
	if (state->code != CONF_PARSE_CONFIGURATION || parse_xml_is_not_empty()) {
	  msg = "Unexpected end of Configuration";
	  goto err;
	}

	c = (Configuration **) data;
	*c = state->object.configuration;
  }
  else if (streq(el, "Jurisdiction")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Jurisdiction section";
	  goto err;
	}
	if (state->code != CONF_PARSE_JURISDICTION_CONF) {
	  msg = "Unexpected end of Jurisdiction section";
	  goto err;
	}
  }
  else if (streq(el, "Auth")) {
	Auth_conf **a, *auth;
	Configuration *c;
	Jurisdiction_conf *j;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	if (state->code != CONF_PARSE_AUTH_CONF) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	auth = (Auth_conf *) state->object.auth_conf;

	if (parse_xml_top((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	if (state->code == CONF_PARSE_JURISDICTION_CONF) {
	  j = (Jurisdiction_conf *) state->object.jurisdiction_conf;
	  a = &j->auth_conf;
	}
	else if (state->code == CONF_PARSE_DEFAULT_CONF) {
	  c = (Configuration *) state->object.configuration;
	  a = &c->default_auth_conf;
	}
	else {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}

	while (*a != NULL)
	  a = &(*a)->next;
	*a = auth;
  }
  else if (streq(el, "Roles")) {
	Roles_conf **r, *roles;
	Configuration *c;
	Jurisdiction_conf *j;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}
	if (state->code != CONF_PARSE_ROLES_CONF) {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}
	roles = (Roles_conf *) state->object.roles_conf;

	if (parse_xml_top((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}
	if (state->code == CONF_PARSE_JURISDICTION_CONF) {
	  j = (Jurisdiction_conf *) state->object.jurisdiction_conf;
	  r = &j->roles_conf;
	}
	else if (state->code == CONF_PARSE_DEFAULT_CONF) {
	  c = (Configuration *) state->object.configuration;
	  r = &c->default_roles_conf;
	}
	else {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}

	while (*r != NULL)
	  r = &(*r)->next;
	*r = roles;
  }
  else if (streq(el, "Transfer")) {
	Transfer_conf **t, *transfer;
	Configuration *c;
	Jurisdiction_conf *j;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Transfer section";
	  goto err;
	}
	if (state->code != CONF_PARSE_TRANSFER_CONF) {
	  msg = "Unexpected end of Transfer section";
	  goto err;
	}
	transfer = (Transfer_conf *) state->object.transfer_conf;

	if (parse_xml_top((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Transfer section";
	  goto err;
	}
	if (state->code == CONF_PARSE_JURISDICTION_CONF) {
	  j = (Jurisdiction_conf *) state->object.jurisdiction_conf;
	  t = &j->transfer_conf;
	}
	else if (state->code == CONF_PARSE_DEFAULT_CONF) {
	  c = (Configuration *) state->object.configuration;
	  t = &c->default_transfer_conf;
	}
	else {
	  msg = "Unexpected end of Transfer section";
	  goto err;
	}

	while (*t != NULL)
	  t = &(*t)->next;
	*t = transfer;
  }
  else if (streq(el, "Default")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Default section";
	  goto err;
	}
	if (state->code != CONF_PARSE_DEFAULT_CONF) {
	  msg = "Unexpected end of Default section";
	  goto err;
	}
  }
  else {
	msg = ds_xprintf("Unknown element: %s", el);
	goto err;
  }

  return;

 err:
  parse_xml_set_error(msg);
}

static void
parse_xml_conf_character_data_handler(void *userData, const XML_Char *s,
									  int len)
{
  Parse_xml_conf_state *state;
  Ds *ds;

  if (parse_xml_is_error(NULL))
    return;

  /*
   * Junk between "Jurisdiction" tags is ignored (provided it doesn't look like
   * XML markup).
   */
  if (parse_xml_top((void **) &state) == PARSE_XML_ERROR)
	return;

  if (state->code == CONF_PARSE_JURISDICTION_CONF) {
	Jurisdiction_conf *conf;

	if ((conf = state->object.jurisdiction_conf) == NULL)
	  return;
	ds = &conf->string;
  }
  else if (state->code == CONF_PARSE_AUTH_CONF) {
	Auth_conf *a;

	if ((a = state->object.auth_conf) == NULL)
	  return;
	ds = &a->string;
  }
  else if (state->code == CONF_PARSE_ROLES_CONF) {
	Roles_conf *r;

	if ((r = state->object.roles_conf) == NULL)
	  return;
	ds = &r->string;
  }
  else if (state->code == CONF_PARSE_TRANSFER_CONF) {
	Transfer_conf *t;

	if ((t = state->object.transfer_conf) == NULL)
	  return;
	ds = &t->string;
  }
  else if (state->code == CONF_PARSE_DEFAULT_CONF)
	ds = &state->object.configuration->default_conf_string;
  else
	return;

  ds_concatn(ds, (char *) s, len);
}

static int
parse_xml_conf(char *conf_string, Configuration **conf)
{
  int st;
  Parse_xml_error err;
  XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);

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

  parse_xml_init("Configuration", p);

  XML_SetElementHandler(p, parse_xml_conf_element_start,
						parse_xml_conf_element_end);
  XML_SetCharacterDataHandler(p, parse_xml_conf_character_data_handler);

  *conf = NULL;
  XML_SetUserData(p, (void *) conf);

  st = XML_Parse(p, conf_string, strlen(conf_string), 1);

  if (parse_xml_is_error(&err) || st == 0) {
	if (err.mesg == NULL) {
	  parse_xml_set_error(NULL);
	  parse_xml_is_error(&err);
	}
    log_msg((LOG_ERROR_LEVEL, "parse_xml_conf: line %d, pos %d",
             err.line, err.pos));
    if (err.mesg != NULL)
      log_msg((LOG_ERROR_LEVEL, "parse_xml_conf: %s", err.mesg));
    parse_xml_end();
    return(-1);
  }

  parse_xml_end();

  if (parse_xml_is_not_empty())
	return(-1);

  return(0);
}

static void
parse_xml_auth_conf_element_start(void *data, const char *element,
								  const char **attr)
{
  char *el, *errmsg;
  Parse_xml_conf_state *state;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  if (parse_xml_is_error(NULL))
	return;

  if (streq(el, "Configuration")) {
	if (parse_xml_is_not_empty())
	  return;
	parse_xml_push(parse_xml_make_conf_state(CONF_PARSE_CONFIGURATION,
											 (void *) NULL));
  }
  else if (streq(el, "Jurisdiction")) {
	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != CONF_PARSE_CONFIGURATION)
	  return;
	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_JURISDICTION_CONF,
											 (void *) NULL));
  }
  else if (streq(el, "Auth")) {
	Auth_conf *auth;

	if (parse_xml_is_not_empty()
		&& (parse_xml_top((void **) &state) != PARSE_XML_OK
			|| (state->code != CONF_PARSE_JURISDICTION_CONF
				&& state->code != CONF_PARSE_DEFAULT_CONF)))
	  return;

	auth = ALLOC(Auth_conf);
	ds_init(&auth->string);
	auth->next = NULL;

	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_AUTH_CONF,
											 (void *) auth));
	conf_auth_attr_tab[0].value = &auth->id;
	if (parse_xml_attr(conf_auth_attr_tab, attr, &errmsg) == -1)
	  parse_xml_set_error(errmsg);
  }
  else if (streq(el, "Roles")) {
	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| (state->code != CONF_PARSE_JURISDICTION_CONF
			&& state->code != CONF_PARSE_DEFAULT_CONF))
	  return;
	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_ROLES_CONF,
											 (void *) NULL));
  }
  else if (streq(el, "Default")) {
	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != CONF_PARSE_CONFIGURATION)
	  return;
	parse_xml_push((void *)
				   parse_xml_make_conf_state(CONF_PARSE_DEFAULT_CONF,
											 (void *) NULL));
  }
  else {
	parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
  }
}

static void
parse_xml_auth_conf_element_end(void *data, const char *element)
{
  char *el, *msg;
  Parse_xml_conf_state *state;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  if (parse_xml_is_error(NULL))
	return;

  msg = "";
  if (streq(el, "Configuration")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Configuration section";
	  goto err;
	}
	if (state->code != CONF_PARSE_CONFIGURATION || parse_xml_is_not_empty()) {
	  msg = "Unexpected end of Configuration";
	  goto err;
	}
  }
  else if (streq(el, "Jurisdiction")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Jurisdiction section";
	  goto err;
	}
	if (state->code != CONF_PARSE_JURISDICTION_CONF) {
	  msg = "Unexpected end of Jurisdiction section";
	  goto err;
	}
  }
  else if (streq(el, "Auth")) {
	Auth_conf **a, *auth;
	Configuration *c;
	Jurisdiction_conf *j;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	if (state->code != CONF_PARSE_AUTH_CONF) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	auth = (Auth_conf *) state->object.auth_conf;

	if (parse_xml_top((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}
	if (state->code == CONF_PARSE_JURISDICTION_CONF) {
	  j = (Jurisdiction_conf *) state->object.jurisdiction_conf;
	  a = &j->auth_conf;
	}
	else if (state->code == CONF_PARSE_DEFAULT_CONF) {
	  c = (Configuration *) state->object.configuration;
	  a = &c->default_auth_conf;
	}
	else {
	  msg = "Unexpected end of Auth section";
	  goto err;
	}

	while (*a != NULL)
	  a = &(*a)->next;
	*a = auth;
  }
  else if (streq(el, "Roles")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}
	if (state->code != CONF_PARSE_ROLES_CONF) {
	  msg = "Unexpected end of Roles section";
	  goto err;
	}
  }
  else if (streq(el, "Default")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR) {
	  msg = "Unexpected end of Default section";
	  goto err;
	}
	if (state->code != CONF_PARSE_DEFAULT_CONF) {
	  msg = "Unexpected end of Default section";
	  goto err;
	}
  }
  else {
	msg = ds_xprintf("Unknown element: %s", el);
	goto err;
  }

  return;

 err:
  parse_xml_set_error(msg);
}

static MAYBE_UNUSED int
parse_xml_auth_conf(char *conf_string, Auth_conf **conf)
{
  int st;
  Parse_xml_error err;
  XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);

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

  parse_xml_init("AuthConfiguration", p);

  XML_SetElementHandler(p, parse_xml_auth_conf_element_start,
						parse_xml_auth_conf_element_end);
  XML_SetCharacterDataHandler(p, parse_xml_conf_character_data_handler);

  *conf = NULL;
  XML_SetUserData(p, (void *) conf);

  st = XML_Parse(p, conf_string, strlen(conf_string), 1);

  if (parse_xml_is_error(&err) || st == 0) {
	if (err.mesg == NULL) {
	  parse_xml_set_error(NULL);
	  parse_xml_is_error(&err);
	}
    log_msg((LOG_ERROR_LEVEL, "parse_xml_auth_conf: line %d, pos %d",
             err.line, err.pos));
    if (err.mesg != NULL)
      log_msg((LOG_ERROR_LEVEL, "parse_auth_xml_conf: %s", err.mesg));
    parse_xml_end();
    return(-1);
  }

  parse_xml_end();

  if (parse_xml_is_not_empty())
	return(-1);

  return(0);
}

/*
 * Certain configuration directives can be computed
 * from other run-time configuration information and defaults rather than
 * having to be explicitly provided by the DACS administrator.
 * These computations are done at initialization time (rather than on-demand)
 * so that any missing but required directives can cause an abort before
 * the program gets very far.
 *
 * Only directives having the "KWV_DERIVABLE" modifier flag may be derived.
 *
 * Directives may contain references to variables; when recognized and
 * available, these references are replaced by the variables' values.
 * The value of configuration directives and certain contextual references
 * are recognized.  Undefined references are left verbatim.
 */

/*
 * The jurisdiction's configuration information and administrator's
 * default preferences have been loaded and merged into KWV.
 * Now we need to find out which missing directives need to be
 * obtained from the site's configuration.
 */
Kwv_pair *
conf_site(Kwv_vartab *v, void *site_arg)
{
  Site *site;
  Kwv_pair *s;

  site = (Site *) site_arg;

  if ((s = kwv_lookup(site->kwv_site, v->name)) != NULL) {
	log_msg((LOG_TRACE_LEVEL, "Derived directive: %s -> %s", v->name, s->val));
	return(s);
  }

  log_msg((LOG_TRACE_LEVEL, "Nothing to derive for directive: %s", v->name));
  return(NULL);
}

#ifdef NOTDEF
static char *
resolve(Var *var, void *arg)
{
  Kwv *kwv;

  if (var->ns == NULL || !streq(var->ns, "Conf"))
	return(NULL);

  kwv = (Kwv *) arg;
  return(kwv_lookup_value(kwv, var->varname));
}
#endif

typedef struct Conf_eval_arg {
  Acs_environment *env;
  char *prefix;				/* For variable names */
  int eval_eval_only;		/* Evaluate only EVAL directives, copy others */
} Conf_eval_arg;

/*
 * Evaluate the rhs of a directive.
 * Note that the EVAL directive is handled specially - each occurrence is
 * evaluated when it is first seen, but the value stored in the variable
 * table is the unevaluated value (e.g., for use by dacsconf(1)).
 *
 * Return -1 on error, 0 if the evaluation did not yield a result,
 * and 1 if it did.
 */
static int
conf_eval(char *name, char *val, const Kwv_conf *conf, void *eval_arg,
		  char **new_val)
{
  Acs_environment *env;
  Acs_expr_result st;
  Conf_eval_arg *arg;
  Expr_result result;

  arg = (Conf_eval_arg *) eval_arg;
  env = arg->env;

  if (arg->eval_eval_only && !streq(name, "EVAL")) {
	*new_val = strdup(val);
	return(1);
  }

  st = acs_expr(val, env, &result);
  log_msg((LOG_TRACE_LEVEL, "Directive evaluation result: %d", st));

  if (st == ACS_EXPR_LEXICAL_ERROR) {
	log_msg((LOG_ERROR_LEVEL,
			 "Lexical error in directive: '%s %s'", name, val));
	return(-1);
  }
  if (st == ACS_EXPR_SYNTAX_ERROR) {
	log_msg((LOG_ERROR_LEVEL,
			 "Syntax error in directive: '%s %s'", name, val));
	return(-1);
  }
  if (st == ACS_EXPR_EVAL_ERROR) {
	log_msg((LOG_ERROR_LEVEL,
			 "Evaluation error in directive: '%s=%s'", name, val));
	return(-1);
  }

  if (is_undef(&result.value)) {
	log_msg((LOG_TRACE_LEVEL, "Directive has been undefined: '%s'", name));
	return(0);
  }

  if ((*new_val = acs_format_result(&result)) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Evaluation returned NULL: '%s=%s'", name, val));
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Interpolated directive: %s -> %s",
		   name, *new_val));

  if (streq(name, "EVAL")) {
	*new_val = val;
	return(1);
  }

  var_ns_add_key(env->namespaces, "Conf",
				 strtoupper(ds_xprintf("%s%s", arg->prefix, name)),
				 *new_val);
  return(1);
}

/*
 * Merge directives in SECONDARY into PRIMARY.
 * For each unique directive in SECONDARY, each of the values of the directive
 * is merged into PRIMARY if the directive is of type KWV_STACK or if the
 * directive does not already appear in PRIMARY.
 *
 * If PRIMARY is NULL, then a copy of SECONDARY is created and returned,
 * otherwise zero or more directives are added to PRIMARY and PRIMARY is
 * returned.
 * As directives are copied and added, they are evaluated.
 */
static Kwv *
conf_merge(Kwv *primary, Kwv *secondary, Kwv_vartab *vartab, void *eval_arg)
{
  int ns, st;
  char *new_val;
  Kwv *kwv;
  Kwv_vartab *v;
  Kwv_iter *iter;
  Kwv_pair *pair, *prev_pair;

  if (primary == NULL)
	kwv = kwv_init(30);
  else
	kwv = primary;

  iter = kwv_iter_begin(secondary, NULL);

  prev_pair = NULL;
  ns = 0;
  for (pair = kwv_iter_first(iter); pair != NULL;
	   pair = kwv_iter_next(iter), ns++) {
	if (vartab != NULL)
	  v = kwv_vartab_lookup(vartab, pair->name);
	else
	  v = NULL;

	if (v != NULL && v->type == KWV_SCAN) {
	  int f;

	  f = kwv->prepend_flag;
	  kwv->prepend_flag = 1;
	  kwv_add_pair(kwv, pair);
	  kwv->prepend_flag = f;
	  continue;
	}

	/*
	 * If this is the second or later occurrence of a directive, it has
	 * already been taken care of when the first occurrence was processed.
	 */
	if (v != NULL
		&& (v->type == KWV_STACK || v->type == KWV_SCAN)
		&& prev_pair != NULL && streq(pair->name, prev_pair->name))
	  continue;

    if ((v != NULL && v->type == KWV_STACK)
		|| kwv_lookup(kwv, pair->name) == NULL) {
	  /*
	   * The directive is stackable or doesn't already exist in PRIMARY,
	   * so merge it into PRIMARY.
	   * If the directive has been repeated, merge each instance
	   * separately; although iteration will visit the subsequent occurrences,
	   * we'll detect that and not do this again.
	   */
	  prev_pair = pair;
	  while (pair != NULL) {
		st = conf_eval(pair->name, pair->val, NULL, eval_arg, &new_val);
		if (st == -1)
		  return(NULL);
		if (st != 0)
		  kwv_add(kwv, pair->name, new_val);
		pair = pair->next;
	  }
	}
	else
	  prev_pair = pair;
  }

  kwv_iter_end(iter);

  return(kwv);
}

int do_conf_resolve = 0;
Kwv *merged_kwv = NULL;
Kwv *nkwv = NULL;
Kwv *xkwv = NULL;

/*
 * KWV is the list of directives in effect but with their RHS unevaluated
 * (other than EVAL directives).
 * XXX The use of globals to hook into acs_variable_value() is an ugly kludge
 * and is only intended to be a quick fix.  Really.
 */
Kwv *
conf_resolve(Kwv *kwv, Site *site, Kwv_vartab *conf_vartab,
			 Conf_eval_arg *eval_arg)
{
  int i;
  char *nval;
  Acs_expr_result st;
  Expr_result result;
  Kwv_iter *iter;
  Kwv_pair *pair, *prev_pair;  
  Kwv_vartab *v;

  /* Reset all values in the configuration table. */
  for (i = 0; conf_vartab[i].name != NULL; i++)
	conf_vartab[i].pair = NULL;

  merged_kwv = kwv;

  nkwv = kwv_init(32);
  kwv_set_mode(nkwv, "da");
  kwv_merge(nkwv, site->kwv_conf, KWV_ALLOW_DUPS);

  xkwv = kwv_init(8);
  kwv_set_mode(xkwv, "dn");

  /*
   * For each directive in effect, evaluate its RHS.  Note that there may
   * be duplicates (e.g., VFS, ACS_ERROR_HANDLER); it's currently safe to
   * assume that they are not referenced on the RHS of any directive.
   */
  iter = kwv_iter_begin(kwv, NULL);
  do_conf_resolve = 1;
  for (pair = kwv_iter_first(iter); pair != NULL; pair = kwv_iter_next(iter)) {
	if ((v = kwv_vartab_lookup(conf_vartab, pair->name)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Internal error: cannot lookup directive"));
#ifdef NOTDEF
	  return(NULL);
#endif
	}

	if (streq(v->name, "EVAL")) {
	  kwv_add(nkwv, pair->name, pair->val);
	  if (v->pair == NULL)
		v->pair = kwv_new_pair(pair->name, pair->val, NULL);
	  else {
		prev_pair = v->pair;
		while (prev_pair->next != NULL)
		  prev_pair = prev_pair->next;
		prev_pair->next = kwv_new_pair(pair->name, pair->val, NULL);
	  }
	  continue;
	}

	if ((v->type == KWV_OPTIONAL1 || v->type == KWV_REQUIRED1)
		&& (nval = kwv_lookup_value(nkwv, pair->name)) != NULL) {
	  if (v->pair == NULL)
		v->pair = kwv_new_pair(pair->name, nval, NULL);
	  else {
		prev_pair = v->pair;
		while (prev_pair->next != NULL)
		  prev_pair = prev_pair->next;
		prev_pair->next = kwv_new_pair(pair->name, nval, NULL);
	  }
	  continue;
	}

	/* Note that NAME is being resolved. */
	if (kwv_add(xkwv, pair->name, "") == NULL) {
	  do_conf_resolve = 0;
	  log_msg((LOG_ERROR_LEVEL,
			   "Config resolution error: recursive evaluation"));
	  return(NULL);
	}

	st = acs_expr(pair->val, eval_arg->env, &result);
	if (st == ACS_EXPR_LEXICAL_ERROR || st == ACS_EXPR_SYNTAX_ERROR
		|| st == ACS_EXPR_EVAL_ERROR || do_conf_resolve < 0) {
	  do_conf_resolve = 0;
	  log_msg((LOG_ERROR_LEVEL, "Config resolution error: evaluation error"));
	  return(NULL);
	}

	if (is_undef(&result.value)) {
	  log_msg((LOG_TRACE_LEVEL, "Directive has been undefined: '%s'",
			   pair->name));
	  kwv_delete(xkwv, pair->name);
	  continue;
	}

	nval = acs_format_result(&result);

	/* Record the variable and its value, delete the note. */
	kwv_add(nkwv, pair->name, nval);
	kwv_delete(xkwv, pair->name);

	if (v->pair == NULL)
	  v->pair = kwv_new_pair(pair->name, nval, NULL);
	else {
	  prev_pair = v->pair;
	  while (prev_pair->next != NULL)
		prev_pair = prev_pair->next;
	  prev_pair->next = kwv_new_pair(pair->name, nval, NULL);
	}
  }

  do_conf_resolve = 0;
  kwv_iter_end(iter);
  kwv_free(xkwv);

  return(nkwv);
}

static Uri *
parse_service_uri(char *str)
{
  char *xstr;
  Uri *uri;

  if (strcaseprefix(str, "http:") != NULL
	  || strcaseprefix(str, "https:") != NULL)
	uri = uri_parse(str);
  else {
	xstr = ds_xprintf("%s://%s",
					  dacs_is_https_request() ? "https" : "http", str);
	uri = uri_parse(xstr);
  }

  return(uri);
}

/*
 * XXX Note that domain names that map to the same IP address (aliases)
 * are not considered to be equivalent.
 */
static int
domain_equiv(const char *subdomain, const char *domain)
{

  if (domain == NULL || domain[0] == '\0')
	return(1);

  if (domain[0] != '*' || domain[1] != '.')
	return(strcaseeq(subdomain, domain));

  return(match_domain_names(subdomain, domain + 2));
}

static int
port_equiv(const char *pattern, int match_port)
{
  int port;
  char *endptr;
  const char *s;

  s = pattern;
  while (1) {
	port = (int) strtoul(s, &endptr, 10);
	if (endptr == s || (*endptr != ',' && *endptr != '\0'))
	  return(-1);
	if (port == match_port)
	  return(1);
	if (*endptr == '\0')
	  return(0);
	s = endptr + 1;
  }
  /*NOTREACHED*/
}

/*
 * Compare a Jurisdiction's URI (pseudo) attribute value with one formed
 * from the request URI.
 * The former may be more general than the latter, or they may be
 * equivalent.
 * If the host and port components match, return the number of path component
 * elements that are shared, or -1 if the two arguments are incompatible.
 */
static int
service_uri_cmp(Uri *conf, Uri *req, char **matched)
{
  int i;
  char *c, *r;
  Ds *c_el, *r_el, match;
  Dsvec *cpath, *rpath;

  ds_init(&match);
  if (strcaseeq(req->scheme, "https")) {
	if (!strcaseeq(conf->scheme, "https"))
	  return(-1);
	ds_asprintf(&match, "https://");
  }
  else if (strcaseeq(req->scheme, "http")) {
	if (!strcaseeq(conf->scheme, "http"))
	  return(-1);
	ds_asprintf(&match, "http://");
  }

  /*
   * Compare hostname, case-insensitively.
   * The config's hostname component can look like *.[domain-name],
   * in which case it will match any request's hostname component that
   * has domain-name as a suffix; e.g., bsd6.dss.bc.ca matches *.dss.bc.ca
   * and *.ca
   */
  if (req->host == NULL || conf->host == NULL)
	return(-1);
  if (domain_equiv(req->host, conf->host) != 1)
	return(-1);
  ds_asprintf(&match, "%s", req->host);

  /* Compare ports, but only if the config URI has one. */
  if (conf->port_given != NULL) {
	if (port_equiv(conf->port_given, req->port) != 1)
	  return(-1);
	ds_asprintf(&match, ":%s", req->port);
  }

  /* Check if the config URI's path is a substring of the request's. */
  if (conf->path == NULL) {
	if (matched != NULL)
	  *matched = ds_buf(&match);
	return(0);
  }

  if (req->path == NULL)
	return(-1);

  cpath = uri_path_parse(conf->path);
  rpath = uri_path_parse(req->path);
  i = 0;
  while (1) {
	c_el = (Ds *) dsvec_ptr_index(cpath, i);
	r_el = (Ds *) dsvec_ptr_index(rpath, i);

	if (c_el == NULL && r_el == NULL) {
	  ds_asprintf(&match, "%s", conf->path);
	  if (matched != NULL)
		*matched = ds_buf(&match);
	  return(i);
	}

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

	if (c_el == NULL) {
	  ds_asprintf(&match, "%s", conf->path);
	  if (matched != NULL)
		*matched = ds_buf(&match);
	  return(i);
	}

	c = ds_buf(c_el);
	r = ds_buf(r_el);
	if (!streq(c, r))
	  return(-1);
	i++;
  }
  /*NOTREACHED*/
}

/*
 * Find the configuration for the given jurisdiction name.
 *
 * NOTE: because the value of JURISDICTION_NAME in the config file is
 * the result of an expression evaluation and evaluation doesn't occur until
 * we've located the correct Jurisdiction section, at this point we're
 * necessarily seeing the "raw" (unevaluated) JURISDICTION_NAME expression.
 * We could assume that it's a simple string or that it's a simple
 * expression that doesn't rely on evaluating anything else in its
 * Jurisdiction section (so we could evaluate it now).  Or we could add a new
 * directive for specifying JURISDICTION_NAME that has the semantics we
 * require here.
 * Instead, we'll require it to be a quoted string and assume that if it
 * needs evaluation our JURISDICTION argument will not match it.
 *
 * As a special case, if JURISDICTION is NULL or the empty string, then as
 * long as there is exactly one jurisdiction section in the config file,
 * return that jurisdiction; otherwise it's an error.
 */
static Jurisdiction_conf *
find_conf_by_jurisdiction(char *conf_file, Configuration *conf,
						  char *jurisdiction)
{
  char *j, *jname, *uri;
  Jurisdiction_conf *cp;
  Kwv *kwv;
  Kwv_conf c;

  if (jurisdiction == NULL || *jurisdiction == '\0') {
	if (conf->jurisdiction_conf_head != NULL
		&& conf->jurisdiction_conf_head->next == NULL) {
	  cp = conf->jurisdiction_conf_head;
	  goto found;
	}

	log_msg((LOG_ERROR_LEVEL, "Could not find config in \"%s\"", conf_file));
	log_msg((LOG_ERROR_LEVEL, "Exactly one jurisdiction section is required"));
	return(NULL);
  }

  c = kwv_conf;
  c.eval_func = NULL;
  c.quote_chars = "\"'";
  c.mode = KWV_CONF_DEFAULT;
  c.mode |= KWV_CONF_MULTISEP;

  for (cp = conf->jurisdiction_conf_head; cp != NULL; cp = cp->next) {
	if ((kwv = kwv_make_new(ds_buf(&cp->string), &c)) == NULL)
	  return(NULL);

	if ((jname = kwv_lookup_value(kwv, "JURISDICTION_NAME")) != NULL) {
	  /* JURISDICTION_NAME may be quoted */
	  if (jname[0] == '\'')
		j = ds_xprintf("'%s'", jurisdiction);
	  else if (jname[0] == '\"')
		j = ds_xprintf("\"%s\"", jurisdiction);
	  else
		j = jurisdiction;

	  if (name_eq(jname, j, DACS_NAME_CMP_CONFIG))
		goto found;
	}
	/* XXX could free kwv */
  }

  log_msg((LOG_ERROR_LEVEL,
		   "Could not find config in \"%s\" using jurisdiction \"%s\"",
		   conf_file, jurisdiction));
  return(NULL);

 found:
  if ((uri = current_uri_no_query(NULL)) != NULL) {
	char *match;
	Uri *conf_uri, *req_uri;

	if ((req_uri = parse_service_uri(uri)) != NULL
		&& (conf_uri = parse_service_uri(cp->uri)) != NULL
		&& service_uri_cmp(conf_uri, req_uri, &match) != -1
		&& match != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Jurisdiction's distinguishing URI=\"%s\"",
			   match));
	  dacs_effective_service_uri = match;
	}
  }

  return(cp);
}

static Jurisdiction_conf *
find_conf_by_uri(char *conf_file, Configuration *conf, char *service_uri)
{
  int best_len, len;
  char *best_match, *match;
  Jurisdiction_conf *best_jc, *cp;
  Uri *conf_uri, *req_uri;

  /*
   * In the first scan, consider only those Jurisdiction sections that have
   * an effective uri that includes a port number - the request's port number
   * must match it.  Find the longest such match and terminate successfully if
   * one if found.
   * If no match is found however, scan through the list again and consider
   * only those Jurisdiction sections that have an effective uri that omits
   * a port number; the request's port number is irrelevant during this scan.
   * Find the longest match.
   *
   * E.g., given two sections, uri=fedroot.com:8080 and uri=fedroot.com
   * a request for fedroot.com:8080 will match the first one but
   * a request for fedroot.com:443 (or any port other than 8080) will match
   * the second one.
   * This feature allows jurisdictions to also be identified based on the
   * port number: fedroot.com:8080, fedroot.com:8081, and so on could all
   * be associated with different jurisdictions.
   */

  req_uri = parse_service_uri(service_uri);
  if (req_uri == NULL)
	return(NULL);
  best_jc = NULL;
  best_len = -1;
  best_match = NULL;

  for (cp = conf->jurisdiction_conf_head; cp != NULL; cp = cp->next) {
	if ((conf_uri = parse_service_uri(cp->uri)) == NULL)
	  return(NULL);
	if (conf_uri->port_given == NULL)
	  continue;
	if ((len = service_uri_cmp(conf_uri, req_uri, &match)) == -1)
	  continue;
	if (len > best_len) {
	  best_jc = cp;
	  best_len = len;
	  best_match = match;
	}
  }

  if (best_jc == NULL) {
	for (cp = conf->jurisdiction_conf_head; cp != NULL; cp = cp->next) {
	  if ((conf_uri = parse_service_uri(cp->uri)) == NULL)
		return(NULL);
	  if (conf_uri->port_given != NULL)
		continue;
	  if ((len = service_uri_cmp(conf_uri, req_uri, &match)) == -1)
		continue;
	  if (len > best_len) {
		best_jc = cp;
		best_len = len;
		best_match = match;
	  }
	}
  }

  if (best_jc != NULL) {
	log_msg((LOG_TRACE_LEVEL, "Using Jurisdiction uri=\"%s\"",
			 best_jc->uri));
	if (best_match != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Jurisdiction's distinguishing URI=\"%s\"",
			   best_match));
	  dacs_effective_service_uri = best_match;
	}
  }
  else {
	log_msg((LOG_ERROR_LEVEL,
			 "Could not find config in \"%s\" using service URI \"%s\"",
			 conf_file, service_uri));
  }

  return(best_jc);
}

static int
process_auth_sections(Auth_conf *auth_conf, Acs_environment *env,
					  Site *site, Dsvec *dsv)
{
  char *errmsg;
  Auth_conf *a, *aa;
  Kwv *kwv_auth;
  Kwv_pair *kw;
  Kwv_vartab *err_vt, *vt, *id;
  Conf_eval_arg eval_arg;

  kwv_conf.eval_func = conf_eval;
  kwv_conf.eval_arg = &eval_arg;

  for (a = auth_conf; a != NULL; a = a->next) {
	eval_arg.env = env;
	eval_arg.prefix = strtoupper(ds_xprintf("AUTH.%s.", a->id));
	eval_arg.eval_eval_only = 0;
	if ((kwv_auth = kwv_make_new(ds_buf(&a->string), &kwv_conf)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error processing Auth section \"%s\"", a->id));
	  return(-1);
	}
	vt = kwv_vartab_new(auth_vartab);
	kwv_vartab_init(kwv_auth, vt, conf_site, (void *) site);
	if (kwv_vartab_check(kwv_auth, vt, &kw, &err_vt, &errmsg) == -1) {
	  if (err_vt != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error, Auth clause \"%s\": %s: %s",
				 a->id, errmsg, err_vt->name));
	  else if (kw != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error, Auth clause \"%s\": %s: %s",
				 a->id, errmsg, kw->name));
	  else
		log_msg((LOG_ERROR_LEVEL, "Config error, Auth clause \"%s\": %s",
				 a->id, errmsg));
	  return(-1);
	}

	/* XXX This is a kludge to retain the id tag */
	if ((id = kwv_vartab_lookup(vt, "AUTH_ID")) == NULL) {
	  log_msg((LOG_ALERT_LEVEL, "Config error: no AUTH_ID variable?"));
	  return(-1);
	}
	if (!is_valid_name(a->id)) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Config error: invalid AUTH_ID variable: \"%s\"", a->id));
	  return(-1);
	}

	for (aa = auth_conf; aa != a; aa = aa->next) {
	  if (streq(aa->id, a->id)) {
		log_msg((LOG_ERROR_LEVEL,
				 "Config error: duplicate Auth id tag '%s'", a->id));
		return(-1);
	  }
	}
	id->pair = kwv_new_pair("AUTH_ID", a->id, NULL);

	if (dsv != NULL)
	  dsvec_add_ptr(dsv, vt);
  }

  return(0);
}

static int
process_roles_sections(Roles_conf *roles_conf, Acs_environment *env,
					  Site *site, Dsvec *dsv)
{
  char *errmsg;
  Roles_conf *r, *rr;
  Kwv *kwv_roles;
  Kwv_pair *kw;
  Kwv_vartab *err_vt, *vt, *id;
  Conf_eval_arg eval_arg;

  kwv_conf.eval_func = conf_eval;
  kwv_conf.eval_arg = &eval_arg;

  for (r = roles_conf; r != NULL; r = r->next) {
	eval_arg.env = env;
	eval_arg.prefix = strtoupper(ds_xprintf("ROLES.%s.", r->id));
	eval_arg.eval_eval_only = 0;
	if ((kwv_roles = kwv_make_new(ds_buf(&r->string), &kwv_conf)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error processing Roles section \"%s\"",
			   r->id));
	  return(-1);
	}
	vt = kwv_vartab_new(roles_vartab);
	kwv_vartab_init(kwv_roles, vt, conf_site, (void *) site);
	if (kwv_vartab_check(kwv_roles, vt, &kw, &err_vt, &errmsg) == -1) {
	  if (err_vt != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error, Roles clause \"%s\": %s: %s",
				 r->id, errmsg, err_vt->name));
	  else if (kw != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error, Roles clause \"%s\": %s: %s",
				 r->id, errmsg, kw->name));
	  else
		log_msg((LOG_ERROR_LEVEL, "Config error, Roles clause \"%s\": %s",
				 r->id, errmsg));
	  return(-1);
	}

	/* XXX This is a kludge to retain the id tag */
	if ((id = kwv_vartab_lookup(vt, "ROLES_ID")) == NULL) {
	  log_msg((LOG_ALERT_LEVEL, "Config error: no ROLES_ID variable?"));
	  return(-1);
	}

	if (!is_valid_name(r->id)) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Config error: invalid ROLES_ID variable: \"%s\"", r->id));
	  return(-1);
	}

	for (rr = roles_conf; rr != r; rr = rr->next) {
	  if (streq(rr->id, r->id)) {
		log_msg((LOG_ERROR_LEVEL,
				 "Config error: duplicate Roles id tag '%s'", r->id));
		return(-1);
	  }
	}
	id->pair = kwv_new_pair("ROLES_ID", r->id, NULL);

	if (dsv != NULL)
	  dsvec_add_ptr(dsv, vt);
  }

  return(0);
}

static int
process_transfer_sections(Transfer_conf *transfer_conf, Acs_environment *env,
						  Site *site, Dsvec *dsv)
{
  char *errmsg;
  Conf_eval_arg eval_arg;
  Kwv *kwv_transfer;
  Kwv_pair *kw;
  Kwv_vartab *err_vt, *vt, *id;
  Transfer_conf *t, *tt;

  kwv_conf.eval_func = conf_eval;
  kwv_conf.eval_arg = &eval_arg;

  for (t = transfer_conf; t != NULL; t = t->next) {
	eval_arg.env = env;
	eval_arg.prefix = strtoupper(ds_xprintf("TRANSFER.%s.", t->id));
	eval_arg.eval_eval_only = 0;
	if ((kwv_transfer = kwv_make_new(ds_buf(&t->string), &kwv_conf)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error processing Transfer section \"%s\"",
			   t->id));
	  return(-1);
	}
	vt = kwv_vartab_new(transfer_vartab);
	kwv_vartab_init(kwv_transfer, vt, conf_site, (void *) site);
	if (kwv_vartab_check(kwv_transfer, vt, &kw, &err_vt, &errmsg) == -1) {
	  if (err_vt != NULL)
		log_msg((LOG_ERROR_LEVEL,
				 "Config error, Transfer clause \"%s\": %s: %s",
				 t->id, errmsg, err_vt->name));
	  else if (kw != NULL)
		log_msg((LOG_ERROR_LEVEL,
				 "Config error, Transfer clause \"%s\": %s: %s",
				 t->id, errmsg, kw->name));
	  else
		log_msg((LOG_ERROR_LEVEL,
				 "Config error, Transfer clause \"%s\": %s",
				 t->id, errmsg));
	  return(-1);
	}

	/* XXX This is a kludge to retain the id tag */
	if ((id = kwv_vartab_lookup(vt, "TRANSFER_ID")) == NULL) {
	  log_msg((LOG_ALERT_LEVEL, "Config error: no TRANSFER_ID variable?"));
	  return(-1);
	}

	if (!is_valid_name(t->id)) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Config error: invalid AUTH_ID variable: \"%s\"", t->id));
	  return(-1);
	}

	for (tt = transfer_conf; tt != t; tt = tt->next) {
	  if (streq(tt->id, t->id)) {
		log_msg((LOG_ERROR_LEVEL,
				 "Config error: duplicate Transfer id tag '%s'", t->id));
		return(-1);
	  }
	}
	id->pair = kwv_new_pair("TRANSFER_ID", t->id, NULL);

	if (dsv != NULL)
	  dsvec_add_ptr(dsv, vt);
  }

  return(0);
}

DACS_conf *
new_dacs_conf(char *site_conf, char *dacs_conf, Jurisdiction_conf *j,
			  Kwv_vartab *vartab, Var_ns *var_ns)
{
  DACS_conf *dc;

  dc = ALLOC(DACS_conf);

  if (j != NULL) {
	dc->uri = strdup(j->uri);
	dc->uri_expr = (j->uri_expr != NULL) ? strdup(j->uri_expr) : NULL;
	dc->parsed_uri = j->parsed_uri;
  }
  else {
	dc->uri = NULL;
	dc->uri_expr = NULL;
	dc->parsed_uri = uri_init(NULL);
  }

  if (site_conf != NULL)
	dc->site_conf = strdup(site_conf);
  else
	dc->site_conf = NULL;
  if (dacs_conf != NULL)
	dc->dacs_conf = strdup(dacs_conf);
  else
	dc->dacs_conf = "";
  dc->conf_vartab = vartab;
  dc->auth_vartab = dsvec_init(NULL, sizeof(Kwv_vartab *));
  dc->roles_vartab = dsvec_init(NULL, sizeof(Kwv_vartab *));
  dc->transfer_vartab = dsvec_init(NULL, sizeof(Kwv_vartab *));
  dc->conf_var_ns = var_ns;
  dc->site = NULL;
  dc->default_kwv = NULL;
  dc->jurisdiction_kwv = NULL;

  return(dc);
}

/*
 * This function is the configuration driver.
 * Look at:
 *   1) An optional site configuration file (having only a Default section)
 *   2) A required jurisdiction configuration file, which must have a section
 *      for a specified jurisdiction and can have an optional Default section.
 * Default sections are used to assert default directives, with site
 * defaults being overridden by jurisdiction defaults, which are in turn
 * overridden by directives in the jurisdiction section.
 * Some directives are required, some are optional.  Some may only appear
 * once, others may be repeated.
 * Every directive's value is evaluated as an expression; most commonly
 * the value will be a string (something enclosed between double or single
 * quotes).
 * Return NULL if there's a problem, otherwise a structure that describes
 * the configuration in effect for this jurisdiction.
 *
 * The FIND argument is called to select the Jurisdiction section; it is
 * passed ARG and returns a partially initialized description of the
 * Jurisdiction section, or NULL if no section was found.
 *
 * XXX We might verify that the permissions on the
 * configuration file are reasonable and refuse to run if there's a problem.
 *
 * XXX I suppose the storage method for configuration files should be
 * configurable, but it seems reasonable
 * to at least bootstrap from a filesystem based config file.
 * Other storage methods will require some way for configuration information
 * to be provided so that they can be used.
 */
static DACS_conf *
process_conf(char *conf_file, char *site_conf_file, Kwv_vartab *vartab,
			 Jurisdiction_conf *(*find) (char *, Configuration *, char *),
			 char *arg)
{
  int got_site;
  char *buf, *errmsg, *j_id, *p, *site_conf_str;
  Acs_environment env;
  Auth_conf *auth_conf;
  Conf_eval_arg eval_arg;
  Configuration *conf;
  DACS_conf *dc;
  Jurisdiction_conf *j;
  Kwv *kwv, *kwv_eval, *default_kwv;
  Kwv_vartab *err_vt, *vt;
  Kwv_pair *kp, *kw;
  Roles_conf *roles_conf;
  Site *site;
  Transfer_conf *transfer_conf;
  Var_ns *conf_var_ns, *env_ns, *vn;

  /*
   * Get the jurisdiction configuration file and extract the default and
   * jurisdiction-specific directives.
   * Might as well do this first because if there's an error here, we
   * fail completely.
   */
  if (load_from_store("fs", conf_file, &buf, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not load config file '%s'", conf_file));
	return(NULL);
  }

  /*
   * Site configuration is optional.
   * We need to instantiate the Conf namespace; these variables may be
   * referenced in dacs.conf or the site configuration file.
   */
  site = ALLOC(Site);
  site->site_conf = NULL;
  site->kwv_site = NULL;

  site->kwv_conf = kwv_init(30);
  kwv_set_mode(site->kwv_conf, "+i-d");

  site->kwv_dacs = kwv_init(30);
  kwv_set_mode(site->kwv_dacs, "-i-d");

  kwv_merge(site->kwv_dacs, kwv_dacsoptions, KWV_REPLACE_DUPS);
  got_site = 0;

  if ((p = getenv("SERVER_NAME")) != NULL)
	kwv_add(site->kwv_conf, "SERVER_NAME", p);
  if ((p = getenv("SERVER_ADDR")) != NULL)
	kwv_add(site->kwv_conf, "SERVER_ADDR", p);
  if ((p = getenv("SERVER_PORT")) != NULL)
	kwv_add(site->kwv_conf, "SERVER_PORT", p);
  if ((p = getenv("HTTP_HOST")) != NULL)
	kwv_add(site->kwv_conf, "HTTP_HOST", p);
  if ((p = getenv("DOCUMENT_ROOT")) != NULL)
	kwv_add(site->kwv_conf, "DOCUMENT_ROOT", p);
  if ((p = getenv("HTTPS")) != NULL && strcaseeq(p, "on"))
	kwv_add(site->kwv_conf, "URI_SCHEME", "https");
  else
	kwv_add(site->kwv_conf, "URI_SCHEME", "http");
  kwv_add(site->kwv_conf, "DACS_HOME", DACS_HOME);
  kwv_add(site->kwv_conf, "DACS_CGIBINDIR", CGIBINDIR);
  kwv_add(site->kwv_conf, "DACS_BIN", DACS_BINDIR);
  kwv_add(site->kwv_conf, "DACS_SBIN", DACS_SBIN);
  kwv_add(site->kwv_conf, "APACHE_HOME", APACHE_HOME);
#ifdef OPENSSL_PROG
  kwv_add(site->kwv_conf, "OPENSSL_PROG", OPENSSL_PROG);
#endif
  kwv_add(site->kwv_conf, "FEDERATIONS_ROOT", FEDERATIONS_ROOT);
  kwv_add(site->kwv_conf, "DACS_CONF", conf_file);
  kwv_add(site->kwv_conf, "CGI_SUFFIX", CGI_SUFFIX);
  kwv_add(site->kwv_conf, "EXE_SUFFIX", EXE_SUFFIX);
  if (dacs_conf_path_spec != NULL)
	kwv_add(site->kwv_conf, "DACS_CONF_SPEC", dacs_conf_path_spec);
  if (dacs_site_conf_path_spec != NULL)
	kwv_add(site->kwv_conf, "DACS_SITE_CONF_SPEC", dacs_site_conf_path_spec);

  kwv_add(site->kwv_conf, "DACS_RELEASE", DACS_VERSION_RELEASE);
  kwv_add(site->kwv_conf, "DACS_VERSION", DACS_VERSION_NUMBER);

  /*
   * XXX We'd really like the DACS namespace to be available here,
   * but dacs_acs() and therefore acs_init_env() are called much later
   * than this function.  Also, if we're not running as a web service
   * then there's no request from which to initialize the DACS namespace.
   * For now, we can cheat by looking in the environment.
   */
  if ((p = getenv("HTTP_USER_AGENT")) != NULL)
	kwv_add(site->kwv_dacs, "USER_AGENT", p);
  else
	kwv_add(site->kwv_dacs, "USER_AGENT", "unknown");
  if ((p = getenv("REMOTE_ADDR")) != NULL)
	kwv_add(site->kwv_dacs, "REMOTE_ADDR", p);
  if ((p = getenv("REMOTE_HOST")) != NULL)
	kwv_add(site->kwv_dacs, "REMOTE_HOST", p);
  if ((p = getenv("SCRIPT_FILENAME")) != NULL)
	kwv_add(site->kwv_dacs, "FILENAME", p);
  if (isatty(0))
	kwv_add(site->kwv_dacs, "INTERACTIVE", "1");

  acs_new_env(&env);
  conf_var_ns = var_ns_new(&env.namespaces, "Conf", site->kwv_conf);
  if (dacs_kwv_args != NULL)
	var_ns_new(&env.namespaces, "Args", dacs_kwv_args);
  var_ns_new(&env.namespaces, "DACS", site->kwv_dacs);
  env.trace_level = 0;

  if ((env_ns = var_ns_from_env("Env")) != NULL) {
	var_ns_add(&env.namespaces, env_ns);
	log_msg((LOG_TRACE_LEVEL, "Initialized Env namespace:\n%s",
			 kwv_buf(env_ns->kwv, '=', '"')));
  }

  if (parse_xml_conf(buf, &conf) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Parse of config file '%s' failed", conf_file));
	return(NULL);
  }
  else {
	Jurisdiction_conf *cp;
	Acs_expr_result st;
	Expr_result result;

	for (cp = conf->jurisdiction_conf_head; cp != NULL; cp = cp->next) {
	  if (cp->uri_expr != NULL) {
		st = acs_expr(cp->uri_expr, &env, &result);
		if (st != ACS_EXPR_TRUE) {
		  log_msg((LOG_ERROR_LEVEL, "Error evaluating uri_expr: \"%s\"",
				   cp->uri_expr));
		  return(NULL);
		}

		cp->uri = acs_format_result(&result);
		cp->parsed_uri = uri_init(NULL);
		if (uri_parse_hier_part(cp->parsed_uri, cp->uri, 1) == NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Evaluation of uri_expr yields invalid uri: \"%s\"",
				   cp->uri_expr));
		  return(NULL);
		}
	  }
	}
  }

  if ((j = find(conf_file, conf, arg)) == NULL)
	return(NULL);
  free(buf);

  kwv_add(site->kwv_conf, "JURISDICTION_URI_PREFIX", j->uri);
  kwv_add(site->kwv_conf, "JURISDICTION_URI", dacs_effective_service_uri);

  /* Process the site configuration, if it exists. */
  if (site_conf_file != NULL && file_exists(site_conf_file)) {
	if (load_from_store("fs", site_conf_file, &site_conf_str, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not load site config file '%s'",
			   site_conf_file));
	  return(NULL);
	}

	kwv_add(site->kwv_conf, "DACS_SITE_CONF", site_conf_file);
	if (parse_xml_conf(site_conf_str, &site->site_conf) == -1)
	  return(NULL);
	if (site->site_conf->jurisdiction_conf_head != NULL)
	  return(NULL);

	/*
	 * Collect the directives in site.conf but do not evaluate the RHS of
	 * any directive other than an EVAL directive.
	 */
	eval_arg.env = &env;
	eval_arg.prefix = "";
	eval_arg.eval_eval_only = 1;
	kwv_conf.eval_func = conf_eval;
	kwv_conf.eval_arg = &eval_arg;
	site->kwv_site
	  = kwv_make_new(ds_buf(&site->site_conf->default_conf_string), &kwv_conf);

	if (site->kwv_site == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error processing Site defaults"));
	  return(NULL);
	}
	log_msg((LOG_INFO_LEVEL, "Site config file is \"%s\"", site_conf_file));
	got_site = 1;
  }

  /* If dacs.conf has a Default section, process it now. */
  if (ds_len(&conf->default_conf_string) != 0) {
	/*
	 * Collect the directives in the Default section but do not evaluate the
	 * RHS of any directive other than an EVAL directive.
	 */
	eval_arg.env = &env;
	eval_arg.prefix = "";
	eval_arg.eval_eval_only = 1;
	kwv_conf.eval_func = conf_eval;
	kwv_conf.eval_arg = &eval_arg;
	default_kwv = kwv_make_new(ds_buf(&conf->default_conf_string), &kwv_conf);
	if (default_kwv == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error processing Default section"));
	  return(NULL);
	}
  }
  else
	default_kwv = NULL;

  /* Process the applicable Jurisdiction section in dacs.conf */
  j_id = ds_xprintf("%s=\"%s\"",
					(j->uri != NULL) ? "uri" : "uri_expr",
					(j->uri != NULL) ? j->uri : j->uri_expr);

  eval_arg.env = &env;
  eval_arg.prefix = "";
  eval_arg.eval_eval_only = 1;
  kwv_conf.eval_func = conf_eval;
  kwv_conf.eval_arg = &eval_arg;
  if ((kwv = kwv_make_new(ds_buf(&j->string), &kwv_conf)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "In Jurisdiction section %s, error processing %s",
			 j_id, arg));
	return(NULL);
  }

  /*
   * Merge directives in the three sections, with the Jurisdiction section
   * overriding the Default section, and the Default section overriding the
   * site configuration.
   */
  kwv_eval = kwv_init(10);
  kwv_eval->dup_mode = KWV_ALLOW_DUPS;
  if (site->kwv_site != NULL
	  && (kp = kwv_lookup(site->kwv_site, "EVAL")) != NULL)
	kwv_add_pair(kwv_eval, kp);
  if (default_kwv != NULL
	  && (kp = kwv_lookup(default_kwv, "EVAL")) != NULL)
	kwv_add_pair(kwv_eval, kp);
  if ((kp = kwv_lookup(kwv, "EVAL")) != NULL)
	kwv_add_pair(kwv_eval, kp);
  if ((vt = kwv_vartab_lookup(conf_vartab, "EVAL")) != NULL)
	vt->pair = kwv_lookup(kwv_eval, "EVAL");
  kwv_replace_pair(kwv, vt->pair);

  if (default_kwv != NULL) {
	/*
	 * Merge the Jurisdiction section and its Default section, with the
	 * former taking precedence.
	 */
	if (conf_merge(kwv, default_kwv, conf_vartab, &eval_arg) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "In Jurisdiction section %s, error merging defaults", j_id));
	  return(NULL);
	}
  }

  if (site->kwv_site != NULL) {
	/*
	 * Merge the merged Jurisdiction/Default section and the Site config,
	 * with the former taking precedence.
	 */
	if (conf_merge(kwv, site->kwv_site, conf_vartab, &eval_arg) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "In Jurisdiction section %s, error merging site defaults",
			   j_id));
	  return(NULL);
	}
  }

  /*
   * Now we know which directives are in effect: those in KWV.
   * Run some general checks on them.
   */
  if (vartab != NULL) {
	kwv_vartab_init(kwv, vartab, conf_site, (void *) site);
	if (dacs_app_type != DACS_LOCAL_SERVICE
		&& kwv_vartab_check(kwv, vartab, &kw, &err_vt, &errmsg) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "In Jurisdiction section %s:", j_id));
	  if (err_vt != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error: %s: %s",
				 errmsg, err_vt->name));
	  else if (kw != NULL)
		log_msg((LOG_ERROR_LEVEL, "Config error: %s: %s", errmsg, kw->name));
	  else
		log_msg((LOG_ERROR_LEVEL, "Config error: %s", errmsg));
	  return(NULL);
	}
  }

  if ((kwv = conf_resolve(kwv, site, conf_vartab, &eval_arg)) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "In Jurisdiction section %s, failed to resolve merged config",
			 j_id));
	return(NULL);
  }

  site->kwv_conf = kwv;
  vn = var_ns_lookup(env.namespaces, "Conf"); 
  vn->kwv = kwv;

  dc = new_dacs_conf(got_site ? site_conf_file : NULL, conf_file, j,
					 conf_vartab, conf_var_ns);

  dc->default_kwv = default_kwv;
  dc->jurisdiction_kwv = kwv;
  if (got_site)
	dc->site = site;

  kwv_add(site->kwv_conf, "JURISDICTION_URI", dc->uri);

  /*
   * Process the Auth sections in effect.
   * It's all or nothing - all Auth sections must come from the same
   * source according to the priority of the source; either Jurisdiction only,
   * or Default only, or Site only.
   */
  if ((auth_conf = j->auth_conf) == NULL) {
	if (conf->default_auth_conf != NULL) {
	  if (process_auth_sections(conf->default_auth_conf, &env,
								site, dc->auth_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Default Jurisdiction section %s, invalid Auth section",
				 j_id));
		return(NULL);
	  }
	}
	else if (site->site_conf != NULL
			 && site->site_conf->default_auth_conf != NULL) {
	  if (process_auth_sections(site->site_conf->default_auth_conf, &env,
								site, dc->auth_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Site Jurisdiction section %s, invalid Default section",
				 j_id));
		return(NULL);
	  }
	}
  }
  else {
	if (conf->default_auth_conf != NULL) {
	  /* Check for errors but otherwise ignore. */
	  if (process_auth_sections(conf->default_auth_conf, &env,
								site, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Default Jurisdiction section %s, invalid Auth config",
				 j_id));
		return(NULL);
	  }
	}
	if (process_auth_sections(auth_conf, &env, site, dc->auth_vartab) == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "In Jurisdiction section %s, invalid Auth config", j_id));
	  return(NULL);
	}
  }

  /*
   * Process the Roles sections in effect.
   * The procedure is the same as for the Auth sections.
   */
  if ((roles_conf = j->roles_conf) == NULL) {
	if (conf->default_roles_conf != NULL) {
	  if (process_roles_sections(conf->default_roles_conf, &env,
								site, dc->roles_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Default section", j_id));
		return(NULL);
	  }
	}
	else if (site->site_conf != NULL
			 && site->site_conf->default_roles_conf != NULL) {
	  if (process_roles_sections(site->site_conf->default_roles_conf, &env,
								 site, dc->roles_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Site Default section",
				 j_id));
		return(NULL);
	  }
	}
  }
  else {
	if (conf->default_roles_conf != NULL) {
	  /* Check for errors but otherwise ignore. */
	  if (process_roles_sections(conf->default_roles_conf, &env,
								site, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Roles config in Default section",
				 j_id));
		return(NULL);
	  }
	}
	if (process_roles_sections(roles_conf, &env, site, dc->roles_vartab)
		== -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "In Jurisdiction section %s, invalid Roles config in Jurisdiction section",
			   j_id));
	  return(NULL);
	}
  }

  /*
   * Process the Transfer sections in effect.
   * The procedure is the same as for the Auth sections.
   */
  if ((transfer_conf = j->transfer_conf) == NULL) {
	if (conf->default_transfer_conf != NULL) {
	  if (process_transfer_sections(conf->default_transfer_conf, &env,
									site, dc->transfer_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Default section",
				 j_id));
		return(NULL);
	  }
	}
	else if (site->site_conf != NULL
			 && site->site_conf->default_transfer_conf != NULL) {
	  if (process_transfer_sections(site->site_conf->default_transfer_conf,
									&env, site, dc->transfer_vartab) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Site Default section",
				 j_id));
		return(NULL);
	  }
	}
  }
  else {
	if (conf->default_transfer_conf != NULL) {
	  /* Check for errors but otherwise ignore. */
	  if (process_transfer_sections(conf->default_transfer_conf, &env,
								site, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL,
				 "In Jurisdiction section %s, invalid Transfer config in Default section",
				 j_id));
		return(NULL);
	  }
	}
	if (process_transfer_sections(transfer_conf, &env, site,
								  dc->transfer_vartab) == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "In Jurisdiction section %s, invalid Transfer config in Jurisdiction section",
			   j_id));
	  return(NULL);
	}
  }

  /* Disallow further changes to the Conf namespace. */
  var_ns_set_flags(env.namespaces, "Conf", VAR_NS_READONLY);

  {
	char *str;
	Kwv *k;
	Kwv_iter *iter;
	Kwv_pair *pair;

	iter = kwv_iter_begin(kwv, NULL);
	while ((pair = kwv_iter_next(iter)) != NULL)
	  log_msg((LOG_TRACE_LEVEL, "%s=%s", pair->name, pair->val));
	kwv_iter_end(iter);

	k = var_ns_lookup_kwv(env.namespaces, "Conf");
	str = kwv_buf(k, 0, '\'');
	log_msg((LOG_TRACE_LEVEL, "%s", str));
  }

  return(dc);
}

#ifdef NOTDEF
static char *conf_app_uri = NULL;

void
conf_set_app_uri(char *uri)
{

  conf_app_uri = uri;
}

char *
conf_get_app_uri(void)
{

  return(conf_app_uri);
}
#endif

/*
 * This is called to initialize configuration by locating the applicable
 * Jurisdiction section by matching a JURISDICTION_NAME directive within it.
 * JURISDICTION can be NULL; see find_conf_by_jurisdiction().
 */
DACS_conf *
conf_init_by_jurisdiction(char *conf_file, char *site_conf_file,
						  char *jurisdiction, Kwv_vartab *vartab)
{
  DACS_conf *dc;

  dc = process_conf(conf_file, site_conf_file, vartab,
					find_conf_by_jurisdiction, jurisdiction);
  return(dc);
}

/*
 * This is called to initialize configuration from a Jurisdiction section
 * having a uri attribute that best matches SERVICE_URI, which comes from
 * the requested resource.
 */
DACS_conf *
conf_init(char *conf_file, char *site_conf_file,
		  char *service_uri, Kwv_vartab *vartab)
{
  DACS_conf *dc;

  if (service_uri == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No service_uri provided"));
	return(NULL);
  }

  dc = process_conf(conf_file, site_conf_file, vartab,
					find_conf_by_uri, service_uri);

  return(dc);
}

Kwv_vartab *
conf_auth_vartab_by_id(char *auth_id)
{
  int i;
  Kwv_vartab *found, *id, *tab;

  i = 0;
  found = NULL;
  while ((tab = conf_auth_vartab(i++)) != NULL) {
	if ((id = kwv_vartab_lookup(tab, "AUTH_ID")) != NULL
		&& id->pair != NULL && id->pair->val != NULL
		&& streq(id->pair->val, auth_id)) {
	  /* Make sure it's unique. */
	  if (found != NULL)
		return(NULL);
	  found = tab;
	}
  }

  return(found);
}

Kwv_vartab *
conf_roles_vartab_by_id(char *roles_id)
{
  int i;
  Kwv_vartab *found, *id, *tab;

  i = 0;
  found = NULL;
  while ((tab = conf_roles_vartab(i++)) != NULL) {
	if ((id = kwv_vartab_lookup(tab, "ROLES_ID")) != NULL
		&& id->pair != NULL && id->pair->val != NULL
		&& streq(id->pair->val, roles_id)) {
	  /* Make sure it's unique. */
	  if (found != NULL)
		return(NULL);
	  found = tab;
	}
  }

  return(found);
}

int
conf_set_directive(Kwv *kwv_conf, char *directive, char *value)
{
  Kwv_vartab *v;
  Kwv_pair *pair;

  if (dacs_conf == NULL) {
	DACS_conf *dc;

	dacs_conf = dc = ALLOC(DACS_conf);
	dc->conf_vartab = conf_vartab;
	dc->uri_expr = NULL;
	dc->uri = NULL;
	dc->parsed_uri = NULL;
	dc->site_conf = NULL;
	dc->dacs_conf = NULL;
	dc->auth_vartab = NULL;
	dc->roles_vartab = NULL;
	dc->conf_var_ns = NULL;
  }

  if ((v = kwv_vartab_lookup(dacs_conf->conf_vartab, directive)) == NULL)
	return(-1);

  if ((pair = v->pair) == NULL
	  || (v->type != KWV_STACK && v->type != KWV_SCAN)) {
	v->pair = kwv_new_pair(directive, value, NULL);
	kwv_replace(kwv_conf, directive, value);

	return(0);
  }

  pair = kwv_new_pair(directive, value, NULL);
  pair->next = v->pair;
  v->pair = pair;

  kwv_set_mode(kwv_conf, "da");
  kwv_add(kwv_conf, directive, value);
  kwv_set_mode(kwv_conf, "dn");

  return(0);
}

#ifdef NOTDEF
/*
 * Given a POSIX regular expression REGEX_TEXT, match it against TEXT.
 * Return the number of matches or -1 on error.
 * If REGEX_TEXT notes any substrings, they are assigned to MATCHES.
 */
static int
map_match_text(char *text, char *regex_text, Kwv *matches)
{
  int i, st;
  regex_t preg;
  regmatch_t pmatch[10];
  char errbuf[100];

  if ((st = regcomp(&preg, regex_text, REG_EXTENDED)) != 0)
	goto fail;

  if ((st = regexec(&preg, text, 10, pmatch, 0)) != 0) {
	if (st != REG_NOMATCH)
	  goto fail;
	return(0);
  }

  for (i = 0; i < 10; i++) {
	char *varname;
	Ds varvalue;

	if (pmatch[i].rm_so != -1 && pmatch[i].rm_eo != -1) {
	  ds_init(&varvalue);
	  varname = ds_xprintf("%d", i);
	  ds_concatn(&varvalue, text + pmatch[i].rm_so,
				 pmatch[i].rm_eo - pmatch[i].rm_so);
	  kwv_add(matches, varname, varvalue.buf);
	  ds_free(&varvalue);
	}
  }

  regfree(&preg);
  return(0);

 fail:
  log_msg((LOG_ERROR_LEVEL,
		   "map_match_text: bad regular expression: %s", regex_text));
  errbuf[0] = '\0';
  regerror(st, &preg, errbuf, sizeof(errbuf));
  log_msg((LOG_ERROR_LEVEL, "map_match_text: %s", errbuf));
  regfree(&preg);

  return(-1);
}

/*
 * Create and return a new string by substituting references to variables in
 * TEMPLATE with values found in KWV and/or MATCHES (either of which may
 * be NULL if they have no variables).
 * A variable is denoted by the syntax ${varname}.
 * Return NULL if there is an error.
 * Notes:
 * In TEMPLATE, any character may be escaped by preceding it with a backslash.
 * Substitution is not recursive.
 * It is an error to end TEMPLATE with an unescaped backslash or to reference
 * an undefined variable.
 */
static char *
map_make_text(char *template, Kwv *kwv, Kwv *matches)
{
  char *p, *val;
  Ds *ds, *var;

  ds = ds_init(NULL);
  var = ds_init(NULL);
  p = template;

  while (*p != '\0') {
	if (*p == '$' && *(p + 1) == '{') {
	  /* Extract the variable name... */
	  ds_reinit(var);
	  p += 2;
	  while (*p != '}') {
		if (*p == '\\') {
		  p++;
		  if (*p == '\0')
			return(NULL);
		}
		ds_appendc(var, (int) *p);
		p++;
	  }
	  ds_appendc(var, '\0');

	  /* get its value... */
	  if (kwv != NULL
		  && (val = kwv_lookup_value(kwv, ds_buf(var))) != NULL) {
		;
	  }
	  else if (matches != NULL
			   && (val = kwv_lookup_value(matches, ds_buf(var))) != NULL) {
		;
	  }
	  else
		return(NULL);

	  /* and finally interpolate. */
	  ds_append(ds, val);
	}
	else if (*p == '\\') {
	  p++;
	  if (*p == '\0')
		return(NULL);
	  ds_appendc(ds, (int) *p);
	}
	else
	  ds_appendc(ds, (int) *p);
	p++;
  }

  ds_appendc(ds, '\0');
  ds_free(var);
  return(ds_buf(ds));
}

/*
 * The original syntax is just one string argument (the target URL).
 * The newer syntax also recognizes:
 *    map [-r <string> <regex>] <target-URL> [arg ...]
 * The <target-URL> must have the prefix "http:" or "https:".
 * ARGS is set to point to the first argument following the <target-URL>.
 * Return the mapped string or NULL on error.
 */
char *
conf_map_directive(char *directive, Kwv *kwv, char ***args)
{
  int n;
  char **argv, *match_text, *target;
  Kwv *matches;

  if (directive == NULL)
	return(NULL);
  *args = NULL;

  n = mkargv(directive, NULL, &argv);

  if (n < 1)
	return(NULL);
  if (n == 1)
	return(argv[0]);

  if (!streq(argv[0], "map"))
	return(NULL);

  if (!streq(argv[1], "-r")) {
	if ((target = map_make_text(argv[1], kwv, NULL)) != NULL) {
	  if (!strneq(target, "http:", 5) && !strneq(target, "https:", 6)) {
		log_msg((LOG_ERROR_LEVEL, "conf_map_directive: bad target URL: %s",
				 directive));
		return(NULL);
	  }
	  else {
		if (argv[2] != NULL)
		  *args = &argv[2];
	  }
	}
	return(target);
  }

  if (n < 5)
	return(NULL);

  match_text = map_make_text(argv[2], kwv, NULL);
  matches = kwv_init(10);
  if (map_match_text(match_text, argv[3], matches) == -1)
	return(NULL);

  if ((target = map_make_text(argv[4], kwv, matches)) != NULL) {
	if (!strneq(target, "http", 4)) {
	  log_msg((LOG_ERROR_LEVEL, "conf_map_directive: bad target URL: %s",
			   directive));
	  return(NULL);
	}
	else {
	  if (argv[5] != NULL)
		*args = &argv[5];
	}
  }

  return(target);
}
#endif


static int
conf_name_compar(const void *ap, const void *bp)
{
  Kwv_pair *a, *b;

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

  return(strcmp(a->name, b->name));
}

/*
 * Sort the configuration directives in VARTAB, maintaining the order
 * of EVAL directives since they are the only ones whose order is significant.
 */
Dsvec *
conf_sort_directives(Kwv_vartab *vartab)
{
  Dsvec *dsv, *dsv_eval;
  Kwv_pair *pair;
  Kwv_vartab *vt;

  dsv = dsvec_init(NULL, sizeof(Kwv *));
  for (vt = dacs_conf->conf_vartab; vt->name != NULL; vt++) {
	for (pair = vt->pair; pair != NULL; pair = pair->next) {
	  if (streq(pair->name, "EVAL"))
		break;
	  dsvec_add_ptr(dsv, pair);
	}
  }

  dsvec_sort(dsv, conf_name_compar);

  dsv_eval = dsvec_init(NULL, sizeof(Kwv *));
  if ((vt = kwv_vartab_lookup(vartab, "EVAL")) != NULL) {
	for (pair = vt->pair; pair != NULL; pair = pair->next)
	  dsvec_add_ptr(dsv_eval, pair);
  }

  dsvec_replace(dsv, 0, 0, dsv_eval);

  return(dsv);
}

int
conf_is_predefined_item_type(char *item_type)
{
  int i;

  for (i = 0; predefined_item_types[i] != NULL; i++) {
	if (streq(item_type, predefined_item_types[i]))
	  return(1);
  }

  /* A special case... */
  if (strprefix(item_type, ITEM_TYPE_AUTH_AGENT_FEDERATION_PREFIX) != NULL)
	return(1);

  return(0);
}

#ifdef NOTDEF
typedef struct Item_type_map {
  char *from_name;
  char *to_val;
  char *to_name;
  Vfs_directive *vd;
} Item_type_map;

static Item_type_map *
parse_vfs_item_type(char *val)
{
  char *p;
  Item_type_map *itm;

  if ((p = strchr(val, (int) ' ')) == NULL
	  && (p = strchr(val, (int) '\t')) == NULL)
	return(NULL);
  
  itm = ALLOC(Item_type_map);
  itm->from_name = strndup(val, p - val);
  while (*p == ' ' || *p == '\t')
	p++;
  itm->to_val = strdup(p);
  itm->to_name = NULL;
  itm->vd = NULL;

  return(itm);
}

static char *
conf_vfs_item_type_eval(Conf_context cc, char *old_name, char *expr)
{

  return(NULL);
}

/*
 * Lookup a directive in the configuration context CC that asks
 * to remap item_type OLD_NAME to another item_type name.
 * There are two such directives: VFS_ITEM_TYPE and VFS_ITEM_TYPE*.
 * Both require a value consisting of two space-separated parts: the name of
 * a predefined item_type and an item_type name to which the predefined
 * item_type name should map.
 * The latter must be defined by a corresponding VFS directive.
 * The predefined item_type must not already be used by this directive
 * in this configuration context.
 * If there is a valid mapping, return the new name, otherwise return NULL.
 */
Item_type_map *
conf_vfs_item_type(Conf_context cc, char *old_name)
{
  Item_type_map *current_itm, *itm;
  Kwv_pair *v;

  current_itm = NULL;
  if (cc == CONF_CONTEXT_GLOBAL) {
	for (v = conf_var(CONF_VFS_ITEM_TYPE); v != NULL; v = v->next) {
fprintf(stderr, "\"%s\" -> \"%s\"\n", v->name, v->val);
	  if ((itm = parse_vfs_item_type(v->val)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid VFS_ITEM_TYPE: \"%s\"", v->val));
		return(NULL);
	  }
	  if (streq(itm->from_name, itm->to_val)) {
		log_msg((LOG_ERROR_LEVEL, "Invalid VFS_ITEM_TYPE target: \"%s\"",
				 itm->to_val));
		return(NULL);
	  }
	  if (!conf_is_predefined_item_type(itm->from_name)) {
		log_msg((LOG_ERROR_LEVEL,
				 "VFS_ITEM_TYPE was not given a predefined item_type: \"%s\"",
				 itm->from_name));
		return(NULL);
	  }

	  fprintf(stderr, "\"%s\" -> \"%s\"\n", itm->from_name, itm->to_val);
	  if (current_itm == NULL && streq(old_name, itm->from_name)) {
		if ((itm->vd = vfs_lookup_item_type(itm->to_val)) == NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "VFS_ITEM_TYPE \"%s\" is undefined", itm->to_val));
		  return(NULL);
		}
		current_itm = itm;
	  }
	  else if (current_itm != NULL
			   && streq(current_itm->from_name, itm->from_name)) {
		log_msg((LOG_ERROR_LEVEL,
				 "Duplicate VFS_ITEM_TYPE directive for \"%s\"",
				 itm->from_name));
		return(NULL);
	  }
	}
	if (current_itm != NULL)
	  current_itm->to_name = current_itm->to_val;
  }
  else if (cc == CONF_CONTEXT_AUTH) {
  }
  else if (cc == CONF_CONTEXT_ROLES) {
  }
  else if (cc == CONF_CONTEXT_TRANSFER) {
  }

  return(current_itm);
}
#endif

/*
 * Command line tool and DACS web service.
 *
 * NB: access to this tool/web service should probably be restricted so
 * as not to reveal information that might be helpful to compromise the system
 *
 * This program is useful for scripts, debugging, etc. for accessing
 * DACS run-time configuration information for a jurisdiction.
 * As a command, the required arguments are the jurisdiction's URI and the
 * location of the configuration file.
 * An optional output format specifier may follow.
 * Each remaining argument is the name of a configuration directive;
 * if none are given, then all defined directives are dumped.
 * For each given configuration directive that is defined, its value is
 * printed.  For multi-valued directives, all values are printed.
 * Each directive is printed to stdout, one per line, with quotes around
 * the value.
 *
 * As a web service, FORMAT selects html/xml/plain/vars, and zero or more
 * DIRECTIVE parameters select directives to be listed.
 */

#include "dacs.h"

static int emit_vars = 0;

static void
emit_directive(FILE *fp, char *prefix, Kwv_pair *pair)
{
  char *name;

  if (emit_vars) {
	/*
	name = ds_xprintf("%s%s", strtoupper(prefix), strtoupper(pair->name));
	*/
	name = ds_xprintf("${Conf::%s}", pair->name);
  }
  else
	name = pair->name;

  if (test_emit_format(EMIT_FORMAT_HTML)) {
	fprintf(fp, "<span class=\"directive\">");
	fprintf(fp, "<span class=\"directive_name\">%s</span>", name);
	fprintf(fp, "%s", (emit_vars || *name == '$') ? "=" : " ");
	fprintf(fp, "<span class=\"directive_value\">\"%s\"</span>",
			html_encode(strquote(pair->val, "\"")));
	fprintf(fp, "</span><br/>\n");
  }
  else if (test_emit_xml_format())
	fprintf(fp, "<directive name=\"%s\" value=\"%s\"/>\n",
			name, xml_attribute_value_encode(pair->val, '\"'));
  else
	fprintf(fp, "%s \"%s\"\n", name, pair->val);
}

/*
 * If DNAME is non-NULL, only display that directive or variable, otherwise
 * display all directives or variables.
 */
static void
show(FILE *fp, char *dname)
{
  int i, seen;
  Kwv_pair *pair;
  Kwv_vartab *id, *tab, *vt;

  if (!emit_vars) {
	if (test_emit_format(EMIT_FORMAT_PLAIN)
		|| (test_emit_format(EMIT_FORMAT_TEXT) && dname == NULL)) {
	  if (dacs_conf->uri_expr != NULL)
		fprintf(fp,
				"<Jurisdiction uri=\"%s\" uri_expr=\"%s\">\n",
				xml_attribute_value_encode(dacs_conf->uri, '\"'),
				xml_attribute_value_encode(dacs_conf->uri_expr, '\"'));
	  else
		fprintf(fp, "<Jurisdiction uri=\"%s\">\n",
				xml_attribute_value_encode(dacs_conf->uri, '\"'));
	}
  }
  else {
	Kwv *kwv;
	Kwv_iter *iter;
	Kwv_pair *v;

	kwv = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
	iter = kwv_iter_begin(kwv, NULL);
	for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter)) {
	  emit_directive(fp, "", v);
	}
	kwv_iter_end(iter);

	return;
  }

  if (dname != NULL && *dname == '$' && *(dname + 1) == '{') {
	char *endp;
	Acs_environment *env;
	Ds *ds;
	Kwv_pair xpair;

	env = acs_new_env(NULL);
	env->namespaces = dacs_conf->conf_var_ns;
	if ((ds = var_value(dname, NULL, &endp, acs_variable_resolve, env))
		!= NULL) {
	  xpair.name = dname;
	  xpair.val = ds_buf(ds);
	  emit_directive(fp, "", &xpair);
	}

	return;
  }

  for (vt = dacs_conf->conf_vartab; vt->name != NULL; vt++) {
	if (dname != NULL && vt->pair != NULL
		&& !strcaseeq(vt->pair->name, dname))
	  continue;

	for (pair = vt->pair; pair != NULL; pair = pair->next)
	  emit_directive(fp, "", pair);
  }

  i = 0;
  while ((tab = conf_auth_vartab(i++)) != NULL) {
	id = kwv_vartab_lookup(tab, "AUTH_ID");
	seen = 0;
	for (vt = tab; vt->name != NULL; vt++) {
	  if (vt->pair == NULL)
		continue;
	  if (dname != NULL && !strcaseeq(vt->pair->name, dname))
		continue;

	  if (!seen && !emit_vars) {
		if (test_emit_format(EMIT_FORMAT_HTML)) {
		  printf("\n<div class=\"auth_section\">\n");
		  printf("<span class=\"auth_begin\">&lt;Auth id=\"%s\"&gt;</span>\n",
				 id->pair->val);
		  printf("<br/>\n");
		}
		else
		  printf("<Auth id=\"%s\">\n", id->pair->val);
		seen = 1;
	  }

	  for (pair = vt->pair; pair != NULL; pair = pair->next) {
		if (streq(pair->name, "AUTH_ID"))
		  continue;

		emit_directive(fp, ds_xprintf("AUTH.%s.", id->pair->val), pair);
	  }
	}

	if (seen && !emit_vars) {
	  if (test_emit_format(EMIT_FORMAT_HTML))
		printf("<span class=\"auth_end\">&lt;/Auth&gt;<br/></span></div>\n");
	  else
		printf("</Auth>\n");
	}
  }

  i = 0;
  while ((tab = conf_roles_vartab(i++)) != NULL) {
	id = kwv_vartab_lookup(tab, "ROLES_ID");
	seen = 0;
	for (vt = tab; vt->name != NULL; vt++) {
	  if (vt->pair == NULL)
		continue;
	  if (dname != NULL && !strcaseeq(vt->pair->name, dname))
		continue;
	  if (!seen && !emit_vars) {
		if (test_emit_format(EMIT_FORMAT_HTML)) {
		  printf("\n<div class=\"roles_section\">\n");
		  printf("<span class=\"roles_begin\">&lt;Roles id=\"%s\"&gt;</span><br/>\n",
				 id->pair->val);
		}
		else
		  printf("<Roles id=\"%s\">\n", id->pair->val);
		seen = 1;
	  }

	  for (pair = vt->pair; pair != NULL; pair = pair->next) {
		if (streq(pair->name, "ROLES_ID"))
		  continue;

		emit_directive(fp, ds_xprintf("ROLES.%s.", id->pair->val), pair);
	  }
	}

	if (seen && !emit_vars) {
	  if (test_emit_format(EMIT_FORMAT_HTML))
		printf("<span class=\"roles_end\">&lt;/Roles&gt;<br/></span></div>\n");
	  else
		printf("</Roles>\n");
	}
  }

  i = 0;
  while ((tab = conf_transfer_vartab(i++)) != NULL) {
	id = kwv_vartab_lookup(tab, "TRANSFER_ID");
	seen = 0;
	for (vt = tab; vt->name != NULL; vt++) {
	  if (vt->pair == NULL)
		continue;
	  if (dname != NULL && !strcaseeq(vt->pair->name, dname))
		continue;
	  if (!seen && !emit_vars) {
		if (test_emit_format(EMIT_FORMAT_HTML)) {
		  printf("\n<div class=\"transfer_section\">\n");
		  printf("<span class=\"transfer_begin\">&lt;Transfer id=\"%s\"&gt;</span><br/>\n",
				 id->pair->val);
		}
		else
		  printf("<Transfer id=\"%s\">\n", id->pair->val);
		seen = 1;
	  }

	  for (pair = vt->pair; pair != NULL; pair = pair->next) {
		if (streq(pair->name, "TRANSFER_ID"))
		  continue;

		emit_directive(fp, ds_xprintf("TRANSFER.%s.", id->pair->val), pair);
	  }
	}

	if (seen && !emit_vars) {
	  if (test_emit_format(EMIT_FORMAT_HTML))
		printf("<span class=\"transfer_end\">&lt;/Transfer&gt;<br/></span></div>\n");
	  else
		printf("</Transfer>\n");
	}
  }

  if (!emit_vars) {
	if (test_emit_format(EMIT_FORMAT_PLAIN)
		|| (test_emit_format(EMIT_FORMAT_TEXT) && dname == NULL))
	  fprintf(fp, "</Jurisdiction>\n");
  }
}

static void
show_usage(void)
{

  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "dacsconf [dacsoptions] [-vars] [directive-name ...]\n");
  fprintf(stderr, "dacsconf -item_types\n");
  fprintf(stderr, "dacsoptions: %s\n", standard_command_line_usage);

  exit(1);
}

int
conf_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, saw_directives;
  char *directive, *errmsg, *p;
  Common_status common_status;
  DACS_app_type app_type;
  Html_header_conf *hc;
  Kwv *kwv;

  errmsg = "Internal error";
  emit_vars = 0;
  saw_directives = 0;
  hc = emit_html_header_conf(NULL);

  if (getenv("REMOTE_ADDR") == NULL) {
	app_type = DACS_UTILITY;
    log_module_name = "dacsconf";
  }
  else {
	app_type = DACS_WEB_SERVICE;
    log_module_name = "dacs_conf";
  }

  dacs_init_allow_dups_default = 1;
  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (app_type == DACS_UTILITY && !test_emit_xml_format()) {
	  fprintf(stderr, "%s\n", errmsg);
	  show_usage();
	  return(-1);
	}

	if (test_emit_xml_format()) {
	  emit_xml_header(stdout, "dacs_conf_reply");
	  fprintf(stdout, "<%s>\n", make_xml_root_element("dacs_conf_reply"));
	  init_common_status(&common_status, NULL, NULL, errmsg);
	  fprintf(stdout, "%s", make_xml_common_status(&common_status));
	  fprintf(stdout, "</dacs_conf_reply>\n");
	  emit_xml_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_HTML)) {
	  hc->title = ds_xprintf("DACS Configuration Information for %s",
							 dacs_current_jurisdiction());
	  if (conf_val(CONF_CSS_PATH) != NULL)
		hc->css = ds_xprintf("%s/conf.css", conf_val(CONF_CSS_PATH));
	  else
		hc->css = CSS_DIR/**/"/conf.css";
	  emit_html_header(stdout, hc);
	  fprintf(stdout, "%s\n", errmsg);
	  emit_html_trailer(stdout);
	}
	else if (test_emit_format(EMIT_FORMAT_PLAIN))
	  emit_plain_trailer(stdout);
	else
	  fprintf(stderr, "%s\n", errmsg);

	return(-1);
  }

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

  if (app_type == DACS_UTILITY && !dacs_saw_command_line_log_level)
	log_set_level(NULL, LOG_WARN_LEVEL);

  if ((p = kwv_lookup_value(kwv, "VARS")) != NULL) {
	if (strcaseeq(p, "yes"))
	  emit_vars = 1;
	else if (strcaseeq(p, "no"))
	  emit_vars = 0;
  }

  if (should_use_argv) {
	while (argv[1] != NULL && argv[1][0] == '-') {
	  if (strcaseeq(argv[1], "-vars")) {
		emit_vars = 1;
		argc--;
		argv++;
	  }
	  else if (strcaseeq(argv[1], "-item_types")) {
		for (i = 0; predefined_item_types[i] != NULL; i++)
		  printf("%s\n", predefined_item_types[i]);

		exit(0);
	  }
	  else {
		errmsg = "Unrecognized flag";
		goto fail;
	  }
	}
  }

  if (test_emit_xml_format())
	emit_xml_header(stdout, "dacs_conf_reply");
  else if (test_emit_format(EMIT_FORMAT_HTML)) {
	hc->title = ds_xprintf("DACS Configuration Information for %s",
						   dacs_current_jurisdiction());
	if (conf_val(CONF_CSS_PATH) != NULL)
	  hc->css = ds_xprintf("%s/conf.css", conf_val(CONF_CSS_PATH));
	else
	  hc->css = CSS_DIR/**/"/conf.css";
	emit_html_header(stdout, hc);
  }
  else if (test_emit_format(EMIT_FORMAT_PLAIN))
	emit_plain_header(stdout);

  if (test_emit_xml_format()) {
	if (dacs_conf->uri_expr != NULL)
	  printf("<%s uri=\"%s\" uri_expr=\"%s\">\n",
			 make_xml_root_element("dacs_conf_reply"),
			 xml_attribute_value_encode(dacs_conf->uri, '\"'),
			 xml_attribute_value_encode(dacs_conf->uri_expr, '\"'));
	else
	  printf("<%s uri=\"%s\">\n", make_xml_root_element("dacs_conf_reply"),
			 dacs_conf->uri);
  }
  else if (test_emit_format(EMIT_FORMAT_HTML)) {
	printf("\n<div class=\"configuration\">\n");
	printf("\n<div class=\"jurisdiction_section\">\n");
	if (dacs_conf->uri_expr != NULL)
	  printf("<span class=\"jurisdiction_begin\">&lt;Jurisdiction uri=\"%s\" uri_expr=\"%s\"&gt;<br/></span>\n",
			 html_encode(xml_attribute_value_encode(dacs_conf->uri, '\"')),
			 html_encode(xml_attribute_value_encode(dacs_conf->uri_expr, '\"')));
	else
	  printf("<span class=\"jurisdiction_begin\">&lt;Jurisdiction uri=\"%s\"&gt;<br/></span>\n",
			 dacs_conf->uri);
  }

  if (app_type == DACS_UTILITY) {
	if (argc == 1)
	  show(stdout, NULL);
	else {
	  saw_directives = 1;
	  for (i = 1; i < argc; i++)
		show(stdout, argv[i]);
	}
  }
  else {
	Kwv_pair *v;

	/*
	 * The optional, repeatable DIRECTIVE parameter is a comma separated list
	 * of directive names to be looked up.
	 */
	v = kwv_lookup(kwv, "DIRECTIVE");
	if (v != NULL) {
	  while (v != NULL) {
		directive = strdup(v->val);
		while (directive != NULL && *directive != '\0') {
		  if ((p = strchr(directive, ',')) != NULL)
			*p++ = '\0';
		  show(stdout, directive);
		  directive = p;
		}
		v = v->next;
	  }
	}
	else
	  show(stdout, NULL);
  }

  if (test_emit_format(EMIT_FORMAT_HTML)) {
	printf("<span class=\"jurisdiction_end\">&lt;/Jurisdiction&gt;<br/></span>\n");
	printf("</div></div>\n");
	emit_html_trailer(stdout);
  }
  else if (test_emit_xml_format()) {
	fprintf(stdout, "</dacs_conf_reply>\n");
	emit_xml_trailer(stdout);
  }
  else if (test_emit_format(EMIT_FORMAT_PLAIN))
	emit_plain_trailer(stdout);

  return(0);
}

#else

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

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

  exit(1);
}
#endif
