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

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: misc.c 2878 2016-05-16 23:02:19Z brachman $";
#endif

#ifdef DSSLIB
#include "dsslib.h"
#else
#include "local.h"
#endif

#include <signal.h>

static const char *log_module_name = "misc";

static int
abs_mode(char *mode_str, mode_t *mode)
{

  return(strnum_b(mode_str, STRNUM_MODE_T, 8, mode, NULL));
}

/*
 * Create PATHNAME with MODE_STR if it does not already exist, otherwise set the
 * access and modification times of PATHNAME to the current time (and not change its
 * mode).
 */
int
touch_file(char *pathname, char *mode_str, char **errmsg)
{
  int fd, st;
  mode_t mode;
  struct stat statb;

  st = stat(pathname, &statb);
  if (st == -1) {
    if (errno != ENOENT) {
	  *errmsg = strdup(strerror(errno));
      return(-1);
	}

    /* Doesn't exist, create it. */
    if (mode_str == NULL)
      mode = 0644;
    else if (abs_mode(mode_str, &mode) == -1) {
      *errmsg = "Invalid mode";
      return(-1);
    }

    fd = open(pathname, O_WRONLY | O_CREAT, mode);
    if (fd == -1 || fstat(fd, &statb) != 0 || close(fd) != 0) {
      *errmsg = strdup(strerror(errno));
      return(-1);
    }
  }
  else {
    /* Exists, update it. */
    if (utimes(pathname, NULL) == -1) {
      *errmsg = strdup(strerror(errno));
      return(-1);
    }
  }

  return(0);
}

int
chmod_file(char *pathname, char *mode_str, char **errmsg)
{
  mode_t mode;

  if (abs_mode(mode_str, &mode) != -1) {
	if (chmod(pathname, mode) == -1) {
	  *errmsg = strdup(strerror(errno));
	  return(-1);
	}
  }
  else {
	*errmsg = "Invalid mode";
	return(-1);
  }

  return(1);
}

int
mkdir_file(char *dirname, char *mode_str, char **errmsg)
{
  mode_t mode;

  if (mode_str == NULL)
	mode = 0755;
  else if (abs_mode(mode_str, &mode) == -1) {
	*errmsg = "Invalid mode";
	return(-1);
  }

  if (mkdir(dirname, mode) == -1) {
	*errmsg = strdup(strerror(errno));
	return(-1);
  }

  return(1);
}

/*
 * Allocate a buffer to hold FILENAME and read the contents into it.
 * If FILENAME is NULL, read stdin.
 * The buffer is always null terminated.
 * If SIZE is non-NULL, it is set to the size of the file.
 * The caller is responsible for calling free().
 * Returns -1 on an error, 0 otherwise.
 */
int
load_file(char *pathname, char **buf, size_t *size)
{
  Ds *ds;

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

  if ((ds = ds_load_file(NULL, pathname)) == NULL) {
	if (ds_errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "load_file: \"%s\": %s", pathname, ds_errmsg));
	return(-1);
  }

  *buf = ds_buf(ds);

  if (size != NULL)
	*size = ds_len(ds) - 1;

  /* The following prevents the freeing of the buffer we're returning. */
  ds->buf = NULL;
  ds_free(ds);

  return(0);
}

#define CHILD_TO_PARENT			pipe_to_child[1]
#define PARENT_TO_CHILD			pipe_from_child[1]
#define CHILD_FROM_PARENT		pipe_from_child[0]
#define PARENT_FROM_CHILD		pipe_to_child[0]
#define CHILD_ERROR_TO_PARENT	pipe_error[1]
#define PARENT_ERROR_FROM_CHILD	pipe_error[0]

/*
 * Start a process with descriptors to write to its stdin and read from its
 * stdout.
 *
 * The first element of ARGV is the pathname for the file to be executed.
 * The last element of ARGV must be NULL.
 * ENV, which may be NULL, is a vector of pointers to "NAME=VALUE" strings,
 * terminated by a NULL pointer.  The read_fd/write_fd/error_fd pointers,
 * any of which may be NULL if they are not required, are set to descriptors
 * to the process's read/write/error streams (stdin, stdout, stderr).
 *   a) writes to WRITE_FD by the calling process go to the standard input
 *      of the  new process;
 *   b) reads from READ_FD by the calling process come from the standard
 *      output of the new process; and
 *   c) reads from ERROR_FD by the calling process come from the standard
 *      error of the new process.
 *
 * The caller is responsible for waiting for the new process to terminate,
 * checking for errors, and so on.
 *
 * Return -1 if the set up fails, 0 otherwise.
 *
 * Note that this might fail by a deadlock if the new process is blocked
 * because its output pipe fills while the caller is still writing to the
 * new process.  The I/O should really be handled asynchronously by the
 * caller, e.g., by using select().
 */
int
filterthru(char **argv, char **env, int *read_fd, int *write_fd,
		   int *error_fd, pid_t *pidp)
{
  int i;
  pid_t pid;
  int pipe_from_child[2], pipe_to_child[2], pipe_error[2];
  extern char **environ;

  pipe_from_child[0] = pipe_from_child[1] = -1;
  pipe_to_child[0] = pipe_to_child[1] = -1;
  pipe_error[0] = pipe_error[1] = -1;

  if (pipe(pipe_from_child) == -1) {
	log_err((LOG_ERROR_LEVEL, "filterthru: pipe() failed"));
	goto err;
  }

  if (pipe(pipe_to_child) == -1) {
	log_err((LOG_ERROR_LEVEL, "filterthru: pipe() failed"));
	goto err;
  }

  if (pipe(pipe_error) == -1) {
	log_err((LOG_ERROR_LEVEL, "filterthru: pipe() failed"));
	goto err;
  }

  signal(SIGPIPE, SIG_IGN); 

  log_msg((LOG_TRACE_LEVEL, "filterthru: executing %s", argv[0]));
  if (log_test(LOG_TRACE_LEVEL)) {
	log_msg((LOG_TRACE_LEVEL, "with arguments:"));
	for (i = 0; argv[i] != NULL; i++)
	  log_msg((LOG_TRACE_LEVEL, "argv[%d]: \"%s\"", i, argv[i]));
  }

  if ((pid = fork()) == 0) {
	/* Child */
	close(PARENT_FROM_CHILD);
	close(PARENT_TO_CHILD);
	close(PARENT_ERROR_FROM_CHILD);
	close(0); 
	dup(CHILD_FROM_PARENT);  
	close(1);
	dup(CHILD_TO_PARENT);
	close(2);
	dup(CHILD_ERROR_TO_PARENT);

	/* Set the environment for the new process. */
	environ = env;

	execv(argv[0], argv);

	log_err((LOG_ERROR_LEVEL, "filterthru: execv of %s failed!", argv[0]));
	_exit(1);
  }  
  else if (pid == -1) {
	log_err((LOG_ERROR_LEVEL, "filterthru: fork failed!"));
	goto err;
  }

  /* Parent */

  close(CHILD_TO_PARENT);  
  close(CHILD_FROM_PARENT);
  close(CHILD_ERROR_TO_PARENT);

  if (write_fd != NULL)
	*write_fd = PARENT_TO_CHILD;
  else
	close(PARENT_TO_CHILD);

  if (read_fd != NULL)
	*read_fd = PARENT_FROM_CHILD;
  else
	close(PARENT_FROM_CHILD);

  if (error_fd != NULL)
	*error_fd = PARENT_ERROR_FROM_CHILD;
  else
	close(PARENT_ERROR_FROM_CHILD);

  if (pidp != NULL)
	*pidp = pid;

  log_msg((LOG_TRACE_LEVEL, "filterthru: parent continuing"));
  return(0);

 err:
  if (pipe_from_child[0] != -1)
	close(pipe_from_child[0]);
  if (pipe_from_child[1] != -1)
	close(pipe_from_child[1]);
  if (pipe_to_child[0] != -1)
	close(pipe_to_child[0]);
  if (pipe_to_child[1] != -1)
	close(pipe_to_child[1]);
  if (pipe_error[0] != -1)
	close(pipe_error[0]);
  if (pipe_error[1] != -1)
	close(pipe_error[1]);

  return(-1);
}

#ifdef DSSLIB

FILE *log_stream = NULL;

#define LOG_NONE_LEVEL 9
#define LOG_INVALID_LEVEL -3

typedef struct Level_map {
  char *name;
  char *alt_names;
  Log_level level;
} Level_map;

/* Indexed by Log_level */
static Level_map log_levels[] = {
  { "trace",    "tracing",          LOG_TRACE_LEVEL },
  { "debug",    "debugging",        LOG_DEBUG_LEVEL },
  { "info",     NULL,               LOG_INFO_LEVEL },
  { "notice",   "notices",          LOG_NOTICE_LEVEL },
  { "warn",     "warning,warnings", LOG_WARN_LEVEL },
  { "error",    "errors",           LOG_ERROR_LEVEL },
  { "critical", "crit",             LOG_CRITICAL_LEVEL },
  { "alert",    "alerts",           LOG_ALERT_LEVEL },
  { "emerg",    "emergency",        LOG_EMERG_LEVEL },
  { "none",     NULL,               LOG_NONE_LEVEL } ,
  { NULL,       NULL,               LOG_INVALID_LEVEL }
};

static char *
log_level_to_name(Log_level level)
{
  int i;

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

  return(NULL);
}

int
log_exec_err(int level, char *fmt, ...)
{
  int err;
  va_list ap;
  Ds ds;
  FILE *fp;
  extern int errno;

  err = errno;

  if ((fp = log_stream) == NULL)
	fp = stderr;

  va_start(ap, fmt);
  ds_init(&ds);
  ds_asprintf(&ds, "Error [%u]: ", getpid());
  ds_vasprintf(&ds, fmt, ap);
  ds_asprintf(&ds, ": %s\n", strerror(err));
  va_end(ap);

  fprintf(fp, "%s", ds_buf(&ds));
  fflush(fp);
  ds_free(&ds);

  return(0);
}

int
log_exec_msg(int level, char *fmt, ...)
{
  va_list ap;
  Ds ds;
  FILE *fp;
  extern Log_level current_log_level;

  if (level < current_log_level)
	return(0);

 if ((fp = log_stream) == NULL)
	fp = stderr;

  va_start(ap, fmt);
  ds_init(&ds);
  ds_asprintf(&ds, "([%u, %s] ", getpid(), log_level_to_name(level));
  ds_vasprintf(&ds, fmt, ap);
  va_end(ap);
  ds_asprintf(&ds, ")\n");

  fprintf(fp, "%s", ds_buf(&ds));
  fflush(fp);
  ds_free(&ds);

  return(0);
}

#endif

/*
 * Express the date and time in RFC 822 format, with the only variation
 * that a four-digit year can be selected (if YYYY is non-zero).
 * If USE_GMT is non-zero, use GMT time, otherwise use the local time and
 * include time zone information.
 */
char *
make_rfc822_date_string(struct tm *tm, int yyyy, int use_gmt)
{
  size_t st;
  char *buf;

  if (tm == NULL) {
	time_t now;

	time(&now);
	if (use_gmt)
	  tm = gmtime(&now);
	else
	  tm = localtime(&now);
  }

  buf = (char *) malloc(64);
  if (yyyy) {
	if (use_gmt)
	  st = strftime(buf, 64, "%a, %d %b %Y %H:%M:%S GMT", tm);
	else
	  st = strftime(buf, 64, "%a, %d %b %Y %H:%M:%S %z (%Z)", tm);
  }
  else {
	if (use_gmt)
	  st = strftime(buf, 64, "%a, %d %b %y %H:%M:%S GMT", tm);
	else
	  st = strftime(buf, 64, "%a, %d %b %Y %H:%M:%S %z (%Z)", tm);
  }

  if (st == 0 || st == 64) {
	/* XXX */
	/* ?? Return an expired date. */
	return("Mon, 1-Jan-2001 8:00:00 GMT");
  }

  return(buf);
}

/*
 * Express the date and time in UTC and using a 24 hour clock, in the
 * format "Wdy, DD-Mon-YYYY HH:MM:SS GMT", based on RFC 822, with the
 * variations that the only legal time zone is GMT and the separators between
 * the elements of the date must be dashes.
 * It is the same date format used by Netscape's HTTP cookie specification.
 */
char *
make_utc_date_string(struct tm *tm)
{
  size_t st;
  char *buf;

  if (tm == NULL) {
	time_t now;

	time(&now);
	tm = gmtime(&now);
  }

  buf = (char *) malloc(64);
  st = strftime(buf, 64, "%a, %d-%b-%Y %H:%M:%S GMT", tm);
  if (st == 0 || st == 64) {
	/* XXX */
	/* ?? Return an expired date. */
	return("Mon, 1-Jan-2001 8:00:00 GMT");
  }

  return(buf);
}

char *
make_local_date_string(struct tm *tm, int show_zone)
{
  size_t st;
  char *buf;

  if (tm == NULL) {
	time_t now;

	time(&now);
	tm = localtime(&now);
  }

  buf = (char *) malloc(64);
  if (show_zone)
	st = strftime(buf, 64, "%a %b %e %H:%M:%S %Y %Z", tm);
  else
	st = strftime(buf, 64, "%a %b %e %H:%M:%S %Y", tm);
  if (st == 0 || st == 64) {
	/* XXX */
	/* ?? Return an expired date. */
	return("Mon Jan 1 8:00:00 2001");
  }

  return(buf);
}

/*
 * Compatibility access function.
 * By now you wouldn't think this would be necessary...
 */
char *
tm_get_zone_name(struct tm *xtm)
{
  const char *abbrev_timezone_name;
  struct tm *tm;

  if (xtm == NULL) {
	time_t now;

	time(&now);
	tm = localtime(&now);
  }
  else
	tm = xtm;

#if defined(DACS_OS_SOLARIS) || defined(DACS_OS_CYGWIN)
  if (daylight)
	abbrev_timezone_name = tzname[1];
  else
	abbrev_timezone_name = tzname[0];
#else
  abbrev_timezone_name = tm->tm_zone;
#endif

  if (abbrev_timezone_name == NULL)
	return("");

  return(strdup(abbrev_timezone_name));
}

/*
 * Compatibility access function.
 * By now you wouldn't think this would be necessary...
 */
long
tm_get_gmtoff(struct tm *xtm)
{
  long offset_from_utc_in_secs;
  struct tm *tm;

  if (xtm == NULL) {
	time_t now;

	time(&now);
	tm = localtime(&now);
  }
  else
	tm = xtm;

#if defined(DACS_OS_SOLARIS)
  if (daylight)
	offset_from_utc_in_secs = -1 * altzone;
  else
	offset_from_utc_in_secs = -1 * timezone;
#elif defined(DACS_OS_CYGWIN)
  if (daylight)
	/* XXX is this always true? */
	offset_from_utc_in_secs = -1 * (timezone - (60 * 60));
  else
	offset_from_utc_in_secs = -1 * timezone;
#else
  offset_from_utc_in_secs = tm->tm_gmtoff;
#endif

  return(offset_from_utc_in_secs);
}

char *
make_short_local_date_string(struct tm *tm)
{
  size_t st;
  char *buf;

  if (tm == NULL) {
	time_t now;

	time(&now);
	tm = localtime(&now);
  }

  buf = (char *) malloc(64);
  st = strftime(buf, 64, "%e-%b-%Y@%H:%M:%S", tm);
  if (st == 0 || st == 64) {
	/* XXX */
	return(NULL);
  }

  if (buf[0] == ' ')
	return(buf + 1);

  return(buf);
}

/*
 * Make an XML Schema style dateTime string.
 */
char *
make_datetime(struct tm *tm)
{
  char *str;

  if (tm == NULL) {
	time_t now;

	time(&now);
	tm = gmtime(&now);
  }

  str = ds_xprintf("%d-%.2d-%.2dT%.2d:%.2d:%.2d%.2d%.2d",
				   tm->tm_year + 1900,
				   tm->tm_mon + 1,
				   tm->tm_mday,
				   tm->tm_hour, tm->tm_min, tm->tm_sec,
				   (int) tm_get_gmtoff(tm) / 3600,
				   (int) tm_get_gmtoff(tm) % 3600);

  return(str);
}

#define SECS_PER_MINUTE		60
#define SECS_PER_HOUR		(SECS_PER_MINUTE * 60)
#define SECS_PER_DAY		(SECS_PER_HOUR * 24)
#define SECS_PER_WEEK		(SECS_PER_DAY * 7)
#define SECS_PER_MONTH		(SECS_PER_WEEK * 4)
#define SECS_PER_YEAR		(SECS_PER_MONTH * 12)
#define NYEARS(SECS)		((SECS + (SECS_PER_YEAR / 2))   / SECS_PER_YEAR)
#define NMONTHS(SECS)		((SECS + (SECS_PER_MONTH / 2))  / SECS_PER_MONTH)
#define NWEEKS(SECS)		((SECS + (SECS_PER_WEEK / 2))   / SECS_PER_WEEK)
#define NDAYS(SECS)			((SECS + (SECS_PER_DAY / 2))    / SECS_PER_DAY)
#define NHOURS(SECS)		((SECS + (SECS_PER_HOUR / 2))   / SECS_PER_HOUR)
#define NMINUTES(SECS)		((SECS + (SECS_PER_MINUTE / 2)) / SECS_PER_MINUTE)

/*
 * Given a number of seconds from the current time, REL_SECS, generate a
 * string that describes approximately how far in the future REL_SECS is.
 */
char *
make_approx_relative_date(time_t rel_secs)
{
  unsigned int n;
  char *p;

  if ((n = NYEARS(rel_secs)) > 1)
	p = ds_xprintf("about %u years", n);
  else if ((n = NMONTHS(rel_secs)) > 1)
	p = ds_xprintf("about %u months", n);
  else if ((n = NWEEKS(rel_secs)) > 1)
	p = ds_xprintf("about %u weeks", n);
  else if ((n = NDAYS(rel_secs)) > 1)
	p = ds_xprintf("about %u days", n);
  else if ((n = NHOURS(rel_secs)) > 1)
	p = ds_xprintf("about %u hours", n);
  else if ((n = NMINUTES(rel_secs)) > 1)
	p = ds_xprintf("about %u minutes", n);
  else
	p = ds_xprintf("%u seconds", (unsigned int) rel_secs);

  return(p);
}

/*
 * Parse an XML Schema dateTime string, returning it as the local time
 * and (optionally) as the Unix time (SECS).
 * If ENDP is non-NULL, set it to point to the first invalid character in
 * STR if a valid dateTime is found; if ENDP is NULL, then the entirety of
 * STR is expected to be a valid dateTime.
 *
 * http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#dateTime
 * XML Schema dateTime syntax:
 *     '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
 *
 * The lexical representation of a timezone is a string of the form:
 *     (('+' | '-') hh ':' mm) | 'Z'
 * "The hour magnitude limited to at most 14, and the minute magnitude limited
 * to at most 59, except that if the hour magnitude is 14, the minute value
 * must be 0); they may be both positive or both negative"
 *
 * All timezoned times are Coordinated Universal Time (UTC, sometimes called
 * "Greenwich Mean Time"). Other timezones indicated in lexical representations
 * are converted to UTC during conversion of literals to values. "Local" or
 * untimezoned times are presumed to be the time in the timezone of some
 * unspecified locality as prescribed by the appropriate legal authority;
 * currently there are no legally prescribed timezones which are durations
 * whose magnitude is greater than 14 hours. The value of each numeric-valued
 * property (other than timeOnTimeline) is limited to the maximum value within
 * the interval determined by the next-higher property. For example, the day
 * value can never be 32, and cannot even be 29 for month 02 and year 2002
 * (February 2002). 
 */
struct tm *
parse_datetime(char *str, time_t *secs, char **endp)
{
  char *p, *stop;
  time_t now;
  struct tm *tm;
  static char *dateTime_format = "%Y-%m-%dT%H:%M:%S";
  static char *dateTime_format_tz = "%Y-%m-%dT%H:%M:%S%z";
  static char *dateTime_formatZ = "%Y-%m-%dT%H:%M:%SZ";

  if (*str == '-') {
	/* Valid in 1.0, but nonsensical here. */
	return(NULL);
  }

  tm = (struct tm *) malloc(sizeof(struct tm));
  time(&now);
  localtime_r(&now, tm);

  /* First, try a strptime(3) format that might work. */
  if ((p = strptime(str, dateTime_formatZ, tm)) == NULL) {
	int hours, minutes, sign;
	char *pre, *s;

	/*
	 * Nope, no luck...
	 * Attempt to recognize the date and time, stopping at either the
	 * start of fractional seconds, the start of a signed time zone offset,
	 * or the character following a valid dateTime.  Advance to the end of
	 * an ostensibly valid dateTime, piece together an equivalent date/time
	 * representation that *can* be recognized by strptime(3), then try to
	 * parse it again.  strptime() will reject invalid dates or times.
	 */
	if ((p = strptime(str, dateTime_format, tm)) == NULL)
	  return(NULL);

	stop = p;
	if (*p == '.') {
	  /*
	   * There are fractional seconds.
	   * XXX We could round them, but for now we'll just discard them.
	   */
	  do {
		p++;
	  } while (isdigit((int) *p));
	}

	/*
	 * If there is a time zone offset or UTC indicator we need to
	 * convert the original date into something that strptime(3) can
	 * handle.
	 * '+00:00', '-00:00', and 'Z' all represent the same zero-length duration
	 * timezone, UTC; 'Z' is its canonical representation.
	 */
	if (*p == 'Z' || *p == '+' || *p == '-') {
	  if (*p == 'Z') {
		sign = 0;
		p++;
	  }
	  else {
		/* There is a time zone offset. */
		sign = (*p == '+') ? 1 : -1;
		p++;

		if (!isdigit((int) *p))
		  return(NULL);
		hours = *p++ - '0';
		if (!isdigit((int) *p))
		  return(NULL);
		hours = (hours * 10) + (*p++ - '0');

		if (*p++ != ':')
		  return(NULL);
		minutes = *p++ - '0';
		if (!isdigit((int) *p))
		  return(NULL);
		minutes = (minutes * 10) + (*p++ - '0');
	  }

	  pre = strndup(str, stop - str);
	  if (sign == 0) {
		s = ds_xprintf("%sZ", pre);
		/* Try again, this time with a date string that should match. */
		if (strptime(s, dateTime_formatZ, tm) == NULL)
		  return(NULL);
	  }
	  else {
		s = ds_xprintf("%s%s%.2d%.2d",
					   pre, (sign < 0) ? "-" : "+", hours, minutes);
		/* Try again, this time with a date string that should match. */
		if (strptime(s, dateTime_format_tz, tm) == NULL)
		  return(NULL);
	  }
	}
  }

  if (endp != NULL)
	*endp = p;
  else if (*p != '\0')
	return(NULL);

  if (secs != NULL) {
#ifdef HAVE_TIMEGM
	*secs = timegm(tm);
#else
	/*
	 * mktime(3) does its conversion wrt the local time zone, not UTC.
	 * We work around this by temporarily asserting
	 * that we're in the UTC time zone then restoring the original time zone.
	 * As the time related functions keep some internal state, this is a
	 * little tricky to do, but not all platforms have timegm().
	 */
	char *tz;

	tz = getenv("TZ");
	setenv("TZ", "", 1);
	tzset();
	*secs = mktime(tm);
	if (tz != NULL)
	  setenv("TZ", tz, 1);
	else
	  unsetenv("TZ");
	tzset();
#endif
  }

  return(tm);
}

/*
 * Convert a short representation of the local time ("%e-%b-%Y@%H:%M:%S",
 * "%e-%b-%Y", or "%e-%b-%y) into an equivalent RFC 822 format.
 * XXX this resulting time zone is always GMT, not the local one.
 */
char *
short_local_date_string_to_rfc822(char *date_str)
{
  char *p;
  struct tm *tm;
  time_t now;

  time(&now);
  tm = localtime(&now);		/* Get time zone info. */
  tm->tm_sec = 0;
  tm->tm_min = 0;
  tm->tm_hour = 9;
  /*
  tm->tm_wday = -1;
  tm->tm_yday = -1;
  tm->tm_isdst = -1;
  */

  if ((p = strptime(date_str, "%e-%b-%Y@%H:%M:%S", tm)) == NULL) {
	if ((p = strptime(date_str, "%e-%b-%y@%H:%M:%S", tm)) == NULL) {
	  return(NULL);
	}
  }

  if (*p != '\0')
	return(NULL);

  return(make_rfc822_date_string(tm, 1, 0));
}

/*
 * Convert a UTC time string (one in GMT) into the equivalent number of
 * seconds since the beginning of the epoch.
 */
int
utc_date_string_to_secs(time_t *secs, char *date)
{
  int i;
  char *p;
  struct tm *tm;
  time_t now;
  static char *date_formats[] = {
	"%a, %d-%b-%Y %H:%M:%S GMT",
	"%d-%b-%Y",
	"%b %d, %Y",
	"%b %d",
	"%Y-%m-%dT%H:%M:%SZ",	/* The only possible RFC 3339 compatible spec */
	NULL
  };

  time(&now);
  for (i = 0; date_formats[i] != NULL; i++) {
	tm = localtime(&now);
	p = strptime(date, date_formats[i], tm);
	if (p != NULL && *p == '\0')
	  break;
  }

  if (date_formats[i] == NULL) {
	log_msg((LOG_ERROR_LEVEL, "strptime() failed?"));
	return(-1);
  }

#ifdef HAVE_TIMEGM
  *secs = timegm(tm);
#else
  /*
   * We need to convert the expires_date into seconds for comparison
   * against the current time.  But mktime() does the conversion wrt the
   * local time zone, not UTC.  We work around this by temporarily asserting
   * that we're in the UTC time zone then restoring the original time zone.
   * As the time related functions keep some internal state, this is a
   * little tricky to do, but not all platforms have timegm().
   */
  {
	char *tz;

	tz = getenv("TZ");
	setenv("TZ", "", 1);
	tzset();
	*secs = mktime(tm);
	if (tz != NULL)
	  setenv("TZ", tz, 1);
	else
	  unsetenv("TZ");
	tzset();

#ifdef NOTDEF
	if (this_tz != NULL && !streq(this_tz, "GMT")) {
	  if (putenv(strdup("TZ=GMT")) == -1) {
		log_msg((LOG_ERROR_LEVEL, "putenv() failed?"));
		return(-1);
	  }
	  tzset();
	}

	tm.tm_isdst = -1;
	t = mktime(&tm);

	if (this_tz != NULL && !streq(this_tz, "GMT")) {
	  char *b;

	  b = malloc(10 + strlen(this_tz));		/* XXX */
	  sprintf(b, "TZ=%s", this_tz);
	  if (putenv(b) == -1) {
		log_msg((LOG_ERROR_LEVEL, "putenv() failed? (2)"));
		free(b);
		return(-1);
	  }
	  tzset();
	}
	*secs = t;
#endif
  }
#endif

 return(0);
}

char *
escape_xml_attribute(char *str, int quote_char)
{
  int need_to_escape;
  char *p;
  Ds ds;
  static struct reserved {
	int ch;
	int is_quote;
	char *replacement;
  } reserved_chars[] = {
	{ '"',  1, "&quot;" },
	{ '\'', 1, "&apos;" },
	{ '<',  0, "&lt;" },
	{ '&',  0, "&amp;" },
	{ 0,    0, NULL }
  };
  struct reserved *r;

  need_to_escape = 0;
  for (p = str; *p != '\0'; p++) {
	for (r = reserved_chars; r->replacement != NULL; r++) {
	  if (*p == r->ch && (!r->is_quote || r->ch == quote_char)) {
		if (!need_to_escape) {
		  ds_init(&ds);
		  if (p != str)
			ds_copyb(&ds, str, p - str, 0);
		  need_to_escape = 1;
		}
		ds_append(&ds, r->replacement);
		break;
	  }
	}
	if (r->replacement == NULL && need_to_escape)
	  ds_appendc(&ds, (int) *p);
  }

  if (!need_to_escape)
	return(str);

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