/*
 * Copyright (c) 2003-2013
 * 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.
 ***************************************************************************/

/*
 * Assorted support for DACS services and utilities
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2013\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: dacslib.c 2649 2013-02-28 17:45:03Z brachman $";
#endif

#include "dacs.h"
#include "frame.h"

#include <grp.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <locale.h>
#include <stdarg.h>
#include <signal.h>
#include <pwd.h>

static const char *log_module_name = "dacslib";

char *standard_command_line_usage
	= "[[-u URI] | [-uj jname] [-us] [-un]] [-c config] [-sc site-config]\n  [-format fmt] [-ll log-level] [-q] [-t] [-v|--verbose] [-Dname=value]\n  [--version] [--dumpenv] [--license] [--std] [--enable-dump]";

DACS_app_type dacs_app_type = DACS_UNKNOWN_APP;
char *dacs_compatibility_mode = NULL;
DACS_conf *dacs_conf;
char *dacs_conf_path_spec = NULL;
int dacs_init_allow_dups_default = 0;
Kwv *dacs_kwv_args = NULL;
char *dacs_logfile_path = NULL;
int dacs_no_conf_flag = 0;
int dacs_saw_command_line_format = 0;
int dacs_saw_command_line_log_level = 0;
char *dacs_debug_flag_file = NULL;
char *dacs_site_conf_path_spec = NULL;
char *dacs_version_number = DACS_VERSION_NUMBER;
char *dacs_version_release = DACS_VERSION_RELEASE;

#ifdef ENABLE_RULE_PATTERNS
int dacs_enable_rule_patterns = 1;
#else
int dacs_enable_rule_patterns = 0;
#endif

/*
 * Note that for a forward proxied request, the REQUEST_URI environment
 * variable and the SERVICE_URI variable passed by mod_auth_dacs are absolute
 * URIs, possibly with a query component; they are normally relative URIs with
 * the query component is omitted.  Also, for a forward proxied request,
 * SERVICE_FILENAME is an absolute URI, possibly with a query component,
 * prefixed by "proxy:", otherwise it is path with query component omitted.
 * XXX clarify the case for a reverse proxied request
 *
 * The dacs_service_uri is the currently requested resource (usually
 * REQUEST_URI), always sans query component, and is absolute only for a
 * proxied resource.
 *
 * The dacs_effective_service_uri, if non-NULL, is an absolute URI (sans-query)
 * that uniquely identifies the current jurisdiction.  It is determined during
 * configuration processing.
 */
char *dacs_service_uri = NULL;
char *dacs_effective_service_uri = NULL;

int should_use_argv = 1;
unsigned long trace_level = 0;
unsigned long verbose_level = 0;
int quiet_flag = 0;
Kwv *kwv_dacsoptions = NULL;
int ssl_verify = 0;
int use_ssl = 0;
int forward_proxy_jurisdiction = 0;

static int dacs_enable_dump = 0;
static char *dacs_app_name = NULL;
static int dacs_log_done_init = 0;

static struct timespec start_time;
static Log_level log_startup_level = LOG_STARTUP_LEVEL;
static int is_windows_platform = 0;

static FILE *set_initial_logging(DACS_app_type app_type, char *progname,
								 char **errmsg);
static FILE *set_logging_stream(DACS_app_type app_type, FILE *fp, int stage,
								char **logfile_path, char **errmsg);
static char *logging_callback(Log_desc *logd, Log_level level, void *arg);
static int validate_conf(void);
static void dacs_end(void);

static char *this_tz = NULL;
#ifndef BEGIN_MESSAGE
#define BEGIN_MESSAGE	"\n----- ----- ----- ----- ----- Execution begins ----- ----- ----- ----- -----\n"
#endif

typedef struct Atexit_entry {
  void (*func)(void *arg);
  void *arg;
} Atexit_entry;

static Dsvec *dacs_atexit_callouts = NULL;

void
dacs_atexit(void (*func)(void *arg), void *arg)
{
  Atexit_entry *e;

  if (dacs_atexit_callouts == NULL)
	dacs_atexit_callouts = dsvec_init(NULL, sizeof(Atexit_entry *));

  e = ALLOC(Atexit_entry);
  e->func = func;
  e->arg = arg;
  dsvec_add_ptr(dacs_atexit_callouts, e);
}

static void
sighandler(int sig)
{

  if (!dacs_enable_dump
	  && (dacs_app_type != DACS_STANDALONE
		  && dacs_app_type != DACS_STANDALONE_NOARGS))
	dacs_disable_dump();

  if (dacs_app_type == DACS_UTILITY
	  || dacs_app_type == DACS_UTILITY_OPT
	  || dacs_app_type == DACS_STANDALONE
	  || dacs_app_type == DACS_STANDALONE_NOARGS)
	exit(1);

  log_msg((LOG_ALERT_LEVEL, "Received signal %d", sig));
  exit(1);
}

static void
catch_signals(void)
{
  int i;
  static int sigs[] = {
	SIGHUP, SIGINT, SIGTERM, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
	SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE, SIGALRM, SIGXCPU, SIGXFSZ,
	SIGVTALRM, SIGPROF, SIGUSR1, SIGUSR2,
#ifdef SIGEMT
	SIGEMT,
#endif
#ifdef SIGTHR
	SIGTHR,
#endif
#ifdef SIGPOLL
	SIGPOLL,
#endif
#ifdef SIGPWR
	SIGPWR,
#endif
#ifdef SIGLOST
	SIGLOST,
#endif
	0
  };

  for (i = 0; sigs[i] != 0; i++)
	signal(sigs[i], sighandler);
}

typedef struct DACS_app_type_name_map {
  DACS_app_type app_type;
  char *name;
} DACS_app_type_name_map;

static DACS_app_type_name_map app_type_name_map[] = {
  { DACS_WEB_SERVICE,         "web service" },
  { DACS_LOCAL_SERVICE,       "local service" },
  { DACS_UTILITY,             "utility" },
  { DACS_UTILITY_OPT,         "utility optconfig" },
  { DACS_UTILITY_PROC,        "utility subprocess optconfig" },
  { DACS_ACS,                 "acs" },
  { DACS_STANDALONE,          "standalone" },
  { DACS_STANDALONE_NOARGS,   "standalone noargs" },
  { DACS_STANDALONE_SERVICE,  "standalone service" },
  { DACS_SERVER,              "server" },
  { DACS_UNKNOWN_APP,         NULL }
};

static char *
dacs_app_type_name(DACS_app_type app_type)
{
  int i;

  for (i = 0; app_type_name_map[i].name != NULL; i++) {
	if (app_type_name_map[i].app_type == app_type)
	  return(app_type_name_map[i].name);
  }

  return(NULL);
}

char *
dacs_current_jurisdiction(void)
{

  if (dacs_conf != NULL && dacs_conf->conf_vartab != NULL) {
	char *f, *j, *s;

	f = conf_val(CONF_FEDERATION_NAME);
	j = conf_val(CONF_JURISDICTION_NAME);
	s = ds_xprintf("%s%c%c%s",
				   f,
				   JURISDICTION_NAME_SEP_CHAR, JURISDICTION_NAME_SEP_CHAR,
				   j);
	return(s);
  }

  return(NULL);	
}

/*
 * Using this jurisdiction's meta information, return the domain name
 * associated with the jurisdiction.
 * Note that because of URL to jurisdiction mapping, more than one
 * jurisdiction can have the same domain name.
 * Optionally return the jurisdiction's subdomain prefix (relative to the
 * FEDERATION_DOMAIN)
 */
char *
dacs_current_jurisdiction_domain(char **jurisdiction_subdomain_prefix,
								 char **domain_name)
{

  int i;
  char *dn, *expanded_url;
  Jurisdiction *j;
  Uri *uri;

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

  if (j->dacs_url == NULL || *j->dacs_url == '\0'
	  || (expanded_url = expand_dacs_url(j->jname, j->dacs_url)) == NULL
	  || (uri = uri_parse(expanded_url)) == NULL)
	return(NULL);
  
  if (uri->host != NULL) {
	if (domain_name != NULL)
	  *domain_name = strdup(uri->host);
	if (jurisdiction_subdomain_prefix != NULL) {
	  char *jsp, *suffix;

	  if ((suffix = strcasesuffix(uri->host, strlen(uri->host),
								  conf_val(CONF_FEDERATION_DOMAIN))) != NULL)
		jsp = strndup(uri->host, suffix - uri->host - 1);
	  else
		jsp = NULL;
	  *jurisdiction_subdomain_prefix = jsp;
	}
  }

  return(uri->host);

#ifdef NOTDEF
  if (dacs_conf != NULL && dacs_conf->conf_vartab != NULL) {
	char *d, *j, *s;

	d = conf_val(CONF_FEDERATION_DOMAIN);
	j = conf_val(CONF_JURISDICTION_NAME);
	/* Don't want it to look like a domain name because it may not be one. */
	s = ds_xprintf("%s/%s", j, d);
	return(s);
  }

  return(NULL);	
#endif
}

int
dacs_app_init(DACS_app_type app_type, char *dacs_conf_file,
			  char *dacs_site_conf_file, char *argv0,
			  char *jurisdiction_name, char *uri, Kwv **kwvp, char **errmsg)
{
  char *conf_file, *fmt, *p, *query_string, *site_conf_file;
  time_t now;
  struct tm *tm;
  FILE *logfp;
  extern Kwv_vartab conf_vartab[];
  static int done_init = 0;

  if (done_init) {
	log_msg((LOG_ALERT_LEVEL, "dacs_app_init: attempt to reinitialize"));
	*errmsg = "Can't reinitialize";
	return(-1);
  }
  done_init = 1;	
  site_conf_file = NULL;

#ifdef DACS_OS_CYGWIN
  is_windows_platform = 1;
#else
  is_windows_platform = 0;
#endif

  dacs_app_type = app_type;
  if (!dacs_saw_command_line_format) {
	/* If the default format has been changed, use that. */
	if (emit_format_default != EMIT_FORMAT_DEFAULT)
	  set_emit_format(NULL);
	else {
	  if (app_type == DACS_LOCAL_SERVICE)
		fmt = "xml";
	  else if (app_type == DACS_WEB_SERVICE)
		fmt = "html";
	  else
		fmt = "text";
	  if (set_emit_format(fmt) == -1) {
		*errmsg = ds_xprintf("set_emit_format(\"%s\") failed?", fmt);
		return(-1);
	  }
	}
  }

  set_dacs_app_name(argv0);
  logfp = NULL;

  /* Disallow world access for any created files. */
  umask(0007);

  catch_signals();

  parse_xml_init_xmlns(ds_xprintf("%s/v%s",
								  XMLNS_PREFIX, DACS_VERSION_NUMBER));

  if (!dacs_log_done_init) {
	if ((logfp = set_initial_logging(app_type, argv0, errmsg)) == NULL)
	  return(-1);
  }

  if (kwvp != NULL) {
	if ((*kwvp = kwv_init(INIT_CGI_KWV)) == NULL) {
	  *errmsg = "kwv_init() failed";
	  return(-1);
	}
	if (dacs_init_allow_dups_default)
	  (*kwvp)->dup_mode = KWV_ALLOW_DUPS;
	else
	  (*kwvp)->dup_mode = KWV_NO_DUPS;
  }

  vfs_init();

  set_name_cmp_mode(DACS_NAME_CMP_CONFIG);

  /*
   * We may play with the timezone later -- try to figure out what it currently
   * is and cause some initialization to occur within the library functions.
   */
  (void) setlocale(LC_TIME, "");
  if ((this_tz = getenv("TZ")) == NULL) {
	now = time(NULL);
	tm = localtime(&now);
#ifdef NOTDEF
	if (tm->tm_zone != NULL)
	  this_tz = strdup(tm->tm_zone);
	else {
	  log_msg((LOG_WARN_LEVEL, "No TZ, using GMT"));
	  this_tz = "GMT";
	}
#endif
	if (tzname[tm->tm_isdst] != NULL && tzname[tm->tm_isdst][0] != '\0')
	  this_tz = strdup(tzname[tm->tm_isdst]);
	else {
	  log_msg((LOG_WARN_LEVEL, "No TZ, using GMT"));
	  this_tz = "GMT";
	}
  }

  if (!dacs_enable_dump
	  && (app_type != DACS_STANDALONE && app_type != DACS_STANDALONE_NOARGS))
	dacs_disable_dump();

  if (crypto_init() == -1)
	return(-1);

  if (uri == NULL && jurisdiction_name == NULL) {
	if (app_type == DACS_STANDALONE || app_type == DACS_STANDALONE_NOARGS)
	  return(0);
	uri = current_uri_no_query(NULL);
  }

  /*
   * It is ok for a DACS_LOCAL_SERVICE not to have access to a configuration
   * file, although that's not particularly desirable.
   */
  if (app_type != DACS_LOCAL_SERVICE && dacs_conf_file == NULL) {
	if (uri != NULL)
	  log_msg((LOG_ERROR_LEVEL, "uri=\"%s\"", uri));
	*errmsg = "No config file path has been specified";
	return(-1);
  }

  /*
   * The CGI 1.1 spec says:
   *   Scripts SHOULD check to see if the QUERY_STRING value contains an
   *   unencoded "=" character, and SHOULD NOT use the command line arguments
   *   if it does.
   * http://hoohoo.ncsa.uiuc.edu/cgi/
   */
  query_string = getenv("QUERY_STRING");
  should_use_argv = (query_string == NULL
					 || strchr(query_string, (int) '=') == NULL);
  /*
   * Having said the above, we always want to ignore command line arguments
   * if we've been invoked as a CGI program.
   * Note that dacs_acs gets the program's arguments via mod_auth_dacs...
   */
  if (app_type == DACS_WEB_SERVICE || app_type == DACS_LOCAL_SERVICE
	  || app_type == DACS_STANDALONE_SERVICE) {

	should_use_argv = 0;

	if (kwvp != NULL && *kwvp != NULL) {
	  if (cgiparse(stdin, query_string, *kwvp, NULL) == -1) {
		if ((*kwvp)->error_msg != NULL)
		  *errmsg = ds_xprintf("CGI parse failed: %s", (*kwvp)->error_msg);
		else
		  *errmsg = "CGI parse failed";
		return(-1);
	  }
	  /*
	   * XXX In theory, "DACS_ACS" should only appear as an argument to a DACS
	   * web service as a result of the -check_fail flag being used.
	   * Because it appears to be difficult to elide the DACS_ACS argument
	   * at the mod_auth_dacs and/or dacs_acs level, it is not invisible
	   * (as it should be).
	   * Some DACS services choke on unrecognized arguments, so they either
	   * need to be recoded to ignore DACS_ACS or we can simply zap it
	   * here - we do the latter.
	   */
	  kwv_delete(*kwvp, "DACS_ACS");
	}
	/* For convenience, make the arguments available globally. */
	dacs_kwv_args = (kwvp != NULL) ? *kwvp : NULL;
  }

  /*
   * If no configuration processing is necessary, or if it has been
   * explicitly suppressed, then we're done.
   */
  if (app_type == DACS_STANDALONE
	  || app_type == DACS_STANDALONE_NOARGS
	  || app_type == DACS_STANDALONE_SERVICE)
	return(0);
  
  if (dacs_conf_file != NULL) {
	conf_file = directory_name_interpolate(dacs_conf_file,
										   getenv("SERVER_NAME"),
										   getenv("SERVER_PORT"));
	if (conf_file == NULL) {
	  if (uri != NULL)
		log_msg((LOG_ERROR_LEVEL, "uri=\"%s\"", uri));
	  *errmsg = ds_xprintf("Interpolation failed for config file \"%s\"",
						   dacs_conf_file);
	  return(-1);
	}
	else
	  log_msg((LOG_TRACE_LEVEL, "Config file interpolation yields: \"%s\"",
			   conf_file));
	dacs_conf_path_spec = dacs_conf_file;

	if (dacs_site_conf_file != NULL) {
	  site_conf_file = directory_name_interpolate(dacs_site_conf_file,
												  getenv("SERVER_NAME"),
												  getenv("SERVER_PORT"));
	  if (site_conf_file == NULL) {
		if (uri != NULL)
		  log_msg((LOG_ERROR_LEVEL, "uri=\"%s\"", uri));
		*errmsg = ds_xprintf("Interpolation failed for site config file \"%s\"",
							 dacs_site_conf_file);
		return(-1);
	  }
	  else
		log_msg((LOG_TRACE_LEVEL,
				 "Site config file interpolation yields: \"%s\"",
				 site_conf_file));
	}
	dacs_site_conf_path_spec = dacs_site_conf_file;

	if (dacs_app_type == DACS_UTILITY_OPT
		|| dacs_app_type == DACS_UTILITY_PROC) {
	  if (dacs_no_conf_flag || conf_file == NULL || !file_exists(conf_file)) {
		Acs_environment env;

		acs_new_env(&env);
		dacs_conf = new_dacs_conf(NULL, NULL, NULL, conf_vartab,
								  var_ns_new(&env.namespaces, "Conf",
											 kwv_init(8)));
		return(0);
	  }
	}

	/* Process the configuration file(s). */
	if (uri != NULL)
	  dacs_conf = conf_init(conf_file, site_conf_file, uri, conf_vartab);
	else
	  dacs_conf = conf_init_by_jurisdiction(conf_file, site_conf_file,
											jurisdiction_name, conf_vartab);

	if (dacs_conf == NULL) {
	  if (dacs_effective_service_uri != NULL)
		log_msg((LOG_ERROR_LEVEL, "uri=\"%s\"", dacs_effective_service_uri));
	  *errmsg = ds_xprintf("Error accessing or processing config file: \"%s\"",
						   conf_file);
	  return(-1);
	}

	log_msg((LOG_INFO_LEVEL, "Config file is \"%s\"", conf_file));
  }

  /*
   * Configuration processing is complete, so config directives can
   * now be referenced.
   */
  if (trace_level == 0 && (p = conf_val(CONF_TRACE_LEVEL)) != NULL) {
	if (strnum(p, STRNUM_UL, &trace_level) == -1) {
	  *errmsg = "Invalid TRACE_LEVEL";
	  return(-1);
	}
  }

  if (verbose_level == 0 && (p = conf_val(CONF_VERBOSE_LEVEL)) != NULL) {
	if (strnum(p, STRNUM_UL, &verbose_level) == -1) {
	  *errmsg = "Invalid VERBOSE_LEVEL";
	  return(-1);
	}
  }

  if (validate_conf() == -1) {
	*errmsg = "Invalid configuration";
	return(-1);
  }

  if ((p = dacs_current_jurisdiction()) != NULL)
	log_msg((LOG_INFO_LEVEL, "This is jurisdiction %s", p));

  if (app_type != DACS_UTILITY
	  && app_type != DACS_UTILITY_OPT
	  && app_type != DACS_STANDALONE
	  && app_type != DACS_STANDALONE_NOARGS) {
	if (chdir(DACS_HOME) == -1)
	  log_err((LOG_ERROR_LEVEL, "Unable to chdir to \"%s\"", DACS_HOME));
	else
	  log_msg((LOG_INFO_LEVEL, "chdir to \"%s\"", DACS_HOME));
  }

  /* Fully initialize logging, but only if it hasn't already been done. */
  if (!dacs_log_done_init) {
	char *path;

	logfp = set_logging_stream(app_type, logfp, 1, &path, errmsg);
	if (logfp == NULL)
	  return(-1);

	dacs_log_init(logfp, path, 1);
  }

  if (app_type == DACS_WEB_SERVICE || app_type == DACS_LOCAL_SERVICE) {
	char *version;

	if (kwvp != NULL && *kwvp != NULL) {
#ifdef NOTDEF
	  if (cgiparse(stdin, query_string, *kwvp, NULL) == -1) {
		if ((*kwvp)->error_msg != NULL)
		  *errmsg = ds_xprintf("CGI parse failed: %s", (*kwvp)->error_msg);
		else
		  *errmsg = "CGI parse failed";
		return(-1);
	  }

	  /*
	   * XXX In theory, "DACS_ACS" should only appear as an argument to a DACS
	   * web service as a result of the -check_fail flag being used.
	   * Because it appears to be difficult to elide the DACS_ACS argument
	   * at the mod_auth_dacs and/or dacs_acs level, it is not invisible
	   * (as it should be).
	   * Some DACS services choke on unrecognized arguments, so they either
	   * need to be recoded to ignore DACS_ACS or we can simply zap it
	   * here - we do the latter.
	   */
	  kwv_delete(*kwvp, "DACS_ACS");
#endif

	  version = kwv_lookup_value(*kwvp, "DACS_VERSION");
	  if (!is_compatible_dacs_version(version)) {
		*errmsg = "Missing or unsupported DACS_VERSION";
		return(-1);
	  }
	  if (kwv_dups(*kwvp, "DACS_VERSION")) {
		*errmsg = "Multiple DACS_VERSION parameters";
		return(-1);
	  }

	  if (!dacs_saw_command_line_format
		  && (fmt = kwv_lookup_value(*kwvp, "FORMAT")) != NULL) {
		if (kwv_dups(*kwvp, "FORMAT")) {
		  *errmsg = "Multiple FORMAT parameters";
		  return(-1);
		}
		if (set_emit_format(fmt) == -1) {
		  *errmsg = "Unrecognized FORMAT parameter value";
		  return(-1);
		}
	  }
	}
  }

  return(0);
}

/*
 * Do various initializations common to all DACS applications.
 * This should be called very early in the execution.
 *
 * Several command line arguments that are common to all DACS applications
 * are processed here:
 *   -u URI       : the URI prefix used to match a configuration file entry
 *   -uj jur      : jurisdiction name
 *   -up jur      : invoked in forward proxy mode as this pseudo jurisdiction
 *   -un          : no jurisdiction, no configuration file
 *   -us          : only a single jurisdiction is configured, use it
 *   -c CONF      : the configuration file to use is CONF
 *   -sc CONF     : the site configuration file to use is CONF
 *   -ll LEV      : the startup logging level to use is LEV
 *   -format fmt  : the output format is FMT (overrides FORMAT argument)
 *   -Dname=value : define/initialize a variable
 *   -t           : set log level to LOG_LEVEL_TRACE
 *   -q           : be more quiet
 *   -v|--verbose : make log level more verbose (repeatable)
 *   --version    : print version information to stderr and exit
 *   --dumpenv    : print environment information to stdout exit
 *   --license    : print the license to stdout and exit
 *   --std        : end of common arguments, keep remaining args
 *   --enable-dump: allow a core dump to be created
 *
 * These common arguments are deleted from the caller's command line.
 *
 * Returns -1 on error (and point ERRMSG to a message), 0 otherwise.
 */
int
dacs_init(DACS_app_type app_type, int *argc_p, char ***argv_p,
		  Kwv **kwv, char **errmsg)
{
  int argc, i;
  char **argv, *argv0, *dacs_conf_file, *dacs_site_conf_file;
  char *cmd_dacs_conf, *cmd_site_conf, *format;
  char *jurisdiction_name, *log_level, *p, *uri;
  Dsvec *dsv;
  static int done_init = 0;

  start_time.tv_sec = 0;
  start_time.tv_nsec = 0;
  clock_gettime(CLOCK_REALTIME, &start_time);

  if (done_init) {
	log_msg((LOG_ALERT_LEVEL, "dacs_init: attempt to reinitialize"));
	*errmsg = "Can't reinitialize";
	return(-1);
  }
  done_init = 1;	

  argc = *argc_p;
  argv = *argv_p;
  dsv = dsvec_init(NULL, sizeof(char *));

  *errmsg = "Internal error";

  if (argv[0] == NULL) {
	fprintf(stderr, "What's my name?!\n");
	argv0 = strdup("unknown-app");
  }
  else {
	argv0 = strdup(argv[0]);
	if ((p = strrchr(argv0, '/')) != '\0') {
	  *p++ = '\0';
	  argv0 = p;
	}
  }
  dsvec_add_ptr(dsv, argv0);

  if (set_initial_logging(app_type, argv0, errmsg) == NULL)
	return(-1);

  cmd_dacs_conf = NULL;
  cmd_site_conf = NULL;
  format = NULL;
  uri = NULL;
  log_level = NULL;
  jurisdiction_name = NULL;

  /*
   * According to the WWW Common Gateway Interface Version 1.2 (29-May-88),
   * Section 6, The CGI Script Command Line:
   *   Some systems support a method for supplying an array of strings to
   *   the CGI script. This is only used in the case of an 'indexed' query.
   *   This is identified by a "GET" or "HEAD" HTTP request with a URL
   *   search string not containing any unencoded "=" characters. For such
   *   a request, the server should parse the search string into words,
   *   using the rules: [...]
   * After parsing, each word is URL-decoded, optionally encoded in a system
   * defined manner and then the argument list is set to the list of words.
   * http://cgi-spec.golux.com/cgi-120-00a.html
   *
   * So given a URL like: .../cgi-bin/foo?blah
   * "blah" will appear as a command line flag (whether you want it or not).
   */
  kwv_dacsoptions = kwv_init(10);
  kwv_set_mode(kwv_dacsoptions, "dr");
  kwv_replace(kwv_dacsoptions, "DACS_APP_TYPE",
			  dacs_app_type_name(dacs_app_type));

  for (i = 1; app_type != DACS_STANDALONE_NOARGS && i < argc; i++) {
	if (streq(argv[i], "-u")) {
	  if (uri != NULL) {
		*errmsg = "Usage: multiple URI parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing URI parameter";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-u", argv[i]);
	  uri = strdup(argv[i]);
	}
	else if (streq(argv[i], "-uj")) {
	  if (uri != NULL || jurisdiction_name != NULL) {
		*errmsg = "Usage: multiple jurisdiction selection parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing jurisdiction name parameter";
		return(-1);
	  }

	  jurisdiction_name = argv[i];
	  if (!is_valid_jurisdiction_name(jurisdiction_name)) {
		*errmsg = "Usage: invalid -uj jurisdiction name";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-uj", argv[i]);
	}
	else if (streq(argv[i], "-up")) {
	  if (uri != NULL || jurisdiction_name != NULL) {
		*errmsg = "Usage: multiple jurisdiction selection parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing jurisdiction name parameter";
		return(-1);
	  }

	  jurisdiction_name = argv[i];
	  if (!is_valid_jurisdiction_name(jurisdiction_name)) {
		*errmsg = "Usage: invalid -up jurisdiction name";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-up", argv[i]);
	  forward_proxy_jurisdiction = 1;
	}
	else if (streq(argv[i], "-un")) {
	  if (uri != NULL || jurisdiction_name != NULL) {
		*errmsg = "Usage: multiple jurisdiction selection parameters";
		return(-1);
	  }

	  if (app_type != DACS_UTILITY_OPT && app_type != DACS_UTILITY_PROC) {
		*errmsg = "Cannot use -un flag with this program";
		return(-1);
	  }

	  dacs_no_conf_flag = 1;

	  /* This is a special use of this variable... */
	  jurisdiction_name = "";	  

	  kwv_replace(kwv_dacsoptions, "-un", "1");
	}
	else if (streq(argv[i], "-us")) {
	  if (uri != NULL || jurisdiction_name != NULL) {
		*errmsg = "Usage: multiple jurisdiction selection parameters";
		return(-1);
	  }

	  /*
	   * This is a special use of this variable - the invalid jurisdiction
	   * name implies "any jurisdiction section provided there's only one".
	   */
	  jurisdiction_name = "";

	  kwv_replace(kwv_dacsoptions, "-us", "1");
	}
	else if (streq(argv[i], "-c")) {
	  if (cmd_dacs_conf != NULL) {
		*errmsg = "Usage: multiple config file parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing config file parameter";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-c", argv[i]);
	  cmd_dacs_conf = strdup(argv[i]);
	}
	else if (streq(argv[i], "-format")) {
	  if (dacs_saw_command_line_format != 0) {
		*errmsg = "Usage: multiple format parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing format type parameter";
		return(-1);
	  }
	  dacs_saw_command_line_format++;
	  kwv_replace(kwv_dacsoptions, "-format", argv[i]);
	  format = argv[i];
	  if (set_emit_format(format) == -1) {
		*errmsg = ds_xprintf("Usage: invalid format type parameter: %s",
							 format);
		return(-1);
	  }
	}
	else if (streq(argv[i], "-sc")) {
	  if (cmd_site_conf != NULL) {
		*errmsg = "Usage: multiple site config file parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing site config file parameter";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-sc", argv[i]);
	  cmd_site_conf = strdup(argv[i]);
	}
	else if (argv[i][0] == '-' && argv[i][1] == 'D') {
	  char *varname, *varvalue;

	  if (kwv_parse_str(&argv[i][2], &varname, &varvalue) == -1
		  || !var_ns_is_valid_varname(varname, NULL)) {
		*errmsg = "Usage: -Dname=value";
		return(-1);
	  }

	  if (kwv_add_nocopy(kwv_dacsoptions, varname, varvalue) == NULL) {
		*errmsg = ds_xprintf("Can't initialize: %s", argv[i]);
		return(-1);
	  }
	}
	else if (streq(argv[i], "-q")) {
	  log_startup_level = LOG_WARN_LEVEL;
	  dacs_saw_command_line_log_level = 1;
	  quiet_flag++;
	  kwv_replace(kwv_dacsoptions, "-q", ds_xprintf("%d", quiet_flag));
	}
	else if (streq(argv[i], "-ll")) {
	  if (log_level != NULL) {
		*errmsg = "Usage: multiple log level parameters";
		return(-1);
	  }
	  if (++i == argc) {
		*errmsg = "Usage: missing log level value parameter";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-ll", argv[i]);
	  log_level = argv[i];
	  if ((log_startup_level = log_lookup_level(log_level))
		  == LOG_INVALID_LEVEL) {
		*errmsg = "Usage: unrecognized log level value parameter";
		return(-1);
	  }
	  dacs_saw_command_line_log_level = 1;
	}
	else if (streq(argv[i], "-t")) {
	  trace_level++;
	  dacs_saw_command_line_log_level = 1;
	  kwv_replace(kwv_dacsoptions, "-t", ds_xprintf("%d", trace_level));
	}
	else if (streq(argv[i], "-v") || streq(argv[i], "--verbose")) {
	  verbose_level++;
	  dacs_saw_command_line_log_level = 1;
	  kwv_replace(kwv_dacsoptions, "-v", ds_xprintf("%d", verbose_level));
	  kwv_replace(kwv_dacsoptions, "--verbose",
				  ds_xprintf("%d", verbose_level));
	}
	else if (streq(argv[i], "--enable-dump")) {
	  dacs_enable_dump = 1;
	  kwv_replace(kwv_dacsoptions, "--enable-dump", "1");
	}
	else if (streq(argv[i], "--version")) {
	  kwv_replace(kwv_dacsoptions, "--version", "1");
	  dacs_version(stderr);
	  exit(0);
	}
	else if (streq(argv[i], "--dumpenv")) {
	  kwv_replace(kwv_dacsoptions, "--dumpenv", "1");
	  envdump();
	  exit(0);
	}
	else if (streq(argv[i], "--license")) {
	  kwv_replace(kwv_dacsoptions, "--license", "1");
	  show_license(0, NULL, 0, NULL);
	  exit(0);
	}
	else if (streq(argv[i], "--std")) {
	  i++;
	  break;
	}
	else
	  break;
  }

  if (!trace_level && !verbose_level) {
	/*
	 * If the debug flag file exists, turn on tracing and verbose output just as
	 * if we saw the -t and -v flags.  This can be handy because it avoids
	 * having to edit httpd.conf and restart Apache (in the case of dacs_acs)
	 * or modify dacs.conf, and then later remember to undo the change.
	 * It allows us to see TRACE level log messages earlier than if
	 * a DACS configuration directive were used.
	 * XXX maybe this should be on a more selectable basis, rather than
	 * all-or-nothing, because the output can be voluminous, but that seems
	 * hard to do this early in the execution.
	 */
	dacs_debug_flag_file = ds_xprintf("%s/debug_%s", DACS_HOME, argv0);
	if (file_exists(dacs_debug_flag_file)) {
	  trace_level++;
	  dacs_saw_command_line_log_level = 1;
	  kwv_replace(kwv_dacsoptions, "-t", ds_xprintf("%d", trace_level));

	  verbose_level++;
	  kwv_replace(kwv_dacsoptions, "-v", ds_xprintf("%d", verbose_level));
	  kwv_replace(kwv_dacsoptions, "--verbose",
				  ds_xprintf("%d", verbose_level));
	}
	else
	  dacs_debug_flag_file = NULL;
  }

  if (app_type != DACS_STANDALONE_NOARGS) {
	/* Unrecognized arguments are retained for the application. */
	while (i < argc)
	  dsvec_add_ptr(dsv, strdup(argv[i++]));

	/* XXX Might be nice to make them invisible to ps(1).  */

	*argc_p = dsvec_len(dsv);
	/*
	 * This must go here, after we get the length but before we get the base.
	 */
	dsvec_add_ptr(dsv, NULL);
	*argv_p = (char **) dsvec_base(dsv);
  }

  /* Repeat this because the requested logging level may have changed. */
  if (set_initial_logging(app_type, argv0, errmsg) == NULL)
	return(-1);

  /*
   * Determine where the configuration files are.
   * mod_auth_dacs may pass the location of dacs.conf (set via the
   * SetDACSAuthConf directive), through the DACS_CONF environment variable.
   * This can be overridden by a command line argument.
   */
#ifdef DACS_CONF
  dacs_conf_file = DACS_CONF;
  log_msg((LOG_TRACE_LEVEL, "Compile-time DACS_CONF is %s", dacs_conf_file));
#else
  dacs_conf_file = NULL;
#endif

  if ((p = getenv("DACS_CONF")) != NULL) {
	dacs_conf_file = p;
	log_msg((LOG_TRACE_LEVEL, "Environment DACS_CONF is %s", dacs_conf_file));
  }

  if (cmd_dacs_conf != NULL) {
	dacs_conf_file = cmd_dacs_conf;
	log_msg((LOG_TRACE_LEVEL, "Command line DACS_CONF is %s", cmd_dacs_conf));
  }

#ifdef DACS_SITE_CONF
  dacs_site_conf_file = DACS_SITE_CONF;
  log_msg((LOG_TRACE_LEVEL, "Compile-time DACS_SITE_CONF is %s",
		   dacs_site_conf_file));
#else
  dacs_site_conf_file = NULL;
#endif

  if ((p = getenv("DACS_SITE_CONF")) != NULL) {
	dacs_site_conf_file = p;
	log_msg((LOG_TRACE_LEVEL, "Environment DACS_SITE_CONF is %s",
			 dacs_site_conf_file));
  }

  if (cmd_site_conf != NULL) {
	dacs_site_conf_file = cmd_site_conf;
	log_msg((LOG_TRACE_LEVEL, "Command line DACS_SITE_CONF is %s",
			 cmd_site_conf));
  }

  if (jurisdiction_name == NULL) {
	if ((jurisdiction_name = getenv("DEFAULT_JURISDICTION")) != NULL) {
	  if (!is_valid_jurisdiction_name(jurisdiction_name)) {
		*errmsg
		  = "Usage: invalid jurisdiction name in DEFAULT_JURISDICTION envar";
		return(-1);
	  }
	  kwv_replace(kwv_dacsoptions, "-uj", jurisdiction_name);
	}
  }

  if (dacs_app_init(app_type, dacs_conf_file, dacs_site_conf_file,
					argv0, jurisdiction_name, uri, kwv, errmsg) == -1)
	return(-1);

  if (app_type != DACS_UTILITY
	  && app_type != DACS_UTILITY_OPT
	  && app_type != DACS_STANDALONE) {
	p = getenv("SERVER_ADDR");
	log_msg((LOG_FORCE_LEVEL, "%sDACS %s (%s)",
			 BEGIN_MESSAGE, DACS_VERSION_RELEASE, DACS_VERSION_DATE));
	log_msg((LOG_FORCE_LEVEL, "On: %s (SERVER_ADDR=%s)",
			 dacs_runtime_os_string(), (p == NULL) ? "?" : p));
	log_msg((LOG_FORCE_LEVEL, "At: %s", make_local_date_string(NULL, 1)));
	log_msg((LOG_FORCE_LEVEL, "Application: %s (type \"%s\")",
			 get_dacs_app_name(),
			 dacs_app_type_name(app_type)));

	if ((p = dacs_current_jurisdiction()) != NULL)
	  log_msg((LOG_FORCE_LEVEL, "Jurisdiction: %s", p));

	if (dacs_effective_service_uri != NULL)
	  log_msg((LOG_FORCE_LEVEL, "Effective dacs_service_uri: \"%s\"",
			   dacs_effective_service_uri));
	else
	  log_msg((LOG_FORCE_LEVEL, "Effective dacs_service_uri is undefined"));
	log_msg((LOG_FORCE_LEVEL,
			 "Process: pid=%u, uid=%u, euid=%u, gid=%u, egid=%u",
			 (unsigned int) getpid(),
			 (unsigned int) getuid(), (unsigned int) geteuid(),
			 (unsigned int) getgid(), (unsigned int) getegid()));

	if (app_type == DACS_WEB_SERVICE || app_type == DACS_LOCAL_SERVICE) {
	  if (dacs_secure_mode()) {
		if (!dacs_is_https_request()) {
		  log_msg((LOG_ALERT_LEVEL,
				   "Insecure connection - SECURE_MODE is enabled!"));
		  return(-1);
		}
	  }
	  else
		log_msg((LOG_FORCE_LEVEL, "WARNING: SECURE_MODE is disabled!"));
	}

	if (dacs_debug_flag_file != NULL)
	  log_msg((LOG_INFO_LEVEL, "Tracing enabled by \"%s\"",
			   dacs_debug_flag_file));
	log_msg((LOG_FORCE_LEVEL, ""));
  }

  atexit(dacs_end);

  return(0);
}

/*
 * An alternate initialization interface that returns information using
 * an extensible data structure.
 */
int
dacs_initialize(DACS_app_type app, int *argc, char **argv, DACS_app_args *aa)
{

  return(-1);
}

#ifndef EXIT_MESSAGE
#define EXIT_MESSAGE	"\n----- ----- ----- ----- ----- Execution ends ----- ----- ----- ----- -----\n\n"
#endif

static void
dacs_end(void)
{
  int i;

  /*
   * The callouts must be done first because afterwards logging is shutdown.
   */
  for (i = 0; i < dsvec_len(dacs_atexit_callouts); i++) {
	Atexit_entry *e;

	e = (Atexit_entry *) dsvec_ptr_index(dacs_atexit_callouts, i);
	(e->func)(e->arg);
  }

  if (verbose_level) {
	char *msg;
	Frame_stats *stats;

	if ((stats = fm_get_stats()) != NULL) {
	  msg = ds_xprintf("malloc=%d, realloc=%d, calloc=%d, free=%d, alloc=%d",
					   stats->malloc_count, stats->realloc_count,
					   stats->calloc_count, stats->free_count,
					   stats->allocated);
	  log_msg((LOG_DEBUG_LEVEL, "%s", msg));
	}
  }

  if (dacs_app_type != DACS_UTILITY
	  && dacs_app_type != DACS_UTILITY_OPT
	  && dacs_app_type != DACS_STANDALONE) {
	unsigned long diff_msecs, end_msecs, start_msecs;
	struct timespec end_time;

	clock_gettime(CLOCK_REALTIME, &end_time);
	start_msecs = (start_time.tv_sec * 1000) + (start_time.tv_nsec / 1000000);
	end_msecs = (end_time.tv_sec * 1000) + (end_time.tv_nsec / 1000000);
	diff_msecs = end_msecs - start_msecs;

#ifdef NOTDEF
	log_msg((LOG_TRACE_LEVEL, "Elapsed: %lu:%lu %lu:%lu",
			 (unsigned long) start_time.tv_sec,
			 (unsigned long) start_time.tv_nsec,
			 (unsigned long) end_time.tv_sec,
			 (unsigned long) end_time.tv_nsec));
#endif
	log_msg((LOG_INFO_LEVEL, "Elapsed time: %lu msec", diff_msecs));
			 
	log_msg((LOG_INFO_LEVEL, EXIT_MESSAGE));
  }

  log_end(NULL);
}

static int
check_dacsdir(char *path)
{
  struct stat sb;

  if (stat(path, &sb) == -1) {
	log_err((LOG_ERROR_LEVEL, "Can't stat %s", path));
	return(-1);
  }

  if ((sb.st_mode & 077) != 0) {
	log_msg((LOG_EMERG_LEVEL, "%s must be readable only by its owner!", path));
	return(-1);
  }

  if ((sb.st_mode & S_IFMT) != S_IFDIR) {
	log_msg((LOG_EMERG_LEVEL, "%s must be a directory", path));
	return(-1);
  }

  if (sb.st_uid != geteuid()) {
	log_msg((LOG_EMERG_LEVEL, "%s can only be accessed by its owner!", path));
	return(-1);
  }

  /* At best, only its owner can access the directory. */

  log_msg((LOG_TRACE_LEVEL, "%s seems secure", path));
  return(0);
}

static char *dacsdir = NULL;

int
set_dacsdir(char *path)
{
  char *home, *p;

  p = path;
  if (p == NULL) {
	if ((p = getenv("DACSDIR")) == NULL) {
	  if ((home = getenv("HOME")) == NULL) {
		/*
		 * XXX Give up.  Could use LOGNAME, getlogin(), or uid and
		 * getpwname/getpwuid to find the home directory...
		 */
		return(-1);
	  }
	  p = ds_xprintf("%s/.dacs", home);
	}
  }

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

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

  dacsdir = strdup(p);

  return(0);
}

char *
get_dacsdir(void)
{

  return(dacsdir);
}  

/*
 * Note that the semantics of setrlimit(2) do not allow limits to be
 * increased, so that once the core dump is disabled we cannot change our mind.
 */
void
dacs_disable_dump(void)
{
  struct rlimit rlimit;

  rlimit.rlim_cur = rlimit.rlim_max = 0;

  if (setrlimit(RLIMIT_CORE, &rlimit) == -1) {
	log_err((LOG_ALERT_LEVEL, "setrlimit"));
	exit(1);
  }
}

static DACS_name_cmp name_cmp_mode_default = DACS_NAME_CMP_DEFAULT;

DACS_name_cmp
set_name_cmp_mode(DACS_name_cmp cmp_mode)
{
  DACS_name_cmp prev_mode;

  prev_mode = name_cmp_mode_default;
  name_cmp_mode_default = cmp_mode;

  log_msg((LOG_TRACE_LEVEL, "Set name_cmp_mode_default to %d", cmp_mode));
  return(prev_mode);
}

DACS_name_cmp
get_name_cmp_mode(void)
{

  return(name_cmp_mode_default);
}

DACS_name_cmp
lookup_name_cmp(char *str)
{
  DACS_name_cmp cmp_mode;

  if (str == NULL)
	cmp_mode = name_cmp_mode_default;
  else if (strcaseeq(str, "case"))
	cmp_mode = DACS_NAME_CMP_CASE;
  else if (strcaseeq(str, "nocase"))
	cmp_mode = DACS_NAME_CMP_NOCASE;
  else if (strcaseeq(str, "default"))
	cmp_mode = name_cmp_mode_default;
  else
	cmp_mode = DACS_NAME_CMP_UNKNOWN;

  return(cmp_mode);
}

/*
 * Test if two federation names, jurisdiction names, or usernames
 * are equivalent using the specified comparison mode, CMP_MODE.
 * The DACS_NAME_CMP_CONFIG mode means to try the NAME_COMPARE directive
 * first and fall back on the default (name_cmp_mode_default) if necessary.
 * This mode will be used except by standalone programs, which will probably
 * call set_name_cmp_mode() based on command line flags and such.
 * The user() function accepts an optional mode argument, overriding
 * name_cmp_mode_default, which will be passed as CMP_MODE.
 *
 * The default should be the most restrictive mode since it will be
 * used in case of an error.
 * Return non-zero iff the two names are equivalent.
 */
int
name_eq(const char *n1, const char *n2, DACS_name_cmp cmp_mode, ...)
{
  int st;
  DACS_name_cmp m;

  if (cmp_mode == DACS_NAME_CMP_STRING) {
	char *p;
	va_list ap;

	va_start(ap, cmp_mode);
	p = va_arg(ap, char *);
	va_end(ap);

	if ((m = lookup_name_cmp(p)) == DACS_NAME_CMP_UNKNOWN) {
	  m = name_cmp_mode_default;
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid DACS_name_cmp specification: \"%s\"", p));
	  log_msg((LOG_ERROR_LEVEL, "Using default mode (%d)", m));
	}
  }
  else
	m = cmp_mode;

  if (m == DACS_NAME_CMP_CONFIG) {
	char *p;

	p = conf_val(CONF_NAME_COMPARE);
	if ((m = lookup_name_cmp(p)) == DACS_NAME_CMP_UNKNOWN) {
	  m = name_cmp_mode_default;
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid DACS_name_cmp specification: \"%s\"", p));
	  log_msg((LOG_ERROR_LEVEL, "Using default mode (%d)", m));
	}

	/* Watch for everything set to DACS_NAME_CMP_CONFIG... */
	if (m == DACS_NAME_CMP_CONFIG) {
	  if ((m = name_cmp_mode_default) == DACS_NAME_CMP_CONFIG)
		m = DACS_NAME_CMP_CASE;
	}
  }

  if (m == DACS_NAME_CMP_CASE)
	st = streq(n1, n2);
  else if (m == DACS_NAME_CMP_NOCASE)
	st = strcaseeq(n1, n2);
  else {
	log_msg((LOG_ERROR_LEVEL, "Invalid DACS_name_cmp mode: %d", m));
	st = -1;
  }

  return(st);
}

#ifndef HAVE_INET_ATON
#ifdef NOTDEF
/*
 * XXX In case the system really does have inet_aton(), use our version
 * with a different name.
 */
#define inet_aton	dacs_inet_aton
#endif

int
inet_aton(const char *cp, struct in_addr *addr)
{

  addr->s_addr = inet_addr(cp);
  return((addr->s_addr == INADDR_NONE) ? 0 : 1);
}
#endif

static int
looks_like_ip_addr(const char *str)
{
  const char *p;

  for (p = str; *p == '.' || isdigit((int) *p); p++)
	;
  return(*p == '\0');
}

int
is_ip_addr(const char *str, struct in_addr *addr)
{
  struct in_addr a, *ap;

  if (looks_like_ip_addr(str) && strchrcount(str, '.') == 3) {
	if (addr == NULL)
	  ap = &a;
	else
	  ap = addr;
	if (inet_aton(str, ap) == 1)
	  return(1);
  }

  return(0);
}

/*
 * Check if STR looks like a domain name (including a hostname), returning
 * 1 if it does, 0 otherwise.
 * See: RFC 920, 952, 1034, 1035, 1123.
 *
 *   A "name" (Net, Host, Gateway, or Domain name) is a text string up
 *   to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
 *   sign (-), and period (.).  Note that periods are only allowed when
 *   they serve to delimit components of "domain style names". (See
 *   RFC-921, "Domain Name System Implementation Schedule", for
 *   background).  No blank or space characters are permitted as part of a
 *   name. No distinction is made between upper and lower case.  The first
 *   character must be an alpha character.  The last character must not be
 *   a minus sign or period.  [...]
 *   Single character names or nicknames are not allowed.
 */
int
looks_like_domain_name(const char *str)
{
  const char *p;

  if (str == NULL || *str == '\0')
	return(0);

  if (!isalnum((int) *str))
	return(0);

  for (p = str + 1; *p != '\0'; p++) {
	if (*p == '.') {
	  if (*(p - 1) == '.')
		break;
	}
	else if (!isalnum((int) *p) && *p != '-')
	  break;
  }

  if (*p != '\0' || *(p - 1) == '.' || *(p - 1) == '-')
	return(0);

  /* The most-significant component must be alphabetic (e.g., ".com"). */
  p--;
  while (p >= str && *p != '.') {
	if (!isalpha((int) *p))
	  break;
	p--;
  }

  if (*p == '.' || p < str)
	return(1);

  return(0);
}

/*
 * IP_EXPR is an IP address in standard "." notation, possibly followed by a
 * mask specification.  It may be any of:
 * 1) A complete, standard IP address
 * 2) A partial IP address ("192.168" is equivalent to "192.168/16")
 * 3) A complete, standard IP address immediately followed by any of:
 *    a) a '/' or ':' and a complete, standard IP address (the mask)
 *    b) a '/' and a number between 0 and 31 (CIDR specification)
 * See RFC 4632
 * Return 0 if ok and values through ADDRP and MASKP, -1 on error.
 */
int
parse_ip_expr(char *ip_expr, struct in_addr *addrp, struct in_addr *maskp)
{
  int ndots;
  unsigned int mask_nbits;
  char *canon_ip, *ip, *mp;
  struct in_addr addr, mask_addr;
  Ds ds;

  mask_nbits = 0;

  /* If there's a mask specification, point to it. */
  if ((mp = strchr(ip_expr, '/')) == NULL)
	mp = strchr(ip_expr, ':');

  ds_init(&ds);

  if (mp != NULL) {
	ds_concatn(&ds, ip_expr, mp - ip_expr);
	ip = ds_buf(&ds);
	mp++;
	if (is_digit_string(mp)) {
	  if ((mask_nbits = atoi(mp)) > 32)
		return(-1);
	  maskp->s_addr = htonl(~0 << (32 - mask_nbits));
	}
	else if (is_ip_addr(mp, &mask_addr))
	  *maskp = mask_addr;
	else
	  return(-1);
  }
  else
	ip = ip_expr;

  if (!looks_like_ip_addr(ip) || (ndots = strchrcount(ip, '.')) > 3)
	return(-1);
  if (mp != NULL && ndots != 3)
	return(-1);

  if (mp == NULL) {
	/* No mask specification. */
	if (ndots == 0) {
	  mask_nbits = 8;
	  canon_ip = ds_xprintf("%s.0.0.0", ip);
	}
	else if (ndots == 1) {
	  mask_nbits = 16;
	  canon_ip = ds_xprintf("%s.0.0", ip);
	}
	else if (ndots == 2) {
	  mask_nbits = 24;
	  canon_ip = ds_xprintf("%s.0", ip);
	}
	else { 
	  /* Probably a complete, standard IP address */
	  mask_nbits = 32;
	  canon_ip = ip;
	}
	maskp->s_addr = htonl(~0 << (32 - mask_nbits));
  }
  else
	canon_ip = ip;

  if (inet_aton(canon_ip, &addr) == 0)
	return(-1);

  *addrp = addr;

  log_msg((LOG_TRACE_LEVEL,
		   "parse_ip_expr: \"%s\" --> \"%s/%d\" --> %s with mask %s",
		   ip_expr, canon_ip, mask_nbits,
		   inet_ntoa(*addrp), inet_ntoa(*maskp)));

  return(0);
}

/*
 * Given two domain names, test if they map to the same IP address (i.e.,
 * they are aliases for the same host).
 * Return 1 if they do, 0 if they do not, or -1 if an error occurs.
 */
int
match_domain_names(char *dn1, char *dn2)
{
  int i;
  struct hostent *host;
  struct in_addr addr;

  if (!looks_like_domain_name(dn1) || !looks_like_domain_name(dn2))
	return(-1);

  /* Note that endhostent() is never called. */
  if ((host = gethostbyname((const char *) dn1)) == NULL)
	return(-1);

  /*
   * If DN1 is not the official hostname (so it's an alias), check if
   * DN2 is the official hostname.
   */
  if (!strcaseeq(dn1, host->h_name) && strcaseeq(dn2, host->h_name))
	return(1);

  /* Check if DN2 is an alias for DN1. */
  for (i = 0; host->h_aliases[i] != NULL; i++) {
    char *a;

    a = host->h_aliases[i];
    if (strcaseeq(a, dn2))
      return(1);
  }

  /*
   * One last possibility... seen on Mac OS X 10.8.2, where DN1 is the offical
   * hostname (in /etc/hosts) and DN2 is an alias but did not appear in the
   * list of aliases.  Looking up DN2 (the alias) returns a hostent where
   * h_name is DN1, and DN2 still does not appear in h_aliases.
   */
  if ((host = gethostbyname((const char *) dn2)) == NULL)
	return(-1);

  if (strcaseeq(dn1, host->h_name))
	return(1);

  /* Check if DN1 is an alias for DN2. */
  for (i = 0; host->h_aliases[i] != NULL; i++) {
    char *a;

    a = host->h_aliases[i];
    if (strcaseeq(a, dn1))
      return(1);
  }

  return(0);
}

/*
 * Test if any IP address for DOMAIN_NAME maps within the IP range described by
 * MATCH_ADDR and MATCH_MASK.
 * Return 1 if it does, 0 if it does not, or -1 if an error occurs.
 */
int
match_domain_name_ip(char *domain_name, struct in_addr match_addr,
					 struct in_addr match_mask)
{
  int i;
  struct hostent *host;
  struct in_addr addr;

  if (!looks_like_domain_name(domain_name))
	return(-1);

  /* Note that endhostent() is never called. */
  if ((host = gethostbyname((const char *) domain_name)) == NULL)
	return(-1);
  if (host->h_addrtype != AF_INET)
	return(-1);

  for (i = 0; host->h_addr_list[i] != NULL; i++) {
	addr = *(struct in_addr *)  host->h_addr_list[i];

	if ((addr.s_addr & match_mask.s_addr) == match_addr.s_addr)
	  return(1);
  }

  return(0);
}

/*
 * Determine whether the IP address, which is in standard "." notation,
 * matches DOMAIN_NAME; that is, whether IP is equivalent or is more specific.
 * Since an IP address may map to multiple names, each of those names is
 * checked.
 * Comparisons of domain names are case insensitive.
 * Return 1 if the match is successful, 0 if not, and -1 on error.
 */
int
match_ip_domain_name(const char *ip, char *domain_name)
{
  int i;
  struct hostent *host;
  struct in_addr addr;

  if (!looks_like_ip_addr(ip) || strchrcount(ip, '.') != 3)
	return(-1);

  if (inet_aton(ip, &addr) == 0)
	return(-1);

  if (!looks_like_domain_name(domain_name))
	return(-1);

  /* Note that endhostent() is never called. */
  if ((host = gethostbyaddr((const char *) &addr, sizeof(addr), AF_INET))
	  == NULL)
	return(-1);

  if (match_subdomain_name(host->h_name, domain_name) == 1)
	return(1);

  for (i = 0; host->h_aliases[i] != NULL; i++) {
	char *a;

	a = host->h_aliases[i];
	if (match_subdomain_name(a, domain_name) == 1)
	  return(1);
  }

  return(0);
}

/*
 * Determine whether the IP address, which is in standard "." notation,
 * matches ADDR while taking into account MASK.
 * Return 1 if it does, 0 if not, and -1 on error.
 */
int
match_ip(const char *ip, struct in_addr match_addr, struct in_addr match_mask)
{
  struct in_addr addr;

  if (!looks_like_ip_addr(ip) || strchrcount(ip, '.') != 3)
	return(-1);

  if (inet_aton(ip, &addr) == 0)
	return(-1);

  if ((addr.s_addr & match_mask.s_addr) == match_addr.s_addr)
	return(1);
  return(0);
}

/*
 * Given two domain names (we'll validate them syntactically), test if
 * SUBDOMAIN is (lexically) the same as DOMAIN or a subdomain of DOMAIN.
 * For example, "foo.apache.org" is a subdomain of "apache.org",
 * "cnn.com" is a subdomain of "com", and "foo.net" is a subdomain of
 * "foo.net".
 * The SUBDOMAIN must consist of at least two components, so "com" is invalid,
 * for example.
 * Return -1 if either of the two arguments is invalid, otherwise 0 if the test
 * fails and 1 if it succeeds.
 */
int
match_subdomain_name(const char *subdomain, const char *domain)
{
  char *d;

  if (!looks_like_domain_name(subdomain) || !looks_like_domain_name(domain))
	return(-1);

  /* Insist that the subdomain has at least one dot. */
  if (strchr(subdomain, (int) '.') == NULL)
	return(-1);

  if (strcaseeq(subdomain, domain))
	return(1);

  d = ds_xprintf(".%s", domain);
  if (strcasesuffix(subdomain, strlen(subdomain), d))
	return(1);

  return(0);
}

/*
 * Test if ADDR_PATTERN is a valid pattern that matches REMOTE_ADDR,
 * which must be an IP address in dot notation.
 * Return 1 if so, 0 if not, or -1 if either argument is invalid.
 *
 * ADDR_PATTERN is (almost) an IP address in dot notation, except that
 * each octet spec in ADDR_PATTERN can either be a decimal
 * number (from 0 to 255), or a range specification; an octet spec is matched
 * against the corresponding octet in REMOTE_ADDR.
 * Missing octets in ADDR_PATTERN are low-order bits that are assumed to
 * match anything.  
 * A range specification appears within square brackets (e.g., [12:30])
 *
 * Examples that return 1:
 *   match_ip_addr_range("192.168", "192.168.10.8")
 *   match_ip_addr_range("192.168.[200]", "192.168.10.8")
 *   match_ip_addr_range("192.168.[9:30,18,200:#]", "192.168.10.8")
 *   match_ip_addr_range("[0:255].[0:255].[0:#].[0:255]", "192.168.10.8")
 */
int
match_ip_addr_range(char *addr_pattern, char *remote_addr)
{
  int i;
  char *p, *q;
  Dsvec *a_vec, *r_vec;

  if (addr_pattern == NULL || *addr_pattern == '\0')
	return(-1);

  if ((a_vec = strsplit(addr_pattern, ".", 0)) == NULL)
	return(-1);
  if (dsvec_len(a_vec) > 4)
	return(-1);

  if ((r_vec = strsplit(remote_addr, ".", 0)) == NULL)
	return(-1);
  if (dsvec_len(r_vec) != 4)
	return(-1);

  for (i = 0; i < dsvec_len(r_vec); i++) {
	char *a_str, *r_str;
	unsigned int a_val, r_val;

	if (i == dsvec_len(a_vec))
	  break;

	r_str = (char *) dsvec_ptr_index(r_vec, i);
	if (strnum(r_str, STRNUM_UI, &r_val) == -1)
	  return(-1);
	if (r_val > 255)
	  return(-1);

	a_str = (char *) dsvec_ptr_index(a_vec, i);
	if (*a_str == '\0') {
	  /* This happens with "[10]" and "[10].", for instance. */
	  break;
	}
	if (strnum(a_str, STRNUM_UI, &a_val) == 0) {
	  if (a_val > 255)
		return(-1);
	  if (a_val != r_val)
		return(0);
	}
	else if (a_str[0] == '[') {
	  int st;
	  char *errmsg, *range_spec;
	  static Range_syntax rs = { ',', ":", "#", 0 };

	  log_msg((LOG_TRACE_LEVEL, "Check pat %s against %s\n", a_str, r_str));
	  if ((range_spec = strextract(a_str, "[", "]")) == NULL)
		return(-1);
	  st = range_test(r_val, range_spec, &rs, &errmsg);
	  if (st != 1)
		return(st);
	}
	else
	  return(-1);
  }

  return(1);
}

/*
 * Test if REMOTE_ADDR (an IP address) or REMOTE_HOST (a domain name) matches
 * ADDRESS (a full or partial domain name, or a full or partial IP address
 * with an optional mask component).
 * An exception is that if ADDRESS is "all" (case insensitive), the test is
 * always successful.
 *
 * If REMOTE_ADDR or REMOTE_HOST is needed to evaluate ADDRESS but it is NULL,
 * the result is 0 (False); the caller may need to decide if this is
 * actually an error condition.
 *
 * Return 1 if a match is made, 0 if not, or -1 if an error occurs.
 */
int
is_from_address(char *address, char *remote_addr, char *remote_host,
				char **errmsg)
{
  int st;
  struct in_addr addr, mask;

  st = 0;

  if (strcaseeq(address, "all"))
	st = 1;
  else if (parse_ip_expr(address, &addr, &mask) != -1) {
	if (remote_addr == NULL)
	  st = 0;
	else {
	  if ((st = match_ip(remote_addr, addr, mask)) == -1) {
		if (errmsg != NULL)
		  *errmsg = "IP address matching error";
	  }
	}
  }
  else {
	if (remote_addr == NULL)
	  st = 0;
	else if ((st = match_ip_addr_range(address, remote_addr)) == -1) {
	  /* Assume it's a full or partial domain name. */
	  if (remote_host == NULL)
		st = 0;
	  else
		st = match_ip_domain_name(remote_addr, address);
	}
	else if (st == 0) {
	  if (remote_host != NULL)
		st = match_subdomain_name(remote_host, address);

	  if (st == -1) {
		if (errmsg != NULL)
		  *errmsg = "Domain name matching error";
	  }
	}
  }

  return(st);
}

/*
 * RFC 822, 2821, 2822
 * Mailbox = Local-part "@" Domain
 * Local-part = Dot-string / Quoted-string
 *           ; MAY be case-sensitive
 * Dot-string = Atom *("." Atom)
 * Atom = 1*atext
 * Quoted-string = DQUOTE *qcontent DQUOTE
 * qtext    =     NO-WS-CTL /     ; Non white space controls
 *                %d33 /          ; The rest of the US-ASCII
 *                %d35-91 /       ;  characters not including "\"
 *                %d93-126        ;  or the quote character
 * qcontent        =       qtext / quoted-pair
 * quoted-pair     =       ("\" text) / obs-qp
 * obs-qp          =       "\" (%d0-127)
 * String = Atom / Quoted-string
 * NO-WS-CTL       =     %d1-8 /         ; US-ASCII control characters
 *                       %d11 /          ;  that do not include the
 *                       %d12 /          ;  carriage return, line feed,
 *                       %d14-31 /       ;  and white space characters
 *                       %d127
 * text            =     %d1-9 /         ; Characters excluding CR and LF
 *                       %d11 /
 *                       %d12 /
 *                       %d14-127 /
 *                       obs-text
 * obs-text        =     *LF *CR *(obs-char *LF *CR)
 * obs-char        =     %d0-9 / %d11 /          ; %d0-127 except CR and
 *                       %d12 / %d14-127         ;  LF
 * Domain = (sub-domain 1*("." sub-domain)) / address-literal
 * sub-domain = Let-dig [Ldh-str]
 * Let-dig = ALPHA / DIGIT
 * Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
 * address-literal = "[" IPv4-address-literal /
 *                       IPv6-address-literal /
 *                       General-address-literal "]"
 * IPv4-address-literal = Snum 3("." Snum)
 * Snum = 1*3DIGIT  ; representing a decimal integer
 *           ; value in the range 0 through 255
 *           ; See section 4.1.3
 * IPv6-address-literal = "IPv6:" IPv6-addr
 * IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
 * IPv6-hex  = 1*4HEXDIG
 * IPv6-full = IPv6-hex 7(":" IPv6-hex)
 * IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":"
 *            IPv6-hex)]
 *       ; The "::" represents at least 2 16-bit groups of zeros
 *       ; No more than 6 groups in addition to the "::" may be
 *       ; present
 * IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
 * IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
 * General-address-literal = Standardized-tag ":" 1*dcontent
 * dcontent        =       dtext / quoted-pair
 * dtext           =     NO-WS-CTL /     ; Non white space controls
 *                       %d33-90 /       ; The rest of the US-ASCII
 *                       %d94-126        ;  characters not including "[",
 *                                       ;  "]", or "\"
 * Standardized-tag = Ldh-str
 *           ; MUST be specified in a standards-track RFC
 *           ; and registered with IANA
 * atext  =       ALPHA / DIGIT / ; Any character except controls,
 *              "!" / "#" /     ;  SP, and specials.
 *              "$" / "%" /     ;  Used for atoms
 *              "&" / "'" /
 *              "*" / "+" /
 *              "-" / "/" /
 *              "=" / "?" /
 *              "^" / "_" /
 *              "`" / "{" /
 *              "|" / "}" /
 *              "~"
 * local-part
 *   The maximum total length of a user name or other local-part is 64
 *   characters.
 *  domain
 *   The maximum total length of a domain name or number is 255
 *   characters.
 *
 * RFC 822 email address syntax:
 * address     =  mailbox / group
 * mailbox     =  addr-spec / phrase route-addr
 * addr-spec   =  local-part "@" domain
 * phrase      =  1*word
 * route-addr  =  "<" [route] addr-spec ">"
 * word        =  atom / quoted-string
 * local-part  =  word *("." word)
 * atom        =  1*<any CHAR except specials, SPACE and CTLs>
 * specials    =  "(" / ")" / "<" / ">" / "@"
 *                    /  "," / ";" / ":" / "\" / <">
 *                    /  "." / "[" / "]"
 * qtext       =  <any CHAR excepting <">, "\" & CR, and including
 *                   linear-white-space>
 * quoted-pair =  "\" CHAR
 * quoted-string = <"> *(qtext / quoted-pair) <">
 * linear-white-space =  1*([CRLF] LWSP-char)
 * LWSP-char   =  SPACE / HTAB
 * CHAR        =  <any ASCII character>
 */

enum {
  RFC822_MAX_LOCAL_PART_LEN =  64,
  RFC822_MAX_DOMAIN_LEN     = 255
};

static int
rfc822_test(char *filename)
{
  int i, rc;
  char *addr;
  Dsvec *addrs;

  if ((addrs = dsvec_load_lines(NULL, filename)) == NULL)
	return(-1);

  rc = 0;
  for (i = 0; i < dsvec_len(addrs); i++) {
	addr = (char *) dsvec_ptr_index(addrs, i);
	if (rfc822_parse_address(addr, NULL, NULL) == -1) {
	  fprintf(stderr, "Address %d: parse failed: \"%s\"\n", i + 1, addr);
	  rc = -1;
	}
  }

  return(rc);
}

static const char *rfc822_specials = "()<>@,;:\\\".[]";

/*
 * Try the most common case: <username>@<domain>
 * where <username> does not contain any special characters, except
 * possibly non-adjacent periods (foo.bar@example.com).
 * In this case, every <word> must be an <atom> in the syntax.
 */
int
rfc822_parse_simple_address(char *addr, char **local_partp, char **domainp)
{
  char *at, *domain, *local_part, *p, *q;

  if (addr == NULL || *addr == '\0' || *addr == '@')
	return(-1);

  if ((at = strchr(addr, (int) '@')) == NULL)
	return(-1);

  local_part = strndup(addr, at - addr);
  domain = strdup(at + 1);
  if (!looks_like_domain_name(domain)) {
	/* No IP address allowed... */
	return(-1);
  }

  p = local_part;
  while (*p != '\0') {
	if ((q = strpbrk(p, rfc822_specials)) == NULL)
	  break;
	if (*q != '.' || *(q + 1) == '.')
	  return(-1);
	p = q + 1;
  }

  if (strlen(local_part) > RFC822_MAX_LOCAL_PART_LEN)
	return(-1);
  if (strlen(domain) > RFC822_MAX_DOMAIN_LEN)
	return(-1);

  if (local_partp != NULL)
	*local_partp = local_part;
  if (domainp != NULL)
	*domainp = domain;

  return(0);
}

/*
 * Parse RFC 822 email address ADDR.
 * At least for now, we are only interested in the case where ADDR
 * is an <addr-spec>.
 */
int
rfc822_parse_address(char *addr, char **local_partp, char **domainp)
{
  int st;

  if (rfc822_parse_simple_address(addr, local_partp, domainp) != -1)
	return(0);

  /*
   * XXX handle the case where a <word> can be an <atom> or a <quoted-string>
   */
  return(-1);
}

/*
 * STR is a (1) username, (2) jurisdiction name, (3) group name, or (4) an
 * IP address (numbers-and-dots notation) in the ACS syntax.
 * If the federation or jurisdiction name components are absent, the element
 * in the structure will be NULL; leave it to the caller to determine whether
 * this implies the "current" federation/jurisdiction, "any"
 * federation/ jurisdiction, etc.
 *
 * Determine which type it is, return the type, and set DACS_NAME to the
 * component parts (unless it's NULL, in which case the caller only wants
 * to know the type or if the syntax is correct).
 *
 * The type element DACS_NAME is set if we can tell what kind of name it is;
 * the return value will agree with that type only if the components of the
 * name are valid.  So if STR is a jurisdiction name but the jurisdiction
 * component contains an invalid character, the type element will be
 * DACS_JURISDICTION_NAME and the return value will be DACS_UNKNOWN_NAME.
 *
 * The syntax is one of:
 *   1. [[[federation::] | [::]] jurisdiction]:username
 *   2. [[federation::] | [::]] jurisdiction:
 *   3. [[federation::] | [::]] %[jurisdiction]:groupname
 *   4. IP address acceptable to inet_addr(3)
 */
DACS_name_type
parse_dacs_name(char *str, DACS_name *dacs_name)
{
  char *name, *p;
  DACS_name dn, *dnp;

  if (dacs_name == NULL)
	dnp = &dn;
  else
	dnp = dacs_name;

  dnp->federation = NULL;
  dnp->jurisdiction = NULL;
  dnp->username = NULL;
  dnp->type = DACS_UNKNOWN_NAME;

  /* Make a copy to slice and dice. */
  name = strdup(str);

  /*
   * Advance to the character that separates the federation name from
   * the jurisdiction (two of them, that is) or the character that
   * separates the jurisdiction name from the username.
   * We assume that the same character is used as this separator.
   */
  p = strchr(name, JURISDICTION_NAME_SEP_CHAR);

  if (p != NULL && *(p + 1) == JURISDICTION_NAME_SEP_CHAR) {
	/*
	 * Since we see two consecutive separators, the thing on the left,
	 * if anything, is the federation name.
	 */
	if (p == name) {
	  /* An empty federation name */
	}
	else {
	  *p = '\0';
	  dnp->federation = name;
	}

	name = p + 2;
	if (dnp->federation != NULL) {
	  /* Nothing follows the federation name. */
	  if (*name == '\0') {
		dnp->type = DACS_FEDERATION_NAME;
		if (!is_valid_federation_name(dnp->federation))
		  return(DACS_UNKNOWN_NAME);
		return(DACS_FEDERATION_NAME);
	  }
	}
	/*
	 * Since something follows the federation name, advance to the
	 * separator that follows the jurisdiction name, if any.
	 */
	p = strchr(name, JURISDICTION_NAME_SEP_CHAR);
  }

  if (*name == GROUP_NAME_PREFIX_CHAR) {
	/*
	 * This is a reference to a DACS group.
	 * The groupname must follow a jurisdiction name.
	 */
	if (p == NULL)
	  return(DACS_UNKNOWN_NAME);
	*p++ = '\0';

	if (*(name + 1) != '\0')
	  dnp->jurisdiction = name + 1;
	dnp->username = p;
	dnp->type = DACS_GROUP_NAME;

	if ((dnp->federation != NULL
		 && !is_valid_federation_name(dnp->federation))
		|| (dnp->jurisdiction != NULL
			&& !is_valid_jurisdiction_name(dnp->jurisdiction))
		|| !is_valid_username(dnp->username))
	  return(DACS_UNKNOWN_NAME);

	return(DACS_GROUP_NAME);
  }

  if (p != NULL) {
	/*
	 * This is a jurisdiction name if nothing follows the separator, else
	 * it is a username.
	 */
	if (*(p + 1) != '\0') {
	  /* Something follows the separator, so this must be a username. */
	  *p++ = '\0';
	  if (*name != '\0')
		dnp->jurisdiction = name;
	  dnp->username = p;
	  dnp->type = DACS_USER_NAME;

	  if ((dnp->federation != NULL
		   && !is_valid_federation_name(dnp->federation))
		  || (dnp->jurisdiction != NULL
			  && !is_valid_jurisdiction_name(dnp->jurisdiction))
		  || !is_valid_username(dnp->username))
		return(DACS_UNKNOWN_NAME);

	  return(DACS_USER_NAME);
	}

	/* Regardless, there must be a jurisdiction name. */
	*p = '\0';
	if (*name != '\0')
	  dnp->jurisdiction = name;
	dnp->type = DACS_JURISDICTION_NAME;

	if ((dnp->federation != NULL
		 && !is_valid_federation_name(dnp->federation))
		|| (dnp->jurisdiction != NULL
			&& !is_valid_jurisdiction_name(dnp->jurisdiction)))
	  return(DACS_UNKNOWN_NAME);

	return(DACS_JURISDICTION_NAME);
  }

  if (isdigit((int) *name)) {
	/* Check that it looks like a valid IP address. */
	if (inet_addr(name) != INADDR_NONE) {
	  dnp->username = name;
	  dnp->type = DACS_IP_NAME;
	  return(DACS_IP_NAME);
	}
  }

  return(DACS_UNKNOWN_NAME);
}

/*
 * Do some sanity checks on configuration values.  Extend as required.
 * Return -1 if there's a problem, 0 otherwise.
 */
static int
validate_conf(void)
{
  char *p;

  if ((p = conf_val(CONF_JURISDICTION_NAME)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing JURISDICTION_NAME?"));
	return(-1);
  }
  if (!is_valid_jurisdiction_name(p)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid JURISDICTION_NAME: \"%s\"", p));
	return(-1);
  }

  if ((p = conf_val(CONF_FEDERATION_NAME)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing FEDERATION_NAME?"));
	return(-1);
  }
  if (!is_valid_federation_name(p)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid FEDERATION_NAME: \"%s\"", p));
	return(-1);
  }

  if ((p = conf_val(CONF_FEDERATION_DOMAIN)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing FEDERATION_DOMAIN?"));
	return(-1);
  }
  if (!looks_like_domain_name(p)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid FEDERATION_DOMAIN: \"%s\"", p));
	return(-1);
  }

  return(0);
}

/*
 * Canonicalize a pathname by removing redundant slashes.
 */
static char *
canon_pathname(char *filename)
{
  char *p;
  Ds ds;

  ds_init(&ds);
  for (p = filename; *p != '\0'; p++) {
	if (*p == '/') {
	  while (*(p + 1) == '/')
		p++;
	}
	ds_appendc(&ds, (int) *p);
  }

  ds_appendc(&ds, (int) '\0');

  return(ds_buf(&ds));
}

#ifndef HAVE_LOCKF

/*
 * Exclusive lock, with blocking.
 */
static int
lock_ex(int fd, int whence, off_t len)
{
  struct flock lck;

  lck.l_type = F_WRLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  return(fcntl(fd, F_SETLKW, &lck));
}

/*
 * Exclusive lock, without blocking.
 */
static int
lock_exnb(int fd, int whence, off_t len)
{
  struct flock lck;

  lck.l_type = F_WRLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  return(fcntl(fd, F_SETLK, &lck));
}

/*
 * Shared lock, with blocking.
 */
static int
lock_sh(int fd, int whence, off_t len)
{
  struct flock lck;

  lck.l_type = F_RDLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  return(fcntl(fd, F_SETLKW, &lck));
}

/*
 * Shared lock, without blocking.
 */
static int
lock_shnb(int fd, int whence, off_t len)
{
  struct flock lck;

  lck.l_type = F_RDLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  return(fcntl(fd, F_SETLK, &lck));
}

/*
 * Test for an exclusive lock.
 */
static int
lock_test(int fd, int whence, off_t len)
{
  struct flock lck;

  /*
   * Get the first lock that blocks a read lock.
   */
  lck.l_type = F_RDLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  if (fcntl(fd, F_GETLK, &lck) == -1)
	return(-1);

  if (lck.l_type == F_UNLCK)
	return(0);

  /* It's locked. */
  errno = EAGAIN;
  return(-1);
}

/*
 * Unlock.
 */
static int
lock_un(int fd, int whence, off_t len)
{
  struct flock lck;

  /* XXX apparently this is necessary, but I'm not so sure... */
  if (fsync(fd) == -1)
	return(-1);

  lck.l_type = F_UNLCK;
  lck.l_start = 0;
  lck.l_len = len;
  lck.l_whence = whence;

  return(fcntl(fd, F_SETLK, &lck));
}

/*
 * An implementation of POSIX lockf() (record locking on files) using
 * fcntl().  FUNC is F_ULOCK, F_LOCK, F_TLOCK, or F_TEST.
 * This function provides blocking and non-blocking exclusive advisory locks.
 * The locked region is from the current offset in the file to the end.
 *
 * Note: this is implemented using fcntl(), which has bogus semantics with
 * respect to releasing locks: "all locks associated with a file for a given
 * process are removed when *any* file descriptor for that file is closed by
 * that process".  It seems that on FreeBSD at least, lockf() is implemented
 * via fcntl(), so it shares these semantics.
 * The flock() API is much better; though widely available, it is not in POSIX.
 * Semantics with respect to NFS mounted files are not well defined.
 * POSIX says that when FD refers to a shared memory object,
 * the effect of the F_SETFL, F_GETLK, F_SETLK, and F_SETLKW operations
 * of fcntl() are unspecified.
 */
int
lockf(int fd, int func, off_t size)
{
  int st;

  switch (func) {
  case F_ULOCK:
	st = lock_un(fd, SEEK_CUR, size);
	break;

  case F_LOCK:
	st = lock_ex(fd, SEEK_CUR, size);
	break;

  case F_TLOCK:
	st = lock_exnb(fd, SEEK_CUR, size);
	break;

  case F_TEST:
	st = lock_test(fd, SEEK_CUR, size);
	break;

  default:
	errno = ENOTSUP;
	st = -1;
  }

  return(st);
}
#endif

/*
 * Return 1 if LOCKFILE exists and set REMAINING_SECS to the number of
 * seconds remaining in the life of the lock.
 * Return 0 if LOCKFILE does not exist.
 * Return -1 on error.
 */
int
check_lock(char *lockfile, unsigned int maxage_secs,
		   unsigned int *remaining_secs)
{
  unsigned int age, r;
  struct stat statb;

  if (stat(lockfile, &statb) != -1) {
	time_t now;

	now = time(NULL);
	if (now < statb.st_mtime) {
	  /* huh? */
	  now = statb.st_mtime;
	}

	age = now - statb.st_mtime;
	if (age >= maxage_secs)
	  r = 0;
	else
	  r = maxage_secs - age;

	if (remaining_secs != NULL)
	  *remaining_secs = r;

	return(1);
  }
  else if (errno != ENOENT) {
	log_err((LOG_WARN_LEVEL, "Could not stat \"%s\"", lockfile));
	return(-1);
  }

  return(0);
}

/*
 * Concurrency locking via a lock file in the filesystem.
 * If LOCKFILE does not exist, create it.
 * If LOCKFILE does exist, test if it is older than MAXAGE_SECS; if so,
 * update its modification time, otherwise indicate failure.
 * Return 1 if LOCKFILE is already locked but the lock hasn't expired,
 * and set REMAINING_SECS to the time remaining.
 * Return -1 if LOCKFILE can't be created or other error.
 * Return 0 if LOCKFILE was created or updated.
 *
 * One application of the lock file is to limit concurrent attempts to
 * a crack a particular user's account via dacs_authenticate.
 * This obviously does not limit concurrent attempts to crack different
 * accounts, however.
 * XXX This might be improved by locking via a Store.
 * XXX This could cause Denial of Service problems... while a Bad Guy
 * repeatedly tries to authenticate as someone and fails, that someone
 * will be locked out.
 */
int
set_lock(char *lockfile, unsigned int maxage_secs,
		 unsigned int *remaining_secs)
{
  int st;
  unsigned int r;
  FILE *fp;

  if (remaining_secs != NULL)
	*remaining_secs = 0;

  if ((st = check_lock(lockfile, maxage_secs, &r)) == 0) {
	if ((fp = create_temp_file(lockfile)) == NULL) {
	  log_err((LOG_WARN_LEVEL, "Can't open/create lockfile \"%s\"", lockfile));
	  return(-1);
	}

	fclose(fp);
	return(0);
  }
  else if (st == 1) {
	if (r == 0) {
	  log_msg((LOG_INFO_LEVEL, "Updating expired lockfile \"%s\"", lockfile));
	  if (utimes(lockfile, NULL) == -1) {
		log_err((LOG_WARN_LEVEL, "Can't set modtime for lockfile \"%s\"",
				 lockfile));
		return(-1);
	  }
	  return(0);  
	}
	if (remaining_secs != NULL)
	  *remaining_secs = r;
	return(1);
  }
  else
	return(-1);
}

int
remove_lock(char *lockfile)
{
  int st;

  if ((st = unlink(lockfile)) == -1) {
	if (errno != ENOENT)
	  log_err((LOG_WARN_LEVEL, "Can't remove lockfile \"%s\"", lockfile));
  }
  else
	log_msg((LOG_TRACE_LEVEL, "Removed lockfile \"%s\"", lockfile));

  return(st);
}

char *
get_dacs_app_name(void)
{

  return(dacs_app_name != NULL ? dacs_app_name : "dacs");
}

char *
set_dacs_app_name(char *app_name)
{

  return((dacs_app_name = strdup(app_name)));
}

/*
 * Return a (presumably) unique pathname or a pathname with a given suffix.
 * The generated pathname is always absolute.
 * If the configured location for the temp directory is not absolute,
 * we prepend DACS_HOME to it.
 * The filename (the last component of the pathname) consists of the
 * application name, a separator character, and a possibly empty suffix.
 * If SUFFIX is non-NULL, use it as the suffix, otherwise use a random
 * suffix.  We assume SUFFIX is syntactically ok to use, but most punctuation
 * characters are percent-encoded to protect against embedded slashes
 * and backslashes (making a suffix that starts with ".." safe).
 * This makes it safe in the event that a user has effectively selected
 * the suffix (e.g., by a username argument).
 * If SUFFIX is the empty string, the result is a pathname that will be the
 * same for all instances of this application; this can be used as a lock
 * file for the application.
 */
char *
create_temp_filename(char *suffix)
{
  char *abs, *app, *dir, *filename, *s;

  /* XXX no slashes? */
  app = get_dacs_app_name();

  if ((dir = conf_val(CONF_TEMP_DIRECTORY)) == NULL)
	dir = DEFAULT_TEMP_DIRECTORY;

  if (dir[0] != '/')
	abs = DACS_HOME;
  else
	abs = "";

  if (suffix == NULL)
	s = crypto_make_random_string(NULL, TEMP_FILENAME_RANDOM_LENGTH);
  else
	s = url_encode(suffix, 0);

  if (*s != '\0')
	filename = canon_pathname(ds_xprintf("%s/%s/%s%c%s",
										 abs, dir, app,
										 TEMP_FILENAME_SEP_CHAR, s));
  else
	filename = canon_pathname(ds_xprintf("%s/%s/%s", abs, dir, app));

  return(filename);
}

/*
 * Create FILENAME for our exclusive use.
 * We assume all directory components already exist.
 */
FILE *
create_temp_file(char *filename)
{
  int fd;
  FILE *fp;

  if ((fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) {
	log_err((LOG_ERROR_LEVEL, "Couldn't create \"%s\"", filename));
	return(NULL);
  }

  if ((fp = fdopen(fd, "w")) == NULL) {
	log_err((LOG_ERROR_LEVEL, "Couldn't open \"%s\" as a stream", filename));
	return(NULL);
  }

  return(fp);
}

static void
proc_lock_cleanup(void *arg)
{
  Proc_lock *lock;

  lock = (Proc_lock *) arg;
  proc_lock_delete(lock);
}

/*
 * Per-process exclusive resource locking.
 * The (host-wide) name of the resource is NAME.
 * When a resource is locked, other processes requesting the lock will block.
 * Arrange for the lock to be automatically deleted when the process exits.
 * Since DACS processes tend to be relatively short-lived, this course-grained
 * mechanism ought to be satisfactory and is easy to use; it is obviously
 * non-optimal in cases where resource sharing would be possible.
 * The locks are placed on temporary files that represent the resources;
 * the files should not be used or accessed for any other purpose.
 *
 * Note that deadlock is possible if two or more processes require two or
 * more of the same set of locks; e.g., ProcA needs lock1, ProcB needs lock2,
 * ProcA needs lock2, ProcB needs lock1.
 */
Proc_lock *
proc_lock_create(char *name)
{
  int fd;
  char *dir, *lock_file;
  Proc_lock *lock;

  lock = ALLOC(Proc_lock);

  lock->type = F_ULOCK;
  lock->fd = -1;
  lock->lock_file = NULL;

  if ((dir = conf_val(CONF_TEMP_DIRECTORY)) == NULL)
	dir = DEFAULT_TEMP_DIRECTORY;

  if (dir[0] != '/')
	lock_file = ds_xprintf("%s/%s/%s", DACS_HOME, dir, name);
  else
	lock_file = ds_xprintf("%s/%s", dir, name);

  if ((fd = open(lock_file, O_WRONLY | O_CREAT, 0600)) == -1) {
	log_err((LOG_ERROR_LEVEL, "Could not create \"%s\"", lock_file));
	return(NULL);
  }

  if (lockf(fd, F_LOCK, 0) == -1) {
	log_err((LOG_ERROR_LEVEL, "Could not lock \"%s\"", lock_file));
	return(NULL);
  }

  lock->fd = fd;
  lock->lock_file = lock_file;
  lock->type = F_LOCK;

  dacs_atexit(proc_lock_cleanup, lock);

  return(lock);
}

/*
 * Set or reset an exclusive lock.
 */
int
proc_lock_set(Proc_lock *lock)
{

  if (lock == NULL || lock->fd == -1)
	return(-1);

  if (lock->type == F_LOCK)
	return(0);
  if (lock->type != F_ULOCK)
	return(-1);

  if (lockf(lock->fd, F_LOCK, 0) == -1) {
	log_err((LOG_ERROR_LEVEL, "Could not lock \"%s\"", lock->lock_file));
	return(-1);
  }
  lock->type = F_LOCK;

  return(0);
}

/*
 * Unset an exclusive lock.
 */
int
proc_lock_unset(Proc_lock *lock)
{

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

  if (lock->fd == -1)
	return(0);

  if (lock->type == F_ULOCK)
	return(0);
  if (lock->type != F_LOCK)
	return(-1);

  if (lockf(lock->fd, F_ULOCK, 0) == -1) {
	log_err((LOG_ERROR_LEVEL, "Could not unlock \"%s\"", lock->lock_file));
	return(-1);
  }
  lock->type = F_ULOCK;
  lock->fd = -1;

  return(0);
}

/*
 * Unset and delete an exclusive lock.
 */
int
proc_lock_delete(Proc_lock *lock)
{

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

  if (lock->fd != -1)
	proc_lock_unset(lock);

  if (lock->lock_file != NULL && unlink(lock->lock_file) == -1) {
	log_err((LOG_ERROR_LEVEL, "Could not unlink \"%s\"", lock->lock_file));
	return(-1);
  }
  lock->lock_file = NULL;

  free(lock);

  return(0);
}

int
file_exists(char *path)
{
  int st;
  struct stat sb;

  if ((st = stat(path, &sb)) == -1)
	return(0);

  return(1);
}

/*
 * Return 1 if PATH exists and is a directory, 0 if it exists but is not
 * a directory, and -1 if it does not exist or an error occurs.
 */
int
is_directory(char *path)
{
  int st;
  struct stat sb;

  if ((st = stat(path, &sb)) == -1)
	return(-1);

  if (!S_ISDIR(sb.st_mode))
	return(0);

  return(1);
}

int
load_from_store(char *store_name, char *pathname, char **buf, size_t *size)
{
  int rc;
  char *dir, *filename, *p;
  Vfs_directive *vd;
  Vfs_handle *h;

  if (pathname[0] == '\0' || (pathname[0] == '/' && pathname[1] == '\0'))
	return(-1);

  if ((p = strrchr(pathname, '/')) == NULL) {
	filename = strdup(pathname);
	dir = strdup("./");
  }
  else {
	dir = strdup(pathname);
	dir[p - pathname] = '\0';
	filename = strdup(pathname + (p - pathname + 1));
  }

  vd = vfs_init_directive(NULL);
  vd->item_type = "";
  vd->store_name = strdup(store_name);
  vd->naming_context = strdup(dir);

  if ((rc = vfs_open(vd, &h)) != -1) {
	rc = vfs_get(h, filename, (void **) buf, size);
	vfs_close(h);
  }

  return(rc);
}

static char *
get_editor_name(void)
{
  char *p;

  if ((p = getenv("EDITOR")) == NULL)
	p = DEFAULT_EDITOR;

  return(p);
}

int
edit_file(char *filename)
{
  char *edit_command, *p;

  edit_command = ds_xprintf("%s %s", get_editor_name(), filename);

  while (1) {
	system(edit_command);
	fprintf(stdout, "What now? [quit|edit|abort] ");
	fflush(stdout);
	if ((p = ds_gets(NULL, stdin)) == NULL)
	  return(-1);
	if (strneq(p, "abort", 5))
	  return(-1);
	if (p[0] == 'q' || p[0] == '\n')
	  break;
  }

  return(0);
}

/*
 * Read up to BUFLEN bytes from FD into BUF.
 * If NOBLOCK is non-zero, return immediately instead of blocking if no
 * input is available (assumes the file descriptor has been set to O_NONBLOCK
 * by the caller).
 * Return the number of bytes copied into BUF or -1 if an error occurs.
 */
ssize_t
read_buffer(int fd, char *buf, size_t buflen, int noblock)
{
  char *p;
  size_t nrem;
  ssize_t n;

  nrem = buflen;
  p = buf;

  while (nrem > 0) {
	n = read(fd, (void *) p, nrem);
	if (n == 0)
	  break;
	if (n == -1) {
	  if (errno == EAGAIN) {
		if (noblock)
		  break;
		/* XXX Block until fd is readable? */
		continue;
	  }
	  return(-1);
	}
	p += n;
	nrem -= n;
  }

  n = (p - buf);
  return(n);
}

/*
 * Write BUFLEN bytes from BUF to FD.
 * Return -1 if an error occurs, 0 otherwise.
 */
int
write_buffer(int fd, char *buf, size_t buflen)
{
  char *p;
  ssize_t n;
  size_t nrem;

  nrem = buflen;
  p = buf;
  while (1) {
	n = write(fd, (void *) p, nrem);
	if (n == -1) {
	  if (errno == EAGAIN) {
		/* XXX Block until fd is writable? */
		continue;
	  }
	  return(-1);
	}
	if ((size_t) n == nrem)
	  break;
	nrem -= n;
	p += n;
  }

  return(0);
}

/****************************************************************************/

typedef enum {
  EMITTED_NO_HEADER       = 0,
  EMITTED_HTML_HEADER     = 1,
  EMITTED_PLAIN_HEADER    = 2,
  EMITTED_XML_HEADER      = 3,
  EMITTED_REDIRECT_HEADER = 4,
  EMITTED_HTTP_HEADER     = 5,
  EMITTED_JSON_HEADER     = 6,
  EMITTED_TRAILER         = 7
} Emitted_header;

static Emitted_header emitted_header_type = EMITTED_NO_HEADER;

char *emit_html_attrname_color = "Blue";

int emit_content_type_header = 1;

Emit_format emit_format_default = EMIT_FORMAT_DEFAULT;
static Emit_format emit_format = EMIT_FORMAT_DEFAULT;

typedef struct Emit_format_tab {
  char *name;
  Emit_format format;
} Emit_format_tab;

static Emit_format_tab emit_format_tab[] = {
  { "HTML",      EMIT_FORMAT_HTML },
  { "PLAIN",     EMIT_FORMAT_PLAIN },
  { "TEXT",      EMIT_FORMAT_TEXT },
  { "PHP",       EMIT_FORMAT_PHP },
  { "XML",       EMIT_FORMAT_XML },
  { "XMLDTD",    EMIT_FORMAT_XMLDTD },
  { "XMLSIMPLE", EMIT_FORMAT_XMLSIMPLE },
  { "XMLSCHEMA", EMIT_FORMAT_XMLSCHEMA },
  { "JSON",      EMIT_FORMAT_JSON },
  { "FILE",      EMIT_FORMAT_FILE },
  { NULL,        EMIT_FORMAT_UNKNOWN }
};

static Emit_format_tab *
lookup_emit_format_by_name(char *name)
{
  int i;

  for (i = 0; emit_format_tab[i].name != NULL; i++) {
	if (strcaseeq(name, emit_format_tab[i].name))
	  return(&emit_format_tab[i]);
  }
  return(NULL);
}

static Emit_format_tab *
lookup_emit_format_by_format(Emit_format format)
{
  int i;

  for (i = 0; emit_format_tab[i].name != NULL; i++) {
	if (format == emit_format_tab[i].format)
	  return(&emit_format_tab[i]);
  }
  return(NULL);
}

char *
get_emit_format(void)
{
  Emit_format_tab *t;

  if ((t = lookup_emit_format_by_format(emit_format)) != NULL)
	return(t->name);

  return(NULL);
}

int
set_emit_format(char *format)
{
  Emit_format_tab *tab;

  if (format == NULL)
	emit_format = emit_format_default;
  else if ((tab = lookup_emit_format_by_name(format)) != NULL)
	emit_format = tab->format;
  else
	return(-1);

  return(0);
}

int
test_emit_format(Emit_format fmt)
{

  return(fmt == emit_format);
}

int
test_emit_xml_format(void)
{

  return(emit_format == EMIT_FORMAT_XML
		 || emit_format == EMIT_FORMAT_XMLDTD
		 || emit_format == EMIT_FORMAT_XMLSIMPLE
		 || emit_format == EMIT_FORMAT_XMLSCHEMA);
}

void
element_hl(FILE *fp, char *s)
{
  fprintf(fp, "<b>%s</b>", s);
}

void
attribute_hl(FILE *fp, char *p, char *n, char *v)
{
  fprintf(fp, "%s <font color=\"%s\">%s</font>=\"%s\"",
		  p == NULL ? "" : p, emit_html_attrname_color, n, v);
}

static Html_header_conf const emit_html_conf_default = {
  0, 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

static Html_header_conf *emit_html_conf = NULL;

Html_header_conf *
emit_html_header_conf(Html_header_conf *new_conf)
{
  Html_header_conf *hc;

  hc = ALLOC(Html_header_conf);
  if (new_conf == NULL) {
	*hc = (emit_html_conf != NULL) ? *emit_html_conf : emit_html_conf_default;
	hc->html = test_emit_format(EMIT_FORMAT_HTML);
	return(hc);
  }

  *hc = *new_conf;
  emit_html_conf = hc;

  return(emit_html_conf);
}

/*
 * http://www.htmlhelp.com/tools/validator/doctype.html
 * http://www.w3.org/QA/2002/04/valid-dtd-list.html
 */
#ifdef USE_STRICT_HTML
#define DOCTYPE_FPI		"-//W3C//DTD HTML 4.01//EN"
#define DOCTYPE_URI		"http://www.w3.org/TR/html4/strict.dtd"
#else
#define DOCTYPE_FPI		"-//W3C//DTD HTML 4.01 Transitional//EN"
#define DOCTYPE_URI		"http://www.w3.org/TR/html4/loose.dtd"
#endif

/*
 * XXX These need to be renamed/refactored to separate content type specific
 * stuff (e.g., HTML) from HTTP headers.
 * XXX These should use the acs_emit_header... functions
 */

int
emit_html_header(FILE *fp, Html_header_conf *c)
{
  int i;
  char *h;
  Html_header_conf const *conf;

  if (c == NULL)
	conf = (emit_html_conf != NULL) ? emit_html_conf : &emit_html_conf_default;
  else
	conf = c;

  if (emitted_header_type == EMITTED_NO_HEADER) {
	/*
	 * Always emit the additional headers, if any.
	 * Should not include Status, which is separate.
	 */
	for (i = 0; i < dsvec_len(conf->headers); i++) {
	  h = (char *) dsvec_ptr_index(conf->headers, i);
	  log_msg((LOG_TRACE_LEVEL, "Emitting header: \"%s\"", h));
	  fprintf(fp, "%s\n", h);
	}

	/* RFC 2616, section 6.1 */
	if (conf->status_code != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Emitting Status: \"%s\"", conf->status_code));
	  if (conf->status_reason != NULL)
		fprintf(fp, "Status: %s %s\n", conf->status_code, conf->status_reason);
	  else
		fprintf(fp, "Status: %s\n", conf->status_code);
	}

	/* Just do a redirect? */
	if (conf->redirect_url != NULL) {
	  /*
	   * Redirect the client to the specified URL.
	   * RFC 2616, section 10.3
	   */
	  log_msg((LOG_TRACE_LEVEL, "Emitting redirect: \"%s\"",
			   conf->redirect_url));
	  fprintf(fp, "Location: %s\n", conf->redirect_url);

	  if (conf->status_code == NULL) {
		log_msg((LOG_TRACE_LEVEL, "Emitting Status: 302"));
		fprintf(fp, "Status: 302\n");
	  }

	  fprintf(fp, "\n");
	  emitted_header_type = EMITTED_REDIRECT_HEADER;

	  return(1);
	}

	if (((dacs_app_type != DACS_UTILITY
		  && dacs_app_type != DACS_UTILITY_OPT
		  && dacs_app_type != DACS_STANDALONE)
		 || c != NULL)
		&& emit_content_type_header
		&& conf->html)
	  fprintf(fp, "Content-Type: text/html\n\n");
	else
	  fprintf(fp, "\n");

	if (conf->html) {
	  fprintf(fp, "<!DOCTYPE HTML PUBLIC \"%s\"", DOCTYPE_FPI);
#ifdef DOCTYPE_URI
	  fprintf(fp, " \"%s\">\n", DOCTYPE_URI);
#else
	  fprintf(fp, ">\n");
#endif
	  fprintf(fp, "<html><head>");
	  fprintf(fp, "<title>%s</title>\n",
			  (conf->title == NULL) ? "" : conf->title);
	  if (conf->css != NULL)
		fprintf(fp,
				"<link rel=\"stylesheet\" href=\"%s\" type=\"text/css\">\n",
				conf->css);
	  if (conf->no_cache) {
		fprintf(fp, "<meta http-equiv=\"pragma\" content=\"no-cache\">\n");
		fprintf(fp, "<meta http-equiv=\"expires\" content=\"0\">\n");
	  }
	  fprintf(fp, "</head>\n");

	  if (conf->html_body) {
		fprintf(fp, "<body");
		if (conf->bgcolor != NULL)
		  fprintf(fp, " bgcolor=\"%s\"", conf->bgcolor);
		fprintf(fp, ">\n");
	  }
	  emitted_header_type = EMITTED_HTML_HEADER;
	}
	else
	  emitted_header_type = EMITTED_HTTP_HEADER;

	return(1);
  }

  return(0);
}

int
emit_html_header_status_line(FILE *fp, char *code, char *reason)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	Html_header_conf *hc;

	hc = emit_html_header_conf(NULL);
	hc->status_code = code;
	hc->status_reason = reason;

	return(emit_html_header(fp, hc));
  }

  return(0);
}

int
emit_http_header_status(FILE *fp, char *code, char *reason)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	Html_header_conf *hc;

	hc = emit_html_header_conf(NULL);
	hc->status_code = code;
	hc->status_reason = reason;
	hc->html = 0;

	return(emit_html_header(fp, hc));
  }

  return(0);
}

int
emit_http_header_redirect(FILE *fp, char *url)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	Html_header_conf *hc;

	hc = emit_html_header_conf(NULL);
	hc->redirect_url = url;
	hc->html = 0;

	return(emit_html_header(fp, hc));
  }

  return(0);
}

/*
 * Emit "safe redirection" that uses HTML to redirect (either automatically
 * via JavaScript or, if JavaScript is unavailable, manually by the user
 * following a link) instead of HTTP.
 */
int
emit_html_header_redirect(FILE *fp, Html_header_conf *ohc, char *redirect_url,
						  char *reason)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	char *url;
	Html_header_conf *hc;

	if (ohc == NULL) {
	  hc = emit_html_header_conf(NULL);
	  hc->title = "DACS Redirection";
	  hc->html_body = 0;
	  if (conf_val(CONF_CSS_PATH) != NULL)
		hc->css = ds_xprintf("%s/dacsdocs.css", conf_val(CONF_CSS_PATH));
	  else
		hc->css = CSS_DIR/**/"/dacsdocs.css";
	  url = redirect_url;
	}
	else {
	  hc = ohc;
	  if (redirect_url != NULL)
		url = redirect_url;
	  else
		url = hc->redirect_url;
	}

	emit_html_header(fp, hc);

	fprintf(fp, "<script>\n");
	fprintf(fp, "window.location.href=\"%s\";\n", url);
	fprintf(fp, "</script>\n");
	if (hc->bgcolor != NULL)
	  fprintf(fp, "<body bgcolor=\"%s\">\n", hc->bgcolor);
	else
	  fprintf(fp, "<body>\n");
	fprintf(fp, "<noscript>\n");
	fprintf(fp, "You are being redirected by <b>DACS</b>.<br/>\n");
	if (reason != NULL)
	  fprintf(fp, "<em>%s</em><br/>\n", reason);
	fprintf(fp, "Please click ");
	fprintf(fp, "<a href=\"%s\">here</a> to continue.\n", url);
	fprintf(fp, "</noscript>\n");
	emitted_header_type = EMITTED_REDIRECT_HEADER;

	return(1);
  }

  return(0);
}

int
emit_html_trailer(FILE *fp)
{
  int rc;

  if (emitted_header_type == EMITTED_HTML_HEADER) {
	fprintf(fp, "</body></html>\n");
	emitted_header_type = EMITTED_TRAILER;
	rc = 1;
  }
  else
	rc = 0;

  fflush(fp);
  return(rc);
}

/*
 * Generate an HTML form element.
 * URL is the value of the 'action' attribute, properly URL-encoded.
 * METHOD is the HTTP method to use (either GET or POST)
 * If the caller wants any query args that are attached to URL to be passed
 * with the form instead of in the 'action' attribute, it should pass a
 * non-zero NO_ACTION_ARGS.
 * If USE_URL_ENCODING is non-zero, the 'enctype' attribute will be
 * application/x-www-form-urlencoded, otherwise it will be multipart/form-data.
 * Additional attributes of the form element are FORM_ATTRS.
 * Additional attributes of the primary submit button are SUBMIT_ATTRS.
 * If CANCEL_ATTRS is non-NULL, it means that an additional submit button
 * is to be created with the given attributes.
 * ARGS become hidden form inputs, which may be preceded in the form by URL
 * query arguments if NO_ACTION_ARGS was specified; they will be URL-encoded.
 *
 * This can be used to convert a "GET" style method invocation into a
 * "POST" invocation - which cannot be done by redirection - by returning an
 * HTML document to a user for him to submit (or possibly cancel), and
 * therefore does not involve any proxying.
 *
 * Note: with Firefox (2.0.0.3), query arguments in URL are NOT passed
 * if METHOD is HTTP_GET_METHOD (but they are if METHOD is HTTP_POST_METHOD).
 */
Ds *
html_form(Ds *ods, char *url, Http_method method, int no_action_args,
		  int use_url_encoding, char *form_attrs, char *submit_attrs,
		  char *cancel_attrs, Kwv *args)
{
  Ds action, *ds;
  Kwv *qargs;
  Uri *uri;

  if (method != HTTP_GET_METHOD && method != HTTP_POST_METHOD)
	return(NULL);
  if ((uri = uri_parse(url)) == NULL)
	return(NULL);
  if (!streq(uri->scheme, "http") && !streq(uri->scheme, "https"))
	return(NULL);

  if (ods != NULL)
	ds = ods;
  else
	ds = ds_init(NULL);

  ds_init(&action);
  qargs = NULL;
  if (no_action_args) {
	/* Strip any query component from URL */
	ds_asprintf(&action, "%s/", uri_scheme_authority(uri));
	if (uri->path != NULL)
	  ds_asprintf(&action, "%s", uri->path);
	if (uri->fragment != NULL)
	  ds_asprintf(&action, "#%s", uri->fragment);
	if (uri->query_args != NULL)
	  qargs = query_args_to_kwv(uri->query_args);
  }
  else
	ds_set(&action, url);

  ds_asprintf(ds, "<form method=\"%s\" enctype=\"%s\" action=\"%s\"",
			  http_method_to_string(method),
			  use_url_encoding ? "application/x-www-form-urlencoded"
			  : "multipart/form-data", ds_buf(&action));

  if (form_attrs != NULL)
	ds_asprintf(ds, " %s", form_attrs);
  ds_asprintf(ds, ">\n");

  if (qargs != NULL && kwv_count(qargs, NULL) > 0) {
	Kwv_iter *iter;
	Kwv_pair *pair;

	iter = kwv_iter_begin(qargs, NULL);
	while ((pair = kwv_iter_next(iter)) != NULL) {
	  ds_asprintf(ds, "<input type=\"hidden\" name=\"%s\" value=\"%s\">\n",
				  pair->name, pair->val);
	}
	kwv_iter_end(iter);
  }

  if (args != NULL && kwv_count(args, NULL) > 0) {
	Kwv_iter *iter;
	Kwv_pair *pair;

	iter = kwv_iter_begin(args, NULL);
	while ((pair = kwv_iter_next(iter)) != NULL) {
	  ds_asprintf(ds, "<input type=\"hidden\" name=\"%s\" value=\"%s\">\n",
				  url_encode(pair->name, 0),
				  url_encode(pair->val, 0));
	}
	kwv_iter_end(iter);
  }

  ds_asprintf(ds, "<input type=\"submit\"");
  if (submit_attrs != NULL)
	ds_asprintf(ds, " %s", submit_attrs);
  ds_asprintf(ds, ">\n");

  if (cancel_attrs != NULL) {
	ds_asprintf(ds, "<input type=\"submit\"");
	ds_asprintf(ds, " %s", cancel_attrs);
	ds_asprintf(ds, ">\n");
  }

  ds_asprintf(ds, "</form>\n");

  return(ds);
}

int
html_form_test(char *url)
{
  Ds *ds;
  Kwv *args;

  args = kwv_init(4);
  kwv_add(args, "a", "1");
  kwv_add(args, "b", "2");
  if ((ds = html_form(NULL, url, HTTP_POST_METHOD, 0, 1,
					  NULL,
					  "value=\" Go! \"",
					  "value=\" Cancel! \" name=\"CANCEL\"",
					  args)) == NULL)
	return(-1);

  printf("%s", ds_buf(ds));

  return(0);
}

int
emit_plain_header(FILE *fp)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	if (emit_content_type_header)
	  fprintf(fp, "Content-Type: text/plain\n\n");
	emitted_header_type = EMITTED_PLAIN_HEADER;
	return(1);
  }

  return(0);
}

int
emit_plain_trailer(FILE *fp)
{

  emitted_header_type = EMITTED_TRAILER;
  fflush(fp);
  return(1);
}

int
emit_json_header(FILE *fp, char *object_name)
{

  if (emitted_header_type == EMITTED_NO_HEADER) {
	if (emit_content_type_header)
	  fprintf(fp, "Content-Type: application/json\n\n");

	if (object_name != NULL)
	  fprintf(fp, "{ %s: ", object_name);
	emitted_header_type = EMITTED_JSON_HEADER;
	return(1);
  }

  return(0);
}

int
emit_json_trailer(FILE *fp)
{

  emitted_header_type = EMITTED_TRAILER;
  if (fflush(fp) == -1) {
	log_err((LOG_ERROR_LEVEL, "JSON output stream flush failed"));
	return(-1);
  }

  return(1);
}

int
emit_xml_header(FILE *fp, char *dtd_name)
{

  if (!test_emit_xml_format())
	return(-1);

  if (emitted_header_type == EMITTED_NO_HEADER) {
	/* See RFC 2376 (http://www.rfc-editor.org/rfc/rfc2376.txt) */
	if (dacs_app_type != DACS_UTILITY
		&& dacs_app_type != DACS_UTILITY_OPT
		&& dacs_app_type != DACS_STANDALONE
		&& emit_content_type_header)
	  fprintf(fp, "Content-Type: text/xml\n\n");
	fprintf(fp, "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n");
			
	if (dtd_name != NULL) {
	  char *buf, *p;

	  if (test_emit_format(EMIT_FORMAT_XML)) {
		if ((p = conf_val(CONF_DTD_BASE_URL)) != NULL)
		  fprintf(fp, "<!DOCTYPE %s SYSTEM \"%s/%s.dtd\">\n",
				  dtd_name, p, dtd_name);
		else {
		  if (load_dtd(dtd_name, &buf) != -1) {
			fprintf(fp, "<!DOCTYPE %s [\n", dtd_name);
			fprintf(fp, "%s", buf);
			fprintf(fp, "]>\n");
			free(buf);
		  }
		  else {
			log_msg((LOG_ERROR_LEVEL, "Missing DTD: %s", dtd_name));
			dacs_fatal(NULL);
			/*NOTREACHED*/
		  }
		}
	  }
	  else if (test_emit_format(EMIT_FORMAT_XMLDTD)) {
		if (load_dtd(dtd_name, &buf) != -1) {
		  fprintf(fp, "<!DOCTYPE %s [\n", dtd_name);
		  fprintf(fp, "%s", buf);
		  fprintf(fp, "]>\n");
		  free(buf);
		}
		else {
		  log_msg((LOG_ERROR_LEVEL, "Missing DTD: %s", dtd_name));
		  dacs_fatal(NULL);
		  /*NOTREACHED*/
		}
	  }
	  else if (test_emit_format(EMIT_FORMAT_XMLSIMPLE)) {
		/* No DOCTYPE, so nothing to do. */
	  }
	  else if (test_emit_format(EMIT_FORMAT_XMLSCHEMA)) {
		/* No DOCTYPE, so nothing to do. */
	  }
	}
	emitted_header_type = EMITTED_XML_HEADER;
	return(1);
  }

  return(0);
}

int
emit_xml_trailer(FILE *fp)
{

  emitted_header_type = EMITTED_TRAILER;
  if (fflush(fp) == -1) {
	log_err((LOG_ERROR_LEVEL, "XML output stream flush failed"));
	return(-1);
  }

  return(1);
}

/*
 * Load the DTD that defines the root element DTD_NAME.
 * The caller is responsible for freeing the buffer.
 */
int
load_dtd(char *dtd_name, char **buf)
{
  int rc;
  char *dtd_filename;
  Vfs_directive *vd;
  Vfs_handle *handle;

  dtd_filename = ds_xprintf("%s%s", dtd_name, DTD_FILE_SUFFIX);

  rc = -1;
  if ((vd = vfs_lookup_item_type(ITEM_TYPE_DTDS)) == NULL)
	goto done;

  if (vfs_open(vd, &handle) == -1) {
	if (handle->error_msg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "load_dtd: %s", handle->error_msg));
	goto done;
  }

  if (vfs_get(handle, dtd_filename, (void **) buf, NULL) == -1) {
	if (handle->error_msg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "load_dtd: %s", handle->error_msg));
	vfs_close(handle);
	goto done;
  }

  if (vfs_close(handle) == -1)
	log_msg((LOG_ERROR_LEVEL, "load_dtd: vfs_close() failed"));
  rc = 0;

 done:
  if (rc == -1)
	log_msg((LOG_ERROR_LEVEL, "load_dtd: couldn't load \"%s\" from \"%s\"",
			 dtd_filename,
			 (vd == NULL || vd->naming_context == NULL)
			 ? "" : vd->naming_context));


  free(dtd_filename);

  return(rc);
}

char *
html_encode(char *str)
{
  char *p;
  Ds ds;

  if (str == NULL)
	return(NULL);
  if (*str == '\0')
	return("");

  ds_init(&ds);

  for (p = str; *p != '\0'; p++) {
	switch (*p) {
	case '<':
	  ds_asprintf(&ds, "&lt;");
	  break;
	case '>':
	  ds_asprintf(&ds, "&gt;");
	  break;
	case '&':
	  ds_asprintf(&ds, "&amp;");
	  break;
	case '\'':
	  ds_asprintf(&ds, "&apos;");
	  break;
	case '\"':
	  ds_asprintf(&ds, "&quot;");
	  break;
	case ' ':
	  ds_asprintf(&ds, "&nbsp;");
	  break;
	case '\n':
	  ds_asprintf(&ds, "<br/>\n");
	  break;
	default:
	  ds_asprintf(&ds, "%c", (int) *p);
	  break;
	}
  }

  return(ds_buf(&ds));
}

/*
 * XXX Partial implementation.
 */
void
text_html(FILE *fp, char *buf)
{

  if (fp == NULL)
	fprintf(stdout, "%s", html_encode(buf));
  else
	fprintf(fp, "%s", html_encode(buf));
}

void
env_html(FILE *fp)
{
  int i;
  extern char **environ;

  fprintf(fp, "<p>Environment:<br>\n");
  for (i = 0; environ[i] != NULL; i++)
	fprintf(fp, "%s<br>\n", environ[i]);
  fprintf(fp, "<p>\n");
}

void
hex_html(FILE *fp, unsigned char *buf, unsigned len, unsigned ncolumns)
{
  unsigned int ui;

  fprintf(fp, "<tt>");
  for (ui = 0; ui < len; ui++) {
	fprintf(fp, "%02X", buf[ui] & 0xff);
	if ((ui + 1) % ncolumns == 0)
	  fprintf(fp, "<br>");
	else
	  fprintf(fp, " ");
  }
  fprintf(fp, "</tt>");
  fprintf(fp, "<p>len=%d<p>\n", len);
}

Ds *
format_html_span(Ds *ods, char *class, char *text)
{
  Ds *ds;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  if (class != NULL)
	ds_asprintf(ds, "<span class=\"%s\">", class);
  if (text != NULL)
	ds_asprintf(ds, "%s", text);
  if ((class == NULL && text == NULL) || (class != NULL && text != NULL))
	ds_asprintf(ds, "</span>");

  return(ds);
}

/****************************************************************************/

int
get_logging_stream(char *file, FILE **fp, char **path, char **errmsg)
{
  char *lf;
  struct stat sb;
  FILE *logfp;

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

  lf = directory_name_interpolate(file, getenv("SERVER_NAME"),
								  getenv("SERVER_PORT"));
  if (lf == NULL) {
	*errmsg = ds_xprintf("Interpolation failed for log file \"%s\"", file);
	return(-1);
  }

  if (*lf == '/') {
	/* XXX file mode, if created */
	if ((logfp = fopen(lf, "a")) == NULL) {
	  if (is_windows_platform)
		logfp = NULL;
	  else
		logfp = stderr;
	}

	/*
	 * Don't bother checking the mode if we're looking at a device,
	 * most likely /dev/null.
	 */
	if (strprefix(lf, "/dev/") == NULL && (fstat(fileno(logfp), &sb) == -1
		|| (sb.st_mode & 0777) != LOG_FILE_MODE)) {
	  if (fchmod(fileno(logfp), LOG_FILE_MODE) == -1)
		fprintf(stderr, "Warning: unable to set mode of %s to %o: %s\n",
				lf, LOG_FILE_MODE, strerror(errno));
	}
  }
  else
	logfp = NULL;

  *fp = logfp;
  *path = lf;

  return(0);
}

/*
 * Set logging messages to be written to FP.
 * If STAGE is zero, DACS configuration directives are not available yet;
 * if it's non-zero, they are.
 * If called more than once for the same stage, return the current logfile
 * stream and optionally return the current logfile name.
 *
 * For stage 0, output will either go to stderr or to DACS_LOG, if that
 * symbol has been defined.
 * Logfile names are directory name interpolated.
 */
static FILE *
set_logging_stream(DACS_app_type app_type, FILE *fp, int stage,
				   char **logfile_path, char **errmsg)
{
  char *f, *path;
  FILE *logfp;
  static int current_stage = -1;
  static FILE *current_logfp = NULL;
  static char *current_logfile_path = NULL;

  if (stage == current_stage) {
	if (logfile_path != NULL)
	  *logfile_path = current_logfile_path;

	return(current_logfp);
  }

  /* Implies output will go to stderr. */
  f = NULL;

  if (stage == 0) {
	if (app_type == DACS_UTILITY
		|| app_type == DACS_UTILITY_OPT
		|| app_type == DACS_STANDALONE)
	  logfp = stderr;
	else {
#ifdef DACS_LOG
	  f = DACS_LOG;
#else
	  /*
	   * No configured default?  Write to stderr for now and perhaps
	   * there will be a configuration directive later.
	   */
      logfp = stderr;
#endif
	}
  }
  else if (stage == 1) {
	if (app_type == DACS_UTILITY
		|| app_type == DACS_UTILITY_OPT
		|| app_type == DACS_STANDALONE)
	  logfp = stderr;
	else if ((f = conf_val(CONF_LOG_FILE)) != NULL) {
	  if (fp != NULL && fp != stderr)
		fclose(fp);
	}
	else
	  logfp = fp;
  }
  else {
	*errmsg = "Unrecognized stage arg to set_logging_stream!";
	return(NULL);
  }

  if (f != NULL) {
	if (get_logging_stream(f, &logfp, &path, errmsg) == -1)
	  return(NULL);
  }
  else
	path = NULL;

  current_logfp = logfp;
  current_logfile_path = dacs_logfile_path = path;
  current_stage = stage;

  if (logfile_path != NULL)
	*logfile_path = current_logfile_path;

  return(current_logfp);
}

/*
 * Logging is configured in stages because it needs to be able to emit messages
 * as it starts up, and possibly before it has read its configuration
 * files.
 */
static FILE *
set_initial_logging(DACS_app_type app_type, char *progname, char **errmsg)
{
  int to_stderr;
  char *path;
  FILE *logfp;
  Log_desc *lf;
  static int done_init = 0;

  logfp = set_logging_stream(app_type, NULL, 0, &path, errmsg);

  if (trace_level)
	log_startup_level = LOG_TRACE_LEVEL;
  else if (verbose_level == 1 && log_startup_level > LOG_INFO_LEVEL)
	log_startup_level = LOG_INFO_LEVEL;
  else if (verbose_level > 1 && log_startup_level > LOG_DEBUG_LEVEL)
	log_startup_level = LOG_DEBUG_LEVEL;

  if (!done_init) {
	lf = log_init(logfp, 0, path, progname, log_startup_level, "");
	to_stderr = (logfp == stderr || logfp == NULL);
	log_set_user_callback(lf, logging_callback, (void *) &to_stderr);
	log_set_desc(lf, LOG_ENABLED);
	done_init = 1;
  }
  else
	log_set_level(log_current_logd, log_startup_level);

  return(logfp);
}

Log_level
dacs_set_log_startup_level(Log_level level)
{
  Log_level old_level;

  if (!log_is_valid_level(level))
	return(LOG_INVALID_LEVEL);

  old_level = log_startup_level;
  log_startup_level = level;

  return(old_level);
}

/*
 * Initialize logging using configuration directives.
 * Messages will be directed to FP (which can be stderr).
 * If ENABLED is non-zero, logging will be enabled now; if it is zero,
 * it will presumably be enabled later.
 */
void
dacs_log_init(FILE *fp, char *path, int enabled)
{
  int to_stderr;
  char *errmsg;
  Log_desc *logd;
  Log_level level;

  if (dacs_log_done_init)
	return;

  /*
   * The logging level is selected as follows:
   *  o the compile time default is overridden by a config directive
   *  o which is overridden by the log level command line flag
   *  o which is overridden by the verbose command line flag or directive
   *  o which is overridden by the trace command line flag or directive
   */
  errmsg = NULL;
  if (dacs_saw_command_line_log_level)
	level = log_startup_level;
  else {
	char *lev;

	if ((lev = conf_val(CONF_LOG_LEVEL)) != NULL) {
	  if ((level = log_lookup_level(lev)) == LOG_INVALID_LEVEL) {
		errmsg = "Invalid LOG_LEVEL directive";
		level = LOG_DEFAULT_LEVEL;
	  }
	}
	else
	  level = LOG_DEFAULT_LEVEL;
  }

  if (verbose_level == 1 && level > LOG_INFO_LEVEL)
	level = LOG_INFO_LEVEL;
  else if (verbose_level > 1 && level > LOG_DEBUG_LEVEL)
	level = LOG_DEBUG_LEVEL;
  if (trace_level)
	level = LOG_TRACE_LEVEL;

  logd = log_init(fp, 0, path, get_dacs_app_name(), level, "");

  if (fp == stderr || fp == NULL)
	to_stderr = 1;
  else
	to_stderr = 0;
  log_set_user_callback(logd, logging_callback, (void *) &to_stderr);
  log_set_desc(logd, enabled ? LOG_ENABLED : LOG_DISABLED);

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

  /* Setup a default filter. */
  if (conf_val(CONF_LOG_SENSITIVE) != NULL) {
	if (conf_val_eq(CONF_LOG_SENSITIVE, "yes"))
	  log_set_flags(logd, LOG_MATCH_NORMAL | LOG_MATCH_SENSITIVE);
	else if (!conf_val_eq(CONF_LOG_SENSITIVE, "no"))
	  log_msg((LOG_ERROR_LEVEL, "Invalid LOG_SENSITIVE directive"));
  }
  else
	log_set_flags(logd, LOG_MATCH_NORMAL);

  if (conf_var(CONF_LOG_FILTER) != NULL) {
	Kwv_pair *k;

	/*
	 * One or more filters have been specified.
	 * Syntax is: <type> <flag>[,<flag>]* <level> <name>
	 */
	for (k = conf_var(CONF_LOG_FILTER); k != NULL; k = k->next) {
	  if (log_add_filter(logd, k->val) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid LOG_FILTER directive: %s", k->val));
		continue;
	  }
	}
  }

  dacs_log_done_init = 1;
}

/* If not specified anywhere else, use this format. */
char *dacs_log_format_default = NULL;

/* If specified, use this one overriding anything else. */
char *dacs_log_format = NULL;

char *
get_current_log_format(void)
{
  char *fmt;

  if ((fmt = dacs_log_format) == NULL) {
	if ((fmt = conf_val(CONF_LOG_FORMAT)) == NULL) {
	  if ((fmt = dacs_log_format_default) == NULL
		  && getenv("REMOTE_ADDR") == NULL)
		fmt = dacs_log_format_default = LOG_FORMAT_DEFAULT_CMD;
	  else 
		fmt = dacs_log_format_default = LOG_FORMAT_DEFAULT_WEB;
	}
  }

  return(fmt);
}

/*
 * After being registered with the logging library, this function will
 * be called to generate the application-specific logging message prefix
 * for each log message.
 * It determines the format of all of the text that precedes the actual
 * message.
 */
static char *
logging_callback(Log_desc *logd, Log_level level, void *arg)
{
  char *fmt, *prefix;

  if (level == LOG_FORCE_LEVEL)
	prefix = NULL;
  else {
	fmt = get_current_log_format();
	prefix = log_format(logd, level, fmt);
  }

  return(prefix);
}

/****************************************************************************/

/*
 * This is called by DACS services and utilities when they start up
 * to try to verify that the jurisdiction that receives the request
 * (obtained from the configuration file, when possible) is the same as
 * JURISDICTION.
 * Return 1 if it is, -1 if it is not, and 0 if we can't tell.
 */
int
dacs_verify_jurisdiction(char *jurisdiction)
{
  char *j;

  if ((j = conf_val(CONF_JURISDICTION_NAME)) == NULL) {
	if (dacs_app_type == DACS_LOCAL_SERVICE)
	  return(0);
	return(-1);
  }

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

  if (name_eq(jurisdiction, j, DACS_NAME_CMP_CONFIG))
	return(1);

  return(-1);
}

char *
hexdump(unsigned char *buf, unsigned int len)
{
  unsigned int ui;
  Ds ds;

  ds_init(&ds);
  for (ui = 0; ui < len; ui++) {
	ds_asprintf(&ds, "%02X", buf[ui] & 0xff);
	if ((ui + 1) % 16 == 0)
	  ds_asprintf(&ds, "\n");
	else
	  ds_asprintf(&ds, " ");
  }
  if (ui % 16)
	ds_asprintf(&ds, "\n");

  return(ds_buf(&ds));
}

/*
 * Domain-match (Netscape, RFC 2109) URL_DOMAIN against COOKIE_DOMAIN.
 * Note that the Netscape HTTP Cookie spec and RFC 2109 do not agree on
 * how to handle a COOKIE_DOMAIN that does not begin with a dot.
 */
int
cookie_tail_match(char *url_domain, char *cookie_domain)
{

  if (looks_like_ip_addr(url_domain)
	  && strchrcount(url_domain, '.') == 3
	  && looks_like_ip_addr(cookie_domain)
	  && strchrcount(cookie_domain, '.') == 3) {
	return(streq(url_domain, cookie_domain));
  }

  /*
   * There are possibilities for a successful match:
   *  1) the hostnames are both valid and identical (case insensitively)
   *  2) the hostnames are both valid, COOKIE_DOMAIN does not begin with a dot,
   *     and COOKIE_DOMAIN is a proper "component" of URL_DOMAIN
   *     (e.g., "shipping.crate.acme.com" and "acme.com")
   *  3) URL_DOMAIN is a valid hostname, COOKIE_DOMAIN begins with a dot but
   *     is otherwise a valid hostname, and COOKIE_DOMAIN is a proper
   *     "component" of URL_DOMAIN (e.g., "a.b.c.com" and ".c.com")
   */
  if ((*cookie_domain == '.'
	   && match_subdomain_name(url_domain, cookie_domain + 1) == 1)
	  || match_subdomain_name(url_domain, cookie_domain) == 1)
	return(1);

  return(0);
}

/*
 * Path-match (Netscape, RFC 2109) URL_PATH against COOKIE_PATH.
 * Examples: COOKIE_PATH "/foo" matches URL_PATHs "/foobar" and
 * "/foo/bar.html".
 * COOKIE_PATH "/" is the most general path and matches all URL_PATHs.
 * Assume an initial '/' if necessary for either argument.
 */
int
cookie_path_match(char *url_path, char *cookie_path)
{
  char *p, *q;

  if (cookie_path == NULL || *cookie_path == '\0')
	return(0);

  p = url_path;
  if (*p == '/')
	p++;

  q = cookie_path;
  if (*q == '/')
	q++;

  if (strprefix(p, q))
	return(1);

  return(0);
}

/*
 * Also see authlib.c:get_cookies()
 */
int
get_app_cookies(char *cookie_str, Dsvec **cookies)
{
  int is_dacs_cookie, n;
  char *p, *r, *s;
  Cookie *cookie;
  Dsvec *dsv;

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

  if (*cookies == NULL)
	dsv = *cookies = dsvec_init(NULL, sizeof(Cookie *));
  else
	dsv = *cookies;

  s = p = cookie_str;
  n = 0;
  while (p != NULL) {
	/*
	 * The Netscape spec says that a semicolon is used to separate
	 * multiple name/value pairs in a Cookie header.
	 * RFC 2109 uses a comma for this purpose; such cookies ought
	 * to be identified by an initial field of '$Version="1";' in the
	 * Cookie header, but that doesn't seem be the case for all clients.
	 * DACS doesn't use a comma in Cookie headers, so treating a comma
	 * as a semicolon ought to be ok.
	 */

	if ((r = strchr(p, ';')) != NULL) {
	  *r++ = '\0';
	  while (*r == ' ')
		r++;
	}
	else if ((r = strchr(p, ',')) != NULL) {
	  *r++ = '\0';
	  while (*r == ' ')
		r++;
	}

	s = p;
	p = r;

  /* Break the cookie up into its fields. */
	if ((cookie = cookie_parse(s, &is_dacs_cookie)) == NULL)
	  continue;

	dsvec_add_ptr(dsv, cookie);
	n++;
  }

  return(n);
}

/*
 * Compose a string consisting of the scheme and authority parts of the
 * current request.  All values are obtained from environment variables or
 * RFC 2616 defaults.
 */
char *
current_uri_sa(Ds *ods)
{
  int https;
  char *host, *server_port;
  Ds *ds, xds;

  if (ods == NULL) {
	ds_init(&xds);
	ds = &xds;
  }
  else
	ds = ods;

  if (dacs_is_https_request()) {
    ds_asprintf(ds, "https://");
	https = 1;
  }
  else {
	ds_asprintf(ds, "http://");
	https = 0;
  }

  /*
   * HTTP_HOST is from the HTTP Host request header.  It appears that Apache
   * always includes a port number.
   * SERVER_NAME and SERVER_PORT are required by the CGI spec.
   * Use HTTP_HOST if available, otherwise use SERVER_NAME.
   * Use SERVER_PORT if HTTP_POST doesn't include a port.
   */
  server_port = getenv("SERVER_PORT");
  if ((host = getenv("HTTP_HOST")) == NULL) {
	char *server_name;

	/* HTTP_HOST is unavailable, require SERVER_NAME and SERVER_PORT */
	if ((server_name = getenv("SERVER_NAME")) == NULL || server_port == NULL) {
	  if (dacs_app_type == DACS_UTILITY
		  || dacs_app_type == DACS_UTILITY_OPT
		  || dacs_app_type == DACS_UTILITY_PROC
		  || dacs_app_type == DACS_STANDALONE) {
		ds_asprintf(ds, "localhost/");
		return(ds_buf(ds));
	  }
	  log_msg((LOG_ERROR_LEVEL, "No HTTP_HOST/SERVER_NAME?"));
	  return(NULL);
	}

	if ((!https && !streq(server_port, "80"))
		|| (https && !streq(server_port, "443")))
	  ds_asprintf(ds, "%s:%s", server_name, server_port);
	else
	  ds_asprintf(ds, "%s", server_name);
  }
  else if (strchr(host, (int) ':') == NULL) {
	char *port;

	/* No explicit port number is available, so guess... */
	if (server_port == NULL) {
	  if (https)
		port = "443";
	  else
		port = "80";
	  log_msg((LOG_ERROR_LEVEL,
			   "No port number available: using default, port %d", port));
	}
	else
	  port = server_port;

	if ((!https && !streq(port, "80")) || (https && !streq(port, "443")))
	  ds_asprintf(ds, "%s:%s", host, port);
	else
	  ds_asprintf(ds, "%s", host);
  }
  else
	ds_asprintf(ds, "%s", host);

  return(ds_buf(ds));
}

char *
set_dacs_service_uri(char *uri)
{
  char *request_uri;

  if (uri != NULL)
	request_uri = uri;
  else
	request_uri = getenv("REQUEST_URI");

  if (request_uri != NULL) {
	char *p, *xuri;

	xuri = strdup(request_uri);
	if ((p = strchr(xuri, (int) '?')) != NULL)
	  *p = '\0';
	dacs_service_uri = strchop(xuri, "/");
  }

  if (dacs_service_uri != NULL)
	log_msg((LOG_TRACE_LEVEL, "dacs_service_uri=\"%s\"", dacs_service_uri));

  return(dacs_service_uri);
}

/*
 * Extract the path, query, and fragment components of the current request URI.
 * Return those components assembled into a string.
 * Set dacs_service_uri if necessary.
 */
char *
current_uri_pqf(char **path, char **query, char **fragment)
{
  char *uri;

  uri = set_dacs_service_uri(NULL);

  if (uri_pqf(uri, path, query, fragment) != -1)
	return(strdup(uri));

  return(NULL);
}

/*
 * Return the last path component of the current request, which should be
 * the "short name" used to invoke the web service.
 */
char *
current_uri_prog(void)
{
  char *p, *uri, *xuri;

  if ((uri = getenv("SCRIPT_NAME")) == NULL || *uri == '\0')
	return(NULL);

  xuri = strdup(uri);
  if ((p = rindex(xuri, (int) '/')) == NULL)
	return(xuri);

  *p++ = '\0';
  /* XXX What to do if it ends with a slash? */
  if (*p == '\0')
	return(NULL);

  return(p);
}

char *
current_uri_script(void)
{
  char *p, *sa, *script_name;

  if ((script_name = getenv("SCRIPT_NAME")) == NULL || *script_name == '\0')
	return(NULL);

  if ((sa = current_uri_sa(NULL)) == NULL)
	return(NULL);

  p = ds_xprintf("%s%s", sa, script_name);

  return(p);
}

/*
 * Assemble a canonicalized absolute URI for the current request.
 * Set dacs_service_uri if necessary.
 */
char *
current_uri_no_query(Ds *ods)
{
  char *uri;
  Ds *ds, xds;

  if (ods == NULL) {
	ds_init(&xds);
	ds = &xds;
  }
  else
	ds = ods;

  uri = set_dacs_service_uri(NULL);
  if (current_uri_is_proxied(NULL)) {
	ds_asprintf(ds, "%s", uri);
  }
  else {
	if (current_uri_sa(ds) == NULL)
	  return(NULL);
	log_msg((LOG_TRACE_LEVEL, "current_uri_sa=\"%s\"", ds_buf(ds)));

	if (uri != NULL)
	  ds_asprintf(ds, "%s", uri);
  }

  return(ds_buf(ds));
}

char *
current_uri(Ds *ods)
{
  char *p;
  Ds *ds, xds;

  if (ods == NULL) {
	ds_init(&xds);
	ds = &xds;
  }
  else
	ds = ods;

  if (current_uri_no_query(ds) == NULL)
	return(NULL);

  if ((p = getenv("QUERY_STRING")) != NULL && *p != '\0')
	ds_asprintf(ds, "?%s", p);

  return(ds_buf(ds));
}

/*
 * Return 1 if the current request is being proxied, 0 if it is not, or
 * -1 if we cannot tell.
 * There are several ways to determine this, but for now we will rely on
 * the fact that iff the current URI is absolute then it must be a proxied
 * request.
 */
int
current_uri_is_proxied(char *uri)
{
  char *request_uri;

  if (uri != NULL)
	request_uri = uri;
  else
	request_uri = getenv("REQUEST_URI");

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

  if (strcaseprefix(request_uri, "http://") != NULL
	  || strcaseprefix(request_uri, "https://") != NULL)
	return(1);

  return(0);
}

/*
 * Construct and return a URL from bits and pieces passed to DACS
 * from Apache/mod_auth_dacs.
 * Note that this URL will probably need to be fully encoded if it is
 * passed as a web service argument.
 */
char *
make_error_url(Kwv *kwv)
{
  int is_https;
  char *p, *service_uri;
  Ds ds;

  if ((service_uri = kwv_lookup_value(kwv, "SERVICE_URI")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "make_url: no SERVICE_URI?"));
	return(NULL);
  }

  if (strcaseprefix(service_uri, "http://") != NULL
	  || strcaseprefix(service_uri, "https://") != NULL) {
	/* This is a proxied request, so this is a complete, absolute URI. */
	return(strdup(service_uri));
  }

  ds_init(&ds);
  if ((p = kwv_lookup_value(kwv, "SERVICE_HTTPS")) != NULL
	  && strcaseeq(p, "on")) {
	is_https = 1;
	ds_asprintf(&ds, "https://");
  }
  else {
	is_https = 0;
	ds_asprintf(&ds, "http://");
  }

  if ((p = kwv_lookup_value(kwv, "SERVICE_HOSTNAME")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "make_url: no SERVICE_HOSTNAME?"));
	return(NULL);
  }
  ds_asprintf(&ds, "%s", p);

  if ((p = kwv_lookup_value(kwv, "SERVICE_PORT")) != NULL) {
	if ((!is_https && !streq(p, "80")) || (is_https && !streq(p, "443")))
	  ds_asprintf(&ds, ":%s", p);
  }

  ds_asprintf(&ds, "%s", service_uri);

  if ((p = kwv_lookup_value(kwv, "SERVICE_ARGS")) != NULL && *p != '\0') {
	char *decoded;

	/* SERVICE_ARGS are base64 encoded, so decode first */
	if (mime_decode_base64(p, (unsigned char **) &decoded) == -1)
	  return(NULL);
	ds_asprintf(&ds, "?%s", decoded);
  }

  return(ds_buf(&ds));
}

/*
 * Check if the given DACS version is acceptable.
 * VERSION can be NULL, in which case no DACS_VERSION argument
 * was provided to this DACS service.
 * Return 1 if VERSION (or lack thereof) is ok, 0 otherwise.
 */
int
is_compatible_dacs_version(char *version)
{

  if (version == NULL)
	return(1);

  if (!streq(version, DACS_VERSION_NUMBER)) {
	char *compat;

	if ((compat = conf_val(CONF_COMPAT_MODE)) == NULL
		|| conf_val_eq(CONF_COMPAT_MODE, "off")) {
	  log_msg((LOG_ERROR_LEVEL, "Require release number %s, got \"%s\"",
			   DACS_VERSION_NUMBER, version));
	  return(0);
	}

	if (!streq(version, compat)) {
	  log_msg((LOG_NOTICE_LEVEL,
			   "Compatibility mode is not enabled for \"%s\"", version));
	  log_msg((LOG_ERROR_LEVEL, "Require release number %s, got \"%s\"",
			   DACS_VERSION_NUMBER, version));
	  return(0);
	}

	log_msg((LOG_NOTICE_LEVEL, "Compatibility mode is enabled for \"%s\"",
			 version));
	dacs_compatibility_mode = strdup(version);
  }

  return(1);
}

static int keyword_scan_file(FILE *file, Dsvec **k);
static int keyword_match(FILE *fp, int *next, char **string);

/*
 * Configure for the RCS/svn keyword string format.
 */
enum {
  KEYWORD_START_CHAR = '$',
  KEYWORD_SEP_CHAR   = ':',
  KEYWORD_END_CHAR   = '$',
  KEYWORD_LEN_LIMIT  = 200
};

static int
keyword_compar(const void *ap, const void *bp)
{
  const char *a, *b;
  char *a_name, *b_name;
  Strfields a_spec, b_spec;

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

  /* XXX none of this memory is freed */
  a_spec.conf = b_spec.conf = NULL;
  strpfields(a, &a_spec);
  strpfields(b, &b_spec);
  a_name = a_spec.argv[1];
  b_name = b_spec.argv[1];

  return(strcmp(a_name, b_name));
}

static int
keyword_mem_match(char **memp, char *mem_end, int *next, char **string)
{
  int ch, prev_ch;
  char *mem;
  Ds ds;

  ds_init(&ds);
  ds.len_limit = KEYWORD_LEN_LIMIT;

  /*
   * We need to see something like "$<alpha>*: <isprint>* $"
   * First, look for the part that precedes the ':'.
   */
  mem = *memp;
  while ((ch = *mem++) != KEYWORD_SEP_CHAR) {
	if (mem >= mem_end)
	  goto error;
	if (isalpha(ch)) {
	  if (ds.len == 0)
		ds_appendc(&ds, (int) KEYWORD_START_CHAR);
	  if (ds_appendc(&ds, ch) != -1)
		continue;
	}
	/* Either not an alphabetic or we've reached the length limit. */
	goto fail;
  }

  /* Append KEYWORD_SEP_CHAR */
  if (ds.len == 0)
	ds_appendc(&ds, (int) KEYWORD_START_CHAR);
  if (ds_appendc(&ds, ch) == -1)
	goto fail;

  /* Expect a space and append it */
  if ((ch = *mem++) != ' ' || ds_appendc(&ds, ch) == -1)
	goto fail;

  prev_ch = 0;
  while ((ch = *mem++) != KEYWORD_END_CHAR) {
	if (mem >= mem_end)
	  goto error;
	if (isprint((int) ch)) {
	  if (ds_appendc(&ds, ch) != -1) {
		prev_ch = ch;
		continue;
	  }
	}
	/* Either an invalid character or we've reached the length limit. */
	goto fail;
  }

  /* Expect a space to precede KEYWORD_END_CHAR */
  if (prev_ch != ' ')
	goto fail;

  /* Append a trailing KEYWORD_END_CHAR and a terminating null */
  if (ds_appendc(&ds, ch) == -1)
	goto fail;
  if (ds_appendc(&ds, (int) '\0') == -1)
	goto fail;

  *string = ds_buf(&ds);

  *memp = mem;
  return(1);

 fail:
  ds_free(&ds);
  *next = ch;
  *memp = mem;
  return(0);

 error:
  ds_free(&ds);
  return(-1);
}

static Dsvec *
keyword_scan_mem(char *mem_start, char *mem_end)
{
  int ch, st;
  char *mem, *str;
  Dsvec *keywords;

  keywords = NULL;
  mem = mem_start;
  ch = *mem++;
  while (mem < mem_end) {
	st = 1;
	if (ch == KEYWORD_START_CHAR) {
	  if ((st = keyword_mem_match(&mem, mem_end, &ch, &str)) == -1)
		break;
	  if (st == 1) {
		if (keywords == NULL)
		  keywords = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(keywords, str);
	  }
	}
	if (st == 1)
	  ch = *mem++;
  }

  return(keywords);
}

#ifdef DACS_OS_MACOSX
#include <mach-o/dyld.h>
#endif

/*
 * Scan FILENAME, looking for strings that look like RCS/svn keyword strings,
 * such as "$Id: dacslib.c 2649 2013-02-28 17:45:03Z brachman $".
 * If FILENAME is NULL, examine the file corresponding to this process.
 * Return a vector of keywords, or NULL if there's an error.
 */
Dsvec *
keyword_scan(char *filename)
{
  char *mem_start, *mem_end, *path;
  pid_t pid;
  Ds *ds;
  Dsvec *keywords;
  FILE *fp;

  ds = NULL;
  mem_start = mem_end = NULL;

  if (filename == NULL) {
	pid = getpid();

#if defined(DACS_OS_LINUX)
	path = ds_xprintf("/proc/%d/exe", pid);
#elif defined(DACS_OS_CYGWIN)
	/*
	 * There is no direct link to the executable.
	 * Instead, get the pathname by reading the winexename file.
	 */
	{
	  char *winexename;

	  winexename = ds_xprintf("/proc/%d/winexename", pid);
	  ds = ds_init(NULL);
	  ds->delnl_flag = 1;
	  if (ds_load_file(ds, winexename) == NULL) {
		log_msg((LOG_NOTICE_LEVEL, "Can't load %s", winexename));
		return(NULL);
	  }
	  path = ds_buf(ds);
	}
#elif defined(DACS_OS_SOLARIS)
	path = ds_xprintf("/proc/%d/object/a.out", pid);
#elif defined(DACS_OS_MACOSX)
	/* MAC OS X has no proc filesystem. */
	char abuf[MAXPATHLEN * 10];
	uint32_t abufsize;

	abufsize = sizeof(abuf);
	if (_NSGetExecutablePath(abuf, &abufsize) == -1) {
	  /* XXX */
	  log_msg((LOG_NOTICE_LEVEL, "Cannot get executable path"));
	  return(NULL);
	}
	path = ds_xprintf("%s", abuf);
#else
	path = ds_xprintf("/proc/%d/file", pid);
#endif
  }
  else
	path = filename;

#ifdef NOTDEF
  if ((fp = fopen(path, "r")) == NULL) {
	log_msg((LOG_NOTICE_LEVEL, "Can't open %s", path));
	return(NULL);
  }
#else
  if (path != NULL) {

	if ((ds = ds_load_file(NULL, path)) == NULL) {
	  log_msg((LOG_NOTICE_LEVEL, "Can't open %s", path));
	  return(NULL);
	}
	mem_start = ds_buf(ds);
	mem_end = ds_buf(ds) + ds_len(ds);
  }
#endif

#ifdef NOTDEF
  keywords = NULL;
  keyword_scan_file(fp, &keywords);
  fclose(fp);
#else
  keywords = keyword_scan_mem(mem_start, mem_end);
  if (ds != NULL)
	ds_free(ds);
#endif

  dsvec_sort(keywords, keyword_compar);

  return(keywords);
}

/*
 * Scan FP, looking for strings that look like RCS/svn keyword strings.
 * Return -1 if there's an error, 0 otherwise along with a vector of
 * strings.
 */
static int
keyword_scan_file(FILE *fp, Dsvec **k)
{
  int ch, st;
  char *str;
  Dsvec *keywords;

#ifndef NOTDEF
  Ds *ds;

  if ((ds = ds_getf(fp)) == NULL)
	return(-1);
  *k = keyword_scan_mem(ds_buf(ds), ds_buf(ds) + ds_len(ds));
  return(0);
#else
  keywords = NULL;
  ch = getc(fp);
  while (ch != EOF && !feof(fp) && !ferror(fp)) {
	st = 1;
	if (ch == KEYWORD_START_CHAR) {
	  if ((st = keyword_match(fp, &ch, &str)) == -1)
		break;
	  if (st == 1) {
		if (keywords == NULL)
		  *k = keywords = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(keywords, str);
	  }
	}
	if (st == 1)
	  ch = getc(fp);
  }

  if (ferror(fp))
	return(-1);

  return(0);
#endif
}

/*
 * Having seen an initial KEYWORD_START_CHAR character, confirm that we're
 * looking at a keyword string.  Anything longer than KEYWORD_LEN_LIMIT,
 * which includes the terminating null, is judged to be "too long" and
 * therefore not a keyword string.
 * If we find a keyword string, return 1 and a copy of it in STRING.
 * If it's decided that we're not, return 0 and the last character inspected
 * in NEXT.
 * If we hit EOF or an error, return -1.
 */
static int
keyword_match(FILE *fp, int *next, char **string)
{
  int ch, prev_ch;
  Ds ds;

  ds_init(&ds);
  ds.len_limit = KEYWORD_LEN_LIMIT;

  /*
   * We need to see something like "$<alpha>*: <isprint>* $"
   * First, look for the part that precedes the ':'.
   */
  while ((ch = getc(fp)) != KEYWORD_SEP_CHAR) {
	if (ch == EOF || feof(fp) || ferror(fp))
	  goto error;
	if (isalpha(ch)) {
	  if (ds.len == 0)
		ds_appendc(&ds, (int) KEYWORD_START_CHAR);
	  if (ds_appendc(&ds, ch) != -1)
		continue;
	}
	/* Either not an alphabetic or we've reached the length limit. */
	goto fail;
  }

  /* Append KEYWORD_SEP_CHAR */
  if (ds.len == 0)
	ds_appendc(&ds, (int) KEYWORD_START_CHAR);
  if (ds_appendc(&ds, ch) == -1)
	goto fail;

  /* Expect a space and append it */
  if ((ch = getc(fp)) != ' ' || ds_appendc(&ds, ch) == -1)
	goto fail;

  prev_ch = 0;
  while ((ch = getc(fp)) != KEYWORD_END_CHAR) {
	if (ch == EOF || feof(fp) || ferror(fp))
	  goto error;
	if (isprint((int) ch)) {
	  if (ds_appendc(&ds, ch) != -1) {
		prev_ch = ch;
		continue;
	  }
	}
	/* Either an invalid character or we've reached the length limit. */
	goto fail;
  }

  /* Expect a space to precede KEYWORD_END_CHAR */
  if (prev_ch != ' ')
	goto fail;

  /* Append a trailing KEYWORD_END_CHAR and a terminating null */
  if (ds_appendc(&ds, ch) == -1)
	goto fail;
  if (ds_appendc(&ds, (int) '\0') == -1)
	goto fail;

  *string = ds_buf(&ds);

  return(1);

 fail:
  ds_free(&ds);
  *next = ch;
  return(0);

 error:
  ds_free(&ds);
  return(-1);
}

char *
dacs_build_datainfo_string(void)
{
  int comma;
  Ds ds;

  comma = 0;
  ds_init(&ds);
  ds_asprintf(&ds, "[sizeof:");

#ifdef SIZEOF_VOID_P
  ds_asprintf(&ds, "%s(void *)=%d", comma ? ", " : " ", SIZEOF_VOID_P);
  comma++;
#endif
#ifdef SIZEOF_LONG_LONG
  ds_asprintf(&ds, "%s(long long)=%d", comma ? ", " : " ", SIZEOF_LONG_LONG);
  comma++;
#endif
#ifdef SIZEOF_LONG
  ds_asprintf(&ds, "%s(long)=%d", comma ? ", " : " ", SIZEOF_LONG);
  comma++;
#endif
#ifdef SIZEOF_INT
  ds_asprintf(&ds, "%s(int)=%d", comma ? ", " : " ", SIZEOF_INT);
  comma++;
#endif
#ifdef SIZEOF_SHORT
  ds_asprintf(&ds, "%s(short)=%d", comma ? ", " : " ", SIZEOF_SHORT);
  comma++;
#endif
#ifdef SIZEOF_CHAR
  ds_asprintf(&ds, "%s(char)=%d", comma ? ", " : " ", SIZEOF_CHAR);
  comma++;
#endif
  ds_asprintf(&ds, "]");

#ifdef DACS_OS_BYTE_ORDER
  ds_asprintf(&ds, " [byteorder=%d]", DACS_OS_BYTE_ORDER);
  comma++;
#endif

  /* Uses the GCC stringification feature... */
#define STRFRY(X)		STRFRY2(X)
#define STRFRY2(X)		#X

  ds_asprintf(&ds, " [types:");
  ds_asprintf(&ds, " ui64=(%s)", STRFRY(DACS_UI64_T));
  ds_asprintf(&ds, ", ui32=(%s)", STRFRY(DACS_UI32_T));
  ds_asprintf(&ds, ", ui16=(%s)", STRFRY(DACS_UI16_T));
  ds_asprintf(&ds, ", ui8=(%s)", STRFRY(DACS_UI8_T));
  ds_asprintf(&ds, ", i64=(%s)", STRFRY(DACS_I64_T));
  ds_asprintf(&ds, ", i32=(%s)", STRFRY(DACS_I32_T));
  ds_asprintf(&ds, ", i16=(%s)", STRFRY(DACS_I16_T));
  ds_asprintf(&ds, ", i8=(%s)", STRFRY(DACS_I8_T));
  ds_asprintf(&ds, "]");

  return(ds_buf(&ds));
}

char *
dacs_build_os_string(void)
{
  char *d, *g, *p;


#ifdef DACS_BUILD_PLATFORM
  p = DACS_BUILD_PLATFORM;
#else
  p = "Unknown";
#endif

#ifdef GCC_VERSION
  g = ds_xprintf("with GCC %s", GCC_VERSION);
#else
  g = "with unknown compiler";
#endif		 

  d = dacs_build_datainfo_string();

  return(ds_xprintf("%s %s %s", p, g, d));
}

#include <sys/utsname.h>

char *
dacs_runtime_os_string(void)
{
  char *str;
  struct utsname name;

  if (uname(&name) == -1)
	return("");

  str = ds_xprintf("%s/%s/%s", name.sysname, name.release, name.machine);

  return(str);
}

char *
dacs_version_string(void)
{
  char *vs;

  vs = ds_xprintf("DACS %s %s (revid %s)",
				  DACS_VERSION_RELEASE, DACS_VERSION_DATE,
				  strffields("%2f", strpfields(DACS_VERSION_REVID, NULL)));

  return(vs);
}

/*
 * The version number identifies the set of protocols being used (e.g., to
 * make sure client and server are using compatible protocols).  Distributions
 * with the same version number should be able to interoperate.
 * The release number identifies a particular version of the software that
 * is released (e.g., "5" or "5.4").
 * The release string is the release number followed by its creation date.
 * The version string is the verion number followed by the release string.
 */
void
dacs_version(FILE *fp)
{
  unsigned int ui;
  Dsvec *keywords;

  fprintf(fp, "%s\n", dacs_version_string());
  if (verbose_level && (keywords = keyword_scan(NULL)) != NULL) {
	for (ui = 0; ui < dsvec_len(keywords); ui++)
	  fprintf(fp, "%s\n", dsvec_ptr(keywords, ui, char *));
  }
}

int
is_internet_explorer(char *user_agent)
{

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

  if (strstr(user_agent, "MSIE") != NULL)
	return(1);
  return(0);
}

#ifdef NOTDEF
static void catch_sigpipe(int);

static void
catch_sigpipe(int x)
{

  log_msg((LOG_ERROR_LEVEL,
		   "filterthru: caught SIGPIPE, child died prematurely"));
  if (dacs_app_type == DACS_WEB_SERVICE
	  || dacs_app_type == DACS_LOCAL_SERVICE)
	log_msg((LOG_INFO_LEVEL, EXIT_MESSAGE));

  _exit(1);
}

void
longjmperror(void)
{

  log_msg((LOG_ALERT_LEVEL, "longjmp failed -- exiting"));
  if (dacs_app_type == DACS_WEB_SERVICE
	  || dacs_app_type == DACS_LOCAL_SERVICE)
	log_msg((LOG_INFO_LEVEL, EXIT_MESSAGE));
  _exit(1);
}
#endif

void
dacs_abort(char *mesg)
{

  if (mesg == NULL || mesg[0] == '\0')
	log_exec(LOG_ALERT_LEVEL, "%s: Exiting after a fatal error",
			 log_event_stamp(log_current_logd, LOG_ALERT_LEVEL, NULL));
  else
	log_exec(LOG_ALERT_LEVEL, "%s: Exiting after a fatal error: %s",
			 log_event_stamp(log_current_logd, LOG_ALERT_LEVEL, NULL), mesg);

  if (dacs_app_type == DACS_WEB_SERVICE
	  || dacs_app_type == DACS_LOCAL_SERVICE)
	log_msg((LOG_INFO_LEVEL, EXIT_MESSAGE));

  /* Leave immediately */
  _exit(1);
}

void
envdump(void)
{
  int i;
  extern char **environ;

  for (i = 0; environ[i] != NULL; i++)
	fprintf(stdout, "%s\n", environ[i]);
}

/*
 * Implementation of BSD's getgrouplist(3).
 * Not all platforms have this function.
 */
int
get_unix_group_membership(const char *name, gid_t basegid, gid_t *groups,
						  int *ngroups)
{
  int i, maxgroups, n, st;
  struct group *grp;

  n = 0;
  maxgroups = *ngroups;

  /*
   * Reproduce getgrouplist(3) kludge where first element of GROUPS is
   * duplicated.
   */
  groups[n++] = basegid;
  if (maxgroups > 1)
	groups[n++] = basegid;

#ifdef DACS_OS_FREEBSD
  if (setgrent() != 1)
	return(-1);
#else
  setgrent();
#endif

  st = 0;
  while (st == 0 && (grp = getgrent()) != NULL) {
	for (i = 0; i < n; i++) {
	  if ((gid_t) grp->gr_gid == groups[i])
		break;
	}

	if (i != n)
	  continue;

	for (i = 0; grp->gr_mem[i] != NULL; i++) {
	  if (streq(grp->gr_mem[i], name)) {
		if (n >= maxgroups) {
		  st = -1;
		  break;
		}
		groups[n++] = grp->gr_gid;
		break;
	  }
	}
  }

  endgrent();
  *ngroups = n;
  return(st);
}

int
get_unix_roles(char *username, char **role_str)
{
  int i, ngroups;
  gid_t groups[MAX_UNIX_GROUPS], lastgid;
  struct passwd *pw;
  struct group *gr;
  Ds ds;

  if ((pw = getpwnam(username)) == NULL) {
	log_err((LOG_TRACE_LEVEL, "getpwnam() failed for \"%s\"", username));
	return(-1);
  }

  ngroups = MAX_UNIX_GROUPS;
  if (get_unix_group_membership(username, pw->pw_gid, groups, &ngroups)
	  == -1) {
	log_msg((LOG_ERROR_LEVEL,
			 "get_unix_group_membership() failed for \"%s\"", username));
	return(-1);
  }

  /*
   * The first group in the list may be duplicated ("the first
   * element is overwritten if a setgid file is executed").  Refer to the
   * source code.
   */
  ds_init(&ds);
  lastgid = 0;	/* Shutup the compiler */
  for (i = 0; i < ngroups; i++) {
	if (i > 0 && lastgid == groups[i])
	  continue;
	if ((gr = getgrgid(groups[i])) != NULL)
	  ds_asprintf(&ds, "%s%s", (i != 0) ? "," : "", gr->gr_name);
	else
	  log_msg((LOG_TRACE_LEVEL, "getgrgid() failed for gid %u", groups[i]));
	lastgid = groups[i];
  }

  if (ds_len(&ds) == 0)
	*role_str = "";
  else
	*role_str = ds_buf(&ds);

  return(0);
}

/*
 * RFC 952, 1123
 * Test for a valid hostname.
 * Also see looks_like_domain_name()
 */
int
is_valid_hostname(char *str)
{
  char *p;

  if (!isalnum((int) str[0]) || str[1] == '\0')
	return(0);
  for (p = str + 1; *p != '\0'; p++) {
	if (!isalnum((int) *p) && *p != '-' && *p != '.')
	  return(0);
  }

  if (*(p - 1) == '.' || *(p - 1) == '-')
	return(0);

  return(1);
}

/*
 * 2008, 1600, 2000 and 2400 are leap years but 1700, 1800, 1900, 2100
 * and 3000 are not.
 */
int
is_leap_year(int year)
{
  int is_leap;

  if ((year % 400) == 0)
	is_leap = 1;
  else if ((year % 100) == 0)
	is_leap = 0;
  else if ((year % 4) == 0)
	is_leap = 1;
  else
	is_leap = 0;
 
  return(is_leap);
}

typedef struct Interpolation_range {
  int neg;
  unsigned long selector;
  int pos;
} Interpolation_range;

typedef struct Interpolation_component {
  char *component;
  char *separator;
  int argc;
  char **argv;
} Interpolation_component;

/*
 * If FORMAT looks like a correct component selector specification,
 * (optionally) initialize R accordingly, and return 0; otherwise return -1.
 * On success, ENDP is (optionally) set to point to the character following
 * the spec and on error it is set to point to the erroneous character.
 */
static int
parse_selector(char *format, char **endp, Interpolation_range *r)
{
  int saw_neg, saw_pos;
  char *endptr, *p;
  unsigned long selector;

  saw_neg = saw_pos = 0;

  p = format;
  if (*p == '-') {
	saw_neg = 1;
	p++;
  }

  if (!isdigit((int) *p)) {
	if (endp != NULL)
	  *endp = p;
	return(-1);
  }

  selector = strtoul(p, &endptr, 10);
  if (errno == ERANGE) {
	if (endp != NULL)
	  *endp = endptr;
	return(-1);
  }

  p = endptr;
  if (*p == '+') {
	saw_pos = 1;
	p++;
  }

  if (r != NULL) {
	r->selector = selector;
	r->pos = saw_pos;
	r->neg = saw_neg;
  }

  if (endp != NULL)
	*endp = p;

  return(0);
}

static char *
interpolate_component(char *s, char **endptr,
					  Interpolation_component *component)
{
  int got_m, i;
  char *endp, *p;
  Ds ds;
  Interpolation_range n, m;

  if (component == NULL)
	return(NULL);

  if (parse_selector(s, &endp, &n) == -1)
	return(NULL);
  p = endp;

  if (*p == '.') {
	p++;
	if (parse_selector(p, &endp, &m) == -1)
	  return(NULL);
	got_m = 1;
	p = endp;
  }
  else
	got_m = 0;

  ds_init(&ds);

  if (n.selector > (unsigned int) component->argc) {
	if (endptr != NULL)
	  *endptr = endp;

	ds_appendc(&ds, (int) '_');
	ds_appendc(&ds, (int) '\0');
	return(ds_buf(&ds));
  }

  log_msg((LOG_TRACE_LEVEL, "neg=%s, selector=%lu, pos=%s",
		   n.neg ? "-" : " ", n.selector, n.pos ? "+" : " "));

  /*
   * Getting the selector semantics right is the tricky part.
   * Rather than getting fancy, we just do a straightforward control flow.
   */
  if (n.neg) {
	if (n.selector == 0)
	  ds_append(&ds, component->component);
	else {
	  if (n.pos) {
		for (i = 0; i < (component->argc - (int) n.selector + 1); i++) {
		  if (i != 0)
			ds_append(&ds, component->separator);
		  ds_append(&ds, component->argv[i]);
		}
	  }
	  else
		ds_append(&ds, component->argv[component->argc - n.selector]);
	}
  }
  else {
	if (n.selector == 0)
	  ds_append(&ds, component->component);
	else {
	  ds_append(&ds, component->argv[n.selector - 1]);
	  if (n.pos) {
		for (i = n.selector; i < component->argc; i++) {
		  ds_append(&ds, component->separator);
		  ds_append(&ds, component->argv[i]);
		}
	  }
	}
  }
  ds_appendc(&ds, (int) '\0');

  if (got_m) {
	int start, end;

	log_msg((LOG_TRACE_LEVEL, "neg=%s, selector=%lu, pos=%s",
			 m.neg ? "-" : " ", m.selector, m.pos ? "+" : " "));  
	if (m.selector > ds_len(&ds)) {
	  ds_reinit(&ds);
	  ds_appendc(&ds, (int) '_');
	  ds_appendc(&ds, (int) '\0');
	}
	else {
	  if (m.neg) {
		if (m.selector == 0)
		  end = ds_len(&ds);
		else
		  end = ds_len(&ds) - m.selector;
		if (m.pos)
		  start = 0;
		else
		  start = end - 1;
	  }
	  else {
		if (m.selector == 0) {
		  start = 0;
		  end = ds_len(&ds);
		}
		else {
		  start = m.selector - 1;
		  if (m.pos)
			end = ds_len(&ds);
		  else
			end = start + 1;
		}
	  }
	  ds_copyb(&ds, ds_buf(&ds) + start, end - start, 0);
	  ds_copyb(&ds, (void *) "", 1, end - start);
	}
  }

  if (endptr != NULL)
	*endptr = endp;

  return(ds_buf(&ds));
}

/*
 * This is a "mostly-upward compatible" implementation of Apache's
 * mod_vhost_alias module's directory name interpolation functionality.  That
 * module's behaviour isn't formally described, so this code may not produce
 * results identical in all contexts to mod_vhost_alias's.
 * This has been tested against the examples in the module's documentation
 * (http://httpd.apache.org/docs-2.0/mod/mod_vhost_alias.html).
 *
 * The interpolated string is returned if no error occurs, otherwise NULL.
 * HOSTNAME and/or PORT are required only if the specification needs them.
 *
 * Format specifiers:
 * %%      -> insert '%'
 * %a      -> insert application name
 * %b[AD]  -> insert build-time variable value
 * %p      -> insert PORT
 * %u[N.M] -> insert the service uri path, or continguous components of it
 * %U[N.M] -> insert dacs_conf->uri, or continguous components of it
 * %s[N.M] -> insert HOSTNAME,  or contiguous components of it
 * %?      -> for any invalid specifier '?', insert the character
 *
 * N.M     -> insert contiguous components of HOSTNAME
 * ?       -> any other character is inserted verbatim
 */
char *
directory_name_interpolate(char *format, char *hostname, char *port)
{
  char *c, *endptr, *p;
  Ds ds;
  Interpolation_component ic;

  ds_init(&ds);

  p = format;
  while (*p != '\0') {
	if (*p != '%') {
	  ds_appendc(&ds, (int) *p);
	  p++;
	  continue;
	}

	p++;
	if (*p == '%') {
	  ds_appendc(&ds, (int) '%');
	  p++;
	  continue;
	}

	if (*p == 'p') {
	  if (port == NULL)
		return(NULL);
	  ds_append(&ds, port);
	  p++;
	  continue;
	}

	if (*p == 'a') {
	  ds_append(&ds, get_dacs_app_name());
	  p++;
	  continue;
	}

	if (*p == 'u') {
	  char *eptr, *uri;
	  Mkargv path_conf = { 0, 0, "/", NULL, NULL };
	  static Uri *service_uri = NULL;

	  if (service_uri == NULL) {
		uri = current_uri_no_query(NULL);
		if (uri == NULL || (service_uri = uri_parse(uri)) == NULL)
		  return(NULL);
	  }

	  ic.component = ds_xprintf("%s%s", service_uri->host, service_uri->path);

	  p++;

	  if (parse_selector(p, &eptr, NULL) == -1) {
		if (eptr == p) {
		  ds_append(&ds, ic.component);
		  continue;
		}
		return(NULL);
	  }

	  ic.separator = "/";
	  if ((ic.argc = mkargv(ic.component, &path_conf, &ic.argv)) == -1)
		return(NULL);
	}
	else if (*p == 'U') {
	  char *eptr;
	  Mkargv path_conf = { 0, 0, "/", NULL, NULL };

	  if (dacs_conf == NULL || (ic.component = dacs_conf->uri) == NULL)
		return(NULL);
	  p++;

	  if (parse_selector(p, &eptr, NULL) == -1) {
		if (eptr == p) {
		  ds_append(&ds, ic.component);
		  continue;
		}
		return(NULL);
	  }

	  ic.separator = "/";
	  if ((ic.argc = mkargv(ic.component, &path_conf, &ic.argv)) == -1)
		return(NULL);
	}
	else if (*p == 's') {
	  char *eptr;
	  Mkargv host_conf = { 0, 0, ".", NULL, NULL };

	  if ((ic.component = hostname) == NULL)
		return(NULL);
	  p++;

	  if (parse_selector(p, &eptr, NULL) == -1) {
		if (eptr == p) {
		  ds_append(&ds, ic.component);
		  continue;
		}
		return(NULL);
	  }

	  ic.separator = ".";
	  if ((ic.argc = mkargv(ic.component, &host_conf, &ic.argv)) == -1)
		return(NULL);
	}
	else if (*p == 'b') {
	  char *str;

	  p++;
	  if (*p == 'A')
		str = APACHE_HOME;
	  else if (*p == 'D')
		str = DACS_HOME;
	  else
		return(NULL);
	  ds_append(&ds, str);

	  p++;
	  continue;
	}
	else {
	  Mkargv host_conf = { 0, 0, ".", NULL, NULL };

	  if (parse_selector(p, &endptr, NULL) != -1) {
		if ((ic.component = hostname) == NULL)
		  return(NULL);

		ic.separator = ".";
		if ((ic.argc = mkargv(ic.component, &host_conf, &ic.argv)) == -1)
		  return(NULL);
	  }
	  else {
		if (p != endptr)
		  return(NULL);
		ds_appendc(&ds, (int) *p++);
		continue;
	  }
	}

	if ((c = interpolate_component(p, &endptr, &ic)) == NULL)
	  return(NULL);
	ds_append(&ds, c);
	p = endptr;
  }

  ds_appendc(&ds, (int) '\0');

  return(ds_buf(&ds));
}

int
show_license(int argc, char **argv, int do_init, void *main_out)
{
#ifdef WWW_MISC_DIR
  char *buf, *pathname;
  size_t size;

  pathname = ds_xprintf("%s/LICENSE", WWW_MISC_DIR);
  if (load_file(pathname, &buf, &size) != -1) {
	fprintf(stdout, "This product includes DACS.\n");
	fprintf(stdout, "The following license can be found in %s\n\n", pathname);
	fwrite(buf, size, 1, stdout);
  }
  else
#endif
  {
	fprintf(stdout, "Sorry, I can't seem to find the license for DACS.\n");
	fprintf(stdout, "Please visit http://dacs.dss.ca for licensing terms.\n");
  }

  return(0);
}

static int
directory_list_add(char *naming_context, char *name, void ***arg)
{
  char *p;
  Directory_list *dl;

  dl = (Directory_list *) arg;
  p = name + strlen(naming_context) + 1;
  if (dl->exclude_dot_files && *p == '.')
	return(0);
	
  dsvec_add_ptr(dl->dsv, ds_xprintf("%s", name));

  return(1);
}

/*
 * Generate a simple listing of files in a directory.
 * Non-recursive.  Path prefixes (relative to DIRNAME) are retained.
 * Dot-files can optionally be omitted.
 * Return the number of file names, or -1 if an error occurs.
 * XXX there are lots of potential extensions
 * XXX a more generic "listing" function for any object type might be useful
 */
int
directory_list(char *dirname, Directory_list *dl)
{
  int n;
  struct stat sb;
  Vfs_handle *h;

  if (stat(dirname, &sb) == -1) {
	log_err((LOG_ERROR_LEVEL, "Can't stat %s", dirname));
	return(-1);
  }

  if ((sb.st_mode & S_IFMT) != S_IFDIR) {
	log_msg((LOG_WARN_LEVEL, "%s must be a directory", dirname));
	return(-1);
  }

  /* If the user has provided a list, append to it. */
  if (dl->dsv == NULL)
	dl->dsv = dsvec_init(NULL, sizeof(char *));

  if ((h = vfs_open_any(dirname)) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Cannot open directory %s for listing", dirname));
	return(-1);
  }

  if ((n = vfs_list(h, NULL, NULL, directory_list_add, (void ***) dl)) == -1) {
	vfs_close(h);
	return(-1);
  }

  vfs_close(h);

  return(n);
}

/*
 * Given KWV, flatten it into a string, encrypt it, convert it to
 * a base 64 text string, and return a pointer to it.
 */
char *
encrypt_kwv(Kwv *kwv)
{
  Crypt_keys *ck;
  char *buf, *str;
  unsigned char *cryptbuf;
  unsigned int clen;

  str = kwv_str(kwv);
  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
  clen = crypto_encrypt_string(ck, (unsigned char *) str, strlen(str) + 1,
							   &cryptbuf);
  crypt_keys_free(ck);
  free(str);
  strba64(cryptbuf, clen, &buf);
  free(cryptbuf);

  return(buf);
}

/*
 * The caller should destroy KWV_STR as soon as it's no longer needed.
 */
Kwv *
decrypt_kwv(char *kwv_str)
{
  char *buf;
  unsigned int crypt_len, plaintext_len;
  unsigned char *encrypted;
  Crypt_keys *ck;
  Kwv *kwv;
  Kwv_conf kwv_conf = {
	"=", NULL, " ", KWV_CONF_DEFAULT, NULL, 30, NULL, NULL
  };

  encrypted = malloc(strlen(kwv_str));
  if (stra64b(kwv_str, &encrypted, &crypt_len) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Error decrypting kwv: kwv_str=\"%s\"", kwv_str));
    return(NULL);
  }

  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
  if (crypto_decrypt_string(ck, encrypted, crypt_len, (unsigned char **) &buf,
							&plaintext_len)
	  == -1) {
	crypt_keys_free(ck);
	log_msg((LOG_ERROR_LEVEL, "decryption failed"));
	return(NULL);
  }
  crypt_keys_free(ck);

  if ((kwv = kwv_make_new(buf, &kwv_conf)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Kwv make error"));
	return(NULL);
  }
  memzap(buf, plaintext_len);

  return(kwv);
}

enum {
  SHM_LEN = 1024,			/* Length of the shared segment */
  USTAMP_HOSTID_LEN = 16
};

static int system_truncate_extends = -1;
static int system_mmap_mapfixed_works = -1;
static long system_page_size = 0;
static int segment_done_init = 0;

static long
get_system_page_size(void)
{

#ifdef HAVE_SYSCONF
  system_page_size = sysconf(_SC_PAGESIZE);
#else
  /* XXX or try something else... */
  system_page_size = -1;
#endif

  return(system_page_size);
}

/* Round NBYTES up to the nearest number of system pages. */
static inline MAYBE_UNUSED long
nbytes_to_npages(long nbytes)
{

  if (system_page_size == 0)
	get_system_page_size();

  if (system_page_size == -1)
	return(-1);

  return((nbytes + (system_page_size - 1)) / system_page_size);
}

/* Round NBYTES up to the nearest system page size. */
static inline MAYBE_UNUSED long
nbytes_to_pagebytes(long nbytes)
{

  if (system_page_size == 0)
	get_system_page_size();

  if (system_page_size == -1)
	return(-1);

  return(nbytes_to_npages(nbytes) * system_page_size);
}

#if !defined(HAVE_SHM_OPEN)
int
ustamp_get_seqno(char *ntp_host, char **seqno_str)
{

  return(-1);
}

int
segment_test(void)
{

  return(-1);
}

int
segment_init(void)
{

  return(0);
}

int
segment_open(char *path, int flags, size_t length, mode_t mode,
			 Segment *segment)
{

  return(-1);
}

int
segment_extend(Segment *segment, size_t req_length)
{

  return(-1);
}

int
segment_close(Segment *segment)
{

  return(-1);
}

int
segment_delete(Segment *segment)
{

  return(-1);
}
#else

#include <sys/mman.h>

#ifdef NOTDEF
/*
 * Test if a) truncate(2) and ftruncate(2) have the (non-POSIX) ability to
 * extend a file by "truncating" it to a size greater than its current size
 * and b) whether mmap() with MAP_FIXED seems to work.
 * Return 1 if the test was performed, or -1 if the result could not be
 * determined.  Also set global flags.
 *
 * Setting aside the question of whether a function called "truncate" ought to
 * increase the size of a file, this capability is handy.
 * On FreeBSD at least, the extension is zero-filled.
 * Note that mmap(2) has the doubleplusgood option of letting the caller
 * request that an mmap-ped region be extended without changing the base
 * address (MAP_FIXED), preserving the validity of pointers into and
 * within the region.  But there is apparently no way for a process to
 * find out what mmap-ed regions it has allocated (e.g., by malloc() or
 * some other library function), and if by extending a region a process causes
 * it to overlap one or more other regions, chaos will ensue when one region
 * gets overwritten or someone calls munmap().
 * So this capability seems to be effectively unusable in this way.
 */
static int
segment_test(void)
{
  int fd_shm, fd_tmp, st;
  char *tmp_path;
  void *curr_ptr, *ptr;
  size_t length, curr_length;
  struct stat statbuf;

#if !#defined(HAVE_MKSTEMP)
  return(-1);
#endif

  system_truncate_extends = -1;
  system_mmap_mapfixed_works = -1;

  if (get_system_page_size() == -1)
	return(-1);

  ptr = NULL;
  curr_ptr = NULL;
  st = -1;
  curr_length = 0;
  tmp_path = create_temp_filename(NULL);
  if ((fd_tmp = mkstemp(tmp_path)) == -1) {
	fprintf(stderr, "mkstemp: %s\n", strerror(errno));
	return(-1);
  }

  if ((fd_shm = shm_open(tmp_path, O_RDWR, 0660)) == -1) {
	fprintf(stderr, "shm_open1: %s\n", strerror(errno));
	goto done;
  }

  if (fstat(fd_shm, &statbuf) == -1) {
	fprintf(stderr, "fstat: %s\n", strerror(errno));
	goto done;
  }
  if (statbuf.st_size != 0) {
	fprintf(stderr, "Created non-empty file?\n");
	goto done;
  }

  length = system_page_size;
  if (ftruncate(fd_shm, (off_t) length) == -1) {
	fprintf(stderr, "ftruncate: %s\n", strerror(errno));
	goto done;
  }

  if (fstat(fd_shm, &statbuf) == -1) {
	fprintf(stderr, "fstat: %s\n", strerror(errno));
	goto done;
  }

  ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0);
  if (ptr == MAP_FAILED) {
	fprintf(stderr, "Note: initial mmap failed: %s", strerror(errno));
	system_mmap_mapfixed_works = -1;
  }
  else {
	curr_ptr = ptr;
	curr_length = length;
  }

  length = system_page_size * 10;
  if (ftruncate(fd_shm, (off_t) length) == -1) {
	fprintf(stderr, "ftruncate: %s\n", strerror(errno));
	goto done;
  }

  if (fstat(fd_shm, &statbuf) == -1) {
	fprintf(stderr, "fstat: %s\n", strerror(errno));
	goto done;
  }
  if (statbuf.st_size != length) {
	fprintf(stderr, "Could not extend %s: st_size=%ld, curr_length=%lu\n",
			tmp_path, (long) statbuf.st_size, (unsigned long) length);
	st = 1;
	goto done;
  }
  if (ptr != MAP_FAILED) {
	ptr = mmap(curr_ptr, length, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED,
			   fd_shm, 0);
	if (ptr == MAP_FAILED) {
	  fprintf(stderr, "Note: mmap extension failed: %s", strerror(errno));
	  system_mmap_mapfixed_works = 0;
	}
	else {
	  curr_ptr = ptr;
	  curr_length = length;
	}
  }

  /* All ok. */
  st = 1;
  system_truncate_extends = 1;
  system_mmap_mapfixed_works = 1;

 done:
  close(fd_tmp);
  if (fd_shm != -1)
	close(fd_shm);
  if (curr_ptr != NULL)
	munmap(curr_ptr, curr_length);
  shm_unlink(tmp_path);
  unlink(tmp_path);

  return(st);
}
#else
static int
segment_test(void)
{

  return(1);
}
#endif

static char *
segment_lockfile_path(char *path)
{
  char *lock_path;

  lock_path = ds_xprintf("%s-lock", path);

  return(lock_path);
}

/*
 * Open a persistent, shared memory object (a "segment") named PATH of
 * size LENGTH bytes, creating it if necessary with file bits MODE,
 * and locking it for exclusive access.  If the segment exists, its length
 * will be shortened or extended to LENGTH bytes.  PATH must be an absolute
 * pathname and it must be possible to create it (if necessary) and write to it.
 * Return 0 if successful; otherwise return -1 and set SEGMENT->ERRMSG
 * to a descriptive message.
 *
 * Not yet implemented: FLAGS is a subset of the flags to open(2) and is
 * the ORing of:
 *   a) one of: SEGMENT_RDONLY, SEGMENT_WRONLY, SEGMENT_RDWR
 *   b) with SEGMENT_WRONLY or SEGMENT_RDWR: SEGMENT_CREAT and/or SEGMENT_TRUNC
 * This will allow the caller to require the segment to already exist,
 * be created it it does not exist, or be truncated if it already exists.
 * SEGMENT_RDONLY requires a read lock, SEGMENT_WRONLY and SEGMENT_RDWR require
 * a write lock.
 *
 * Not implemented: add a header that includes meta information, such as
 * the current size of the segment, last modification date, identity of
 * creator and last modifier, etc.
 *
 * Note that Posix says:
 *   It is unspecified whether the name appears in the file system and is
 *   visible to other functions that take pathnames as arguments. The name
 *   argument conforms to the construction rules for a pathname. If name
 *   begins with the slash character, then processes calling shm_open() with
 *   the same value of name refer to the same shared memory object, as long as
 *   that name has not been removed. If name does not begin with the slash
 *   character, the effect is implementation-dependent. The interpretation of
 *   slash characters other than the leading slash character in name is
 *   implementation-dependent. 
 * On FreeBSD, the first argument is a path; on GNU/Linux, the first argument
 * is a name that cannot contain embedded slashes.

 * Note: FreeBSD's shm_open() says that using open/read/write on PATH
 * (a shared memory object) or the descriptor returned by shm_open() is
 * undefined, as is whether the shared memory object or its contents
 * persist across reboots.  Although it is a pathname, it is unspecified
 * whether PATH exists in the filesystem and is visible to other functions.
 * XXX this is not intended for heavy-duty use.
 */
int
segment_open(char *path, int flags, size_t req_length, mode_t mode,
			 Segment *segment)
{
  int i;
  char ch;
  size_t length;

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

  /*
   * This must be an absolute pathname because FreeBSD's shm_open(3) says:
   *   "Two processes opening the same path are guaranteed to access the same
   *    shared memory object if and only if path begins with a slash (`/')
   *    character."
   */
  if (path == NULL || path[0] != '/') {
	segment->errmsg = "segment_open: invalid path argument";
	return(-1);
  }

  segment_init();

  segment->fd_lock = segment->fd_shm = -1;
  segment->path = strdup(path);
#ifdef DACS_OS_LINUX
  strtr(path + 1, "/", "-", 0, 0, 0, &segment->name);
#else
  segment->name = strdup(path);
#endif
  segment->lock_path = segment_lockfile_path(path);
  segment->req_length = req_length;
  length = segment->length = nbytes_to_pagebytes(req_length);
  segment->mode = mode;
  segment->ptr = NULL;
  segment->errmsg = NULL;
  segment->status = SEGMENT_UNKNOWN;

  /*
   * Wait (indefinitely) for exclusive access.
   * Note that this lock file may be distinct from the shared memory
   * object because of shm_open() semantics; it is unclear whether it is
   * safe to lock the shared memory object descriptor.
   * POSIX says that the lock operations performed by fcntl() are unspecified
   * for a shared memory object..
   * XXX maybe we could use O_EXLOCK here, but apparently it is not
   * POSIX compatible, so use lockf()
   */
  segment->fd_lock = open(segment->lock_path, O_RDWR | O_CREAT, mode);
  if (segment->fd_lock == -1) {
	segment->errmsg = ds_xprintf("open: \"%s\": %s",
								 segment->lock_path, strerror(errno));
	goto fail;
  }

  if (lockf(segment->fd_lock, F_LOCK, 0) == -1) {
	segment->errmsg = ds_xprintf("lockf: \"%s\": %s",
								 segment->lock_path, strerror(errno));
	goto fail;
  }

  /*
   * Create the segment, or fail.
   * We cannot use O_EXLOCK here because FreeBSD's shm_open(3) says:
   *   "Only the O_RDONLY, O_RDWR, O_CREAT, O_EXCL, and O_TRUNC flags may be
   *    used in portable programs."
   */
  segment->fd_shm = shm_open(segment->name, O_RDWR | O_EXCL | O_CREAT, mode);
  if (segment->fd_shm == -1) {
	struct stat statbuf;

	if (errno != EEXIST) {
	  /* Some other unrecoverable error occurred... */
	  segment->errmsg = ds_xprintf("shm_open1: \"%s\": %s",
								   segment->name, strerror(errno));
	  goto fail;
	}

	/*
	 * The open failed, but the segment exists.
	 * Open it again and get an exclusive lock.
	 */
	segment->status = SEGMENT_EXISTS;
	if ((segment->fd_shm = shm_open(segment->name, O_RDWR, mode)) == -1) {
	  segment->errmsg = ds_xprintf("shm_open2: \"%s\": %s",
								   segment->name, strerror(errno));
	  goto fail;
	}

	if (fstat(segment->fd_shm, &statbuf) == -1) {
	  segment->errmsg = ds_xprintf("fstat: %s", strerror(errno));
	  goto fail;
	}

	/*
	 * Shrink or extend the segment, as necessary.
	 */
	if (statbuf.st_size > length) {
	  /* The backing store file is too big. */
	  if (ftruncate(segment->fd_shm, (off_t) length) == -1) {
		segment->errmsg = ds_xprintf("ftruncate: %s", strerror(errno));
		goto fail;
	  }
	}
	else if (statbuf.st_size < length) {
	  int st;
	  off_t pos;
	  size_t ext;

	  /*
	   * The backing store file must be extended.
	   * Note: whether ftruncate() can grow a file is unspecified and
	   * non-portable.
	   * FreeBSD's mmap(2) says:
	   *   "Extending a file with ftruncate(2), thus creating a big hole,
	   *    and then filling the hole by modifying a shared mmap() can lead to
	   *    severe file fragmentation.  In order to avoid such fragmentation you
	   *    should always pre-allocate the file's backing store by
	   *    write()ing zero's into the newly extended area prior to
	   *    modifying the area via your mmap()."
	   * But with FreeBSD 9.0, using write(2) on a shared memory object fails
	   * with EOPNOTSUPP; shm_open(2) also says that the "size of the object
	   * can be adjusted via ftruncate(2)".
	   */
#ifdef DACS_OS_FREEBSD9
	  if (ftruncate(segment->fd_shm, (off_t) length) == -1) {
		segment->errmsg = ds_xprintf("ftruncate/extend: %s", strerror(errno));
		goto fail;
	  }
#else
	  ext = length - statbuf.st_size;
	  if ((pos = lseek(segment->fd_shm, 0, SEEK_END)) == (off_t) -1) {
		segment->errmsg = ds_xprintf("lseek: %s", strerror(errno));
		goto fail;
	  }

	  ch = 0;
	  for (i = 0; i < ext; i++) {
		if ((st = write(segment->fd_shm, &ch, 1)) == -1) {
		  segment->errmsg = ds_xprintf("write/extend: %s", strerror(errno));
		  goto fail;
		}
	  }
#endif
	}

	/*
	 * Map the shared memory object.
	 * The actual length has been rounded up to the nearest multiple of
	 * the system's page size since mmap() does that anyway.
	 */
	segment->ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED,
						segment->fd_shm, 0);
	if (segment->ptr == MAP_FAILED) {
	  segment->errmsg = ds_xprintf("mmap: %s", strerror(errno));
	  goto fail;
	}
  }
  else {
	/*
	 * A new segment was created; lock and initialize it.
	 * XXX maybe we could use O_EXLOCK here, but apparently it is not
	 * POSIX compatible, so use lockf()
	 */
	segment->status = SEGMENT_NEW;

#ifdef DACS_OS_FREEBSD9
	if (ftruncate(segment->fd_shm, length) == -1) {
	  segment->errmsg = ds_xprintf("ftruncate/new: %s", strerror(errno));
	  goto fail;
	}
#else
	/*
	 * Initialize the new segment.
	 * XXX FreeBSD 9.0 does not support write(2) on a shared memory object!
	 * In fact, its shared memory support seems buggy - FreeBSD 7.x worked
	 * better.
	 */
	ch = 0;
	for (i = 0; i < length; i++) {
	  if (write(segment->fd_shm, &ch, 1) == -1) {
		segment->errmsg = ds_xprintf("write/new: %s", strerror(errno));
		goto fail;
	  }
	}
#endif

	/*
	 * Map the shared memory object.
	 * The actual length will be rounded up to the nearest multiple of
	 * the system's page size since mmap() does that anyway.
	 */
	segment->ptr = mmap(NULL, length, PROT_READ | PROT_WRITE,
						MAP_SHARED, segment->fd_shm, (off_t) 0);
	if (segment->ptr == MAP_FAILED) {
	  segment->errmsg = ds_xprintf("mmap: %s", strerror(errno));
	  goto fail;
	}
  }

  lseek(segment->fd_lock, 0, SEEK_SET);
  lockf(segment->fd_lock, F_ULOCK, 0);

  return(0);

 fail:

  if (segment->fd_shm != -1) {
	close(segment->fd_shm);
	segment->fd_shm = -1;
  }

  if (segment->fd_lock != -1) {
	/* This also removes any lock. */
	close(segment->fd_lock);
	segment->fd_lock = -1;
  }

  return(-1);
}

/*
 * Return 1 if initialization is successful, 0 if this feature is unavailable,
 * or -1 otherwise.
 */
int
segment_init(void)
{
  struct utsname name;

  if (segment_done_init)
	return(segment_done_init);

  if (uname(&name) == -1) {
	segment_done_init = -1;
    return(-1);
  }
  /*
   * XXX it seems that the current implementation does not work on FreeBSD 8.x
   * and Mac OS X because they do shared memory differently than FreeBSD 7.x
   * and Linux.
   * Until we can work around this, we just won't provide this capability
   * on those platforms... it's not currently essential anyway.
   */
  if ((strcaseeq(name.sysname, "FreeBSD") && name.release[0] == '8')
	  || strcaseeq(name.sysname, "Darwin")
	  || strcaseprefix(name.sysname, "Cygwin") != NULL
	  || strcaseeq(name.sysname, "SunOS"))
	return(0);

  segment_done_init = segment_test();

  return(segment_done_init);
}

/*
 * Attempt to grow the length of shared SEGMENT to REQ_LENGTH bytes
 * without changing its base address.
 * If the segment is already large enough, nothing is done.
 * Return 1 if successful, -1 otherwise.
 */
int
segment_extend(Segment *segment, size_t req_length)
{
  int i, st;
  char ch;
  off_t pos;
  size_t ext, new_length;

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

  if (!segment_done_init)
	return(-1);

  if (system_mmap_mapfixed_works != 1)
	return(-1);

  if (segment->status == SEGMENT_UNKNOWN)
	return(-1);

  new_length = nbytes_to_pagebytes(req_length);

  if (new_length <= segment->length) {
	segment->req_length = req_length;
	return(1);
  }

  st = -1;
  if (lockf(segment->fd_lock, F_LOCK, 0) == -1) {
	segment->errmsg = ds_xprintf("lockf: %s", strerror(errno));
	goto fail;
  }

  /*
   * The backing store file must be extended.
   * Note: whether ftruncate() can grow a file is unspecified and
   * non-portable.
   * FreeBSD's mmap(2) says:
   *   "Extending a file with ftruncate(2), thus creating a big hole,
   *    and then filling the hole by modifying a shared mmap() can lead to
   *    severe file fragmentation.  In order to avoid such fragmentation you
   *    should always pre-allocate the file's backing store by
   *    write()ing zero's into the newly extended area prior to
   *    modifying the area via your mmap()."
	   */
  ext = new_length - segment->length;
  if ((pos = lseek(segment->fd_shm, 0, SEEK_END)) == (off_t) -1) {
	segment->errmsg = ds_xprintf("lseek: %s", strerror(errno));
	goto fail;
  }

  ch = 0;
  for (i = 0; i < ext; i++) {
	if ((st = write(segment->fd_shm, &ch, 1)) == -1) {
	  segment->errmsg = ds_xprintf("write: %s", strerror(errno));
	  goto fail;
	}
  }

  /*
   * Map the shared memory object.
   * The actual length has been rounded up to the nearest multiple of
   * the system's page size since mmap() does that anyway.
   */
  segment->ptr = mmap(segment->ptr, new_length,
					  PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED,
					  segment->fd_shm, 0);
  if (segment->ptr == MAP_FAILED) {
	segment->errmsg = ds_xprintf("mmap: %s", strerror(errno));
	goto fail;
  }

  st = 1;
  segment->req_length = req_length;
  segment->length = new_length;

 fail:
  lseek(segment->fd_lock, 0, SEEK_SET);
  lockf(segment->fd_lock, F_ULOCK, 0);

  return(st);
}

/*
 * Close and unlock SEGMENT, which was previously opened by segment_open().
 */
int
segment_close(Segment *segment)
{

  if (!segment_done_init || segment == NULL)
	return(-1);

  if (segment->ptr != NULL && segment->ptr != MAP_FAILED) {
	if (munmap(segment->ptr, segment->length) == -1)
	  log_err((LOG_ERROR_LEVEL, "munmap"));
	segment->ptr = NULL;
  }

  if (segment->fd_shm != -1) {
	if (close(segment->fd_shm) == -1)
	  log_err((LOG_ERROR_LEVEL, "close fd_shm"));
	segment->fd_shm = -1;
  }

  if (segment->fd_lock != -1) {
	if (close(segment->fd_lock) == -1)
	  log_err((LOG_ERROR_LEVEL, "close fd_lock"));
	segment->fd_lock = -1;
  }

  segment->length = 0;
  segment->mode = 0;
  segment->errmsg = NULL;
  log_msg((LOG_TRACE_LEVEL, "Closed segment \"%s\"", segment->path));
  segment->path = NULL;
  segment->name = NULL;
  segment->lock_path = NULL;

  return(0);
}

int
segment_delete(Segment *segment)
{
  int st;

  errno = 0;
  if (!segment_done_init)
	return(-1);

  if (shm_unlink(segment->name) == -1)
	return(-1);

  if (unlink(segment->lock_path) == -1)
	return(-1);

  return(0);
}

/*
 * A stamp consists of a start instant (assumed to be monotonically
 * increasing) and a counter relative to that starting time (which
 * increases monotonically).
 */
typedef struct Ustamp_seqno {
  time_t secs;
  unsigned long count;
} Ustamp_seqno;

/*
 * Initialize a sequence number stream.
 * If NTP_HOST is given, it is a hostname/IP address with optional port number
 * for an NTP server from which to get the current time.
 * Without NTP_HOST, the system clock is used.
 */
static Ustamp_seqno *
ustamp_init_seqno(char *ntp_host)
{
  Ustamp_seqno *seq;

  if (ntp_host != NULL)
	return(NULL);

  seq = ALLOC(Ustamp_seqno);
  time(&seq->secs);
  seq->count = 0;

  return(seq);
}

/*
 * Format a sequence number as a string.
 */
static char *
ustamp_seqno_str(Ustamp_seqno *seq)
{
  char *str;

  str = ds_xprintf("%lu:%lu", seq->secs, seq->count);

  return(str);
}

/*
 * Get the next sequence number string,  initializing the sequence if
 * necessary. Shared memory is used so that multiple processes can (safely) get
 * unique sequence numbers.
 * The configuration variable ${Conf::ustamp_seqno} must be set to the
 * name of the POSIX shared memory object where the current sequence number
 * will be stored.
 */
int
ustamp_get_seqno(char *ntp_host, char **seqno_str)
{
  int st;
  char *seqno_pathname;
  Segment *segment;
  Ustamp_seqno *ptr, *seq;

  if ((seqno_pathname = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
										 "ustamp_seqno")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Config variable ustamp_seqno is undefined"));
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "ustamp_seqno=\"%s\"", seqno_pathname));

  st = 0;
  segment = ALLOC(Segment);
  if (segment_open(seqno_pathname, 0, SHM_LEN, 0660, segment) == -1) {
	if (segment->errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "ustamp_get_seqno: %s", segment->errmsg));
	else
	  log_msg((LOG_ERROR_LEVEL, "ustamp_get_seqno: failed"));
	return(-1);
  }

  if (segment->status == SEGMENT_NEW) {
	/* A new segment was created. */
	if ((seq = ustamp_init_seqno(ntp_host)) == NULL)
	  goto done;

	ptr = (Ustamp_seqno *) segment->ptr;
	*ptr = *seq;
	*seqno_str = ustamp_seqno_str(seq);
	free(seq);
	log_msg((LOG_DEBUG_LEVEL, "Initializing seqno to %s", *seqno_str));
  }
  else if (segment->status == SEGMENT_EXISTS) {
	/* Increment, then use - XXX no overflow check... */
	ptr = (Ustamp_seqno *) segment->ptr;
	ptr->count++;
	*seqno_str = ustamp_seqno_str(ptr);	
	log_msg((LOG_TRACE_LEVEL, "Generating seqno %s", *seqno_str));
  }
  else
	st = -1;

 done:
  if (st == -1)
	log_msg((LOG_ERROR_LEVEL, "Could not generate seqno"));

  /* Unlock, etc. */
  segment_close(segment);
  free(segment);

  return(st);
}
#endif

/*
 *
 */
static int
ustamp_make_hostid(char **hostid)
{
  int i;
  unsigned char r[USTAMP_HOSTID_LEN];
  Ds ds;

  if (crypto_randomize_buffer(r, sizeof(r)) == -1)
	return(-1);

  ds_init(&ds);
  for (i = sizeof(r) - 1; i >= 0; i--)
	ds_asprintf(&ds, "%x", r[i]);

  *hostid = ds_buf(&ds);

  return(0);
}

/*
 * Create a unique hostid string and save it in VFS_URI.
 */
static int
ustamp_set_hostid(Vfs_handle *h, char **hostid)
{

  if (ustamp_make_hostid(hostid) == -1)
	return(-1);

  if (vfs_put(h, NULL, *hostid, strlen(*hostid)) == -1)
	return(-1);

  return(0);
}

/*
 * Return the unique hostid string.
 * If it does not exist, this string is created and stored in VFS_URI,
 * after which it is retrieved from there.
 * Deleting the VFS object will therefore cause a new hostid string to
 * be created.
 * Return -1 if the hostid string cannot be read or created, 0 otherwise.
 */
static int
ustamp_get_hostid(char *vfs_uri, char **hostid)
{
  int st;
  Vfs_handle *h;

  if ((h = vfs_open_any(vfs_uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not open \"%s\"", vfs_uri));
	return(-1);
  }

  if ((st = vfs_exists(h, NULL)) == 1) {
	if (vfs_get(h, NULL, (void **) hostid, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not get \"%s\"", vfs_uri));
	  st = -1;
	}
	else
	  st = 0;
  }
  else if (st == 0) {
	if (ustamp_set_hostid(h, hostid) == -1)
	  st = -1;
  }
  else
	st = -1;

  vfs_close(h);

  return(st);
}

/*
 * Use the system clock at the best possible resolution to form a
 * unique sequenced string.
 */
char *
ustamp_clock(char *hid, char *vfs_uri)
{
  char *hostid, *seqno_str, *ustamp;

  if (hid == NULL) {
	if (ustamp_get_hostid(vfs_uri, &hostid) == -1)
	  return(NULL);
  }
  else
	hostid = hid;

  if (ustamp_get_seqno(NULL, &seqno_str) == -1)
	return(NULL);

  ustamp = ds_xprintf("h=%s, s=%s", hostid, seqno_str);

  return(ustamp);
}

/*
 * Use the ntp server at NTP_HOST at the best possible resolution to form a
 * unique sequenced string.
 * NTP_HOST may be an IP address or a domain name.  A colon and port number
 * may be appended to override the default NTP port.
 */
char *
ustamp_ntpclock(char *hid, char *vfs_uri, char *ntp_host)
{

  return(NULL);
}

/*
 * Use USER_UNIQUE to form a unique sequenced string.
 */
char *
ustamp_user(char *hid, char *vfs_uri, char *user_unique)
{
  char *hostid;
  pid_t pid;
  Ds ds;
  static unsigned int counter = 0;

  if (hid == NULL) {
	if (ustamp_get_hostid(vfs_uri, &hostid) == -1)
	  return(NULL);
  }
  else
	hostid = hid;

  pid = getpid();

  ds_init(&ds);
  ds_asprintf(&ds, "h=%s, p=%u, s=%s.%u", hostid, pid, user_unique, counter++);

  return(ds_buf(&ds));
}

#ifdef NOTDEF
int
ustamp_compare(char *stamp1, char *stamp2)
{

}
#endif

#ifdef PROG
int
main(int argc, char **argv)
{
  int st;
  char *errmsg, *tmp_path;
  mode_t mode;
  Segment segment;

  fprintf(stderr, "Testing core functions...\n");
  fprintf(stderr, "Shared memory segment support... ");
  if ((st = segment_init()) == -1) {
	fprintf(stderr, "failed\n");
	exit(1);
  }
  else if (st == 0) {
	fprintf(stderr, "unavailable on this platform, skipping tests\n");
	exit(0);
  }
  fprintf(stderr, "ok\n");

  tmp_path = create_temp_filename(NULL);
  fprintf(stderr, "Segment open, extend, close, delete... ");
  mode = 0660;
  if (segment_open(tmp_path, 0, 1024, mode, &segment) == -1) {
	errmsg = "segment_open";
	goto failed;
  }

  if (segment_delete(&segment) == -1) {
	errmsg = "segment_delete";
	goto failed;
  }
  fprintf(stderr, "ok\n");

  exit(0);

 failed:

  if (segment.errmsg != NULL)
	fprintf(stderr, "%s failed: %s\n", errmsg, segment.errmsg);
  else if (errmsg != NULL)
	fprintf(stderr, "%s: %s\n", errmsg, strerror(errno));
  else
	fprintf(stderr, "failed\n");

  exit(1);  
}
#endif
