/*
 * $Id: cache_cf.c,v 1.463.2.7 2008/06/27 21:53:17 hno Exp $
 *
 * DEBUG: section 3     Configuration File Parsing
 * AUTHOR: Harvest Derived
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include "squid.h"

#if SQUID_SNMP
#include "snmp.h"
#endif

static const char *const T_SECOND_STR = "second";
static const char *const T_MINUTE_STR = "minute";
static const char *const T_HOUR_STR = "hour";
static const char *const T_DAY_STR = "day";
static const char *const T_WEEK_STR = "week";
static const char *const T_FORTNIGHT_STR = "fortnight";
static const char *const T_MONTH_STR = "month";
static const char *const T_YEAR_STR = "year";
static const char *const T_DECADE_STR = "decade";

static const char *const B_BYTES_STR = "bytes";
static const char *const B_KBYTES_STR = "KB";
static const char *const B_MBYTES_STR = "MB";
static const char *const B_GBYTES_STR = "GB";

static const char *const list_sep = ", \t\n\r";

static void parse_cachedir_option_readonly(SwapDir * sd, const char *option, const char *value, int reconfiguring);
static void dump_cachedir_option_readonly(StoreEntry * e, const char *option, SwapDir * sd);
static void parse_cachedir_option_minsize(SwapDir * sd, const char *option, const char *value, int reconfiguring);
static void dump_cachedir_option_minsize(StoreEntry * e, const char *option, SwapDir * sd);
static void parse_cachedir_option_maxsize(SwapDir * sd, const char *option, const char *value, int reconfiguring);
static void dump_cachedir_option_maxsize(StoreEntry * e, const char *option, SwapDir * sd);
static void parse_logformat(logformat ** logformat_definitions);
static void parse_access_log(customlog ** customlog_definitions);
static void dump_logformat(StoreEntry * entry, const char *name, logformat * definitions);
static void dump_access_log(StoreEntry * entry, const char *name, customlog * definitions);
static void free_logformat(logformat ** definitions);
static void free_access_log(customlog ** definitions);


static struct cache_dir_option common_cachedir_options[] =
{
    {"read-only", parse_cachedir_option_readonly, dump_cachedir_option_readonly},
    {"min-size", parse_cachedir_option_minsize, dump_cachedir_option_minsize},
    {"max-size", parse_cachedir_option_maxsize, dump_cachedir_option_maxsize},
    {NULL, NULL}
};


static void update_maxobjsize(void);
static void configDoConfigure(void);
static void parse_refreshpattern(refresh_t **);
static int parseTimeUnits(const char *unit);
static void parseTimeLine(time_t * tptr, const char *units);
static void parse_ushort(u_short * var);
static void parse_string(char **);
static void default_all(void);
static void defaults_if_none(void);
static int parse_line(char *);
static void parseBytesLine(squid_off_t * bptr, const char *units);
static size_t parseBytesUnits(const char *unit);
static void free_all(void);
void requirePathnameExists(const char *name, const char *path);
static OBJH dump_config;
#ifdef HTTP_VIOLATIONS
static void dump_http_header_access(StoreEntry * entry, const char *name, header_mangler header[]);
static void parse_http_header_access(header_mangler header[]);
static void free_http_header_access(header_mangler header[]);
static void dump_http_header_replace(StoreEntry * entry, const char *name, header_mangler header[]);
static void parse_http_header_replace(header_mangler * header);
static void free_http_header_replace(header_mangler * header);
#endif
static void parse_denyinfo(acl_deny_info_list ** var);
static void dump_denyinfo(StoreEntry * entry, const char *name, acl_deny_info_list * var);
static void free_denyinfo(acl_deny_info_list ** var);
#if USE_WCCPv2
static void parse_sockaddr_in_list(sockaddr_in_list **);
static void dump_sockaddr_in_list(StoreEntry *, const char *, const sockaddr_in_list *);
static void free_sockaddr_in_list(sockaddr_in_list **);
#if UNUSED_CODE
static int check_null_sockaddr_in_list(const sockaddr_in_list *);
#endif
#endif
static void parse_http_port_list(http_port_list **);
static void dump_http_port_list(StoreEntry *, const char *, const http_port_list *);
static void free_http_port_list(http_port_list **);
#if 0
static int check_null_http_port_list(const http_port_list *);
#endif
#if USE_SSL
static void parse_https_port_list(https_port_list **);
static void dump_https_port_list(StoreEntry *, const char *, const https_port_list *);
static void free_https_port_list(https_port_list **);
#if 0
static int check_null_https_port_list(const https_port_list *);
#endif
#endif /* USE_SSL */
static void parse_programline(wordlist **);
static void free_programline(wordlist **);
static void dump_programline(StoreEntry *, const char *, const wordlist *);

void
self_destruct(void)
{
    shutting_down = 1;
    fatalf("Bungled %s line %d: %s",
	cfg_filename, config_lineno, config_input_line);
}

void
wordlistDestroy(wordlist ** list)
{
    wordlist *w = NULL;
    while ((w = *list) != NULL) {
	*list = w->next;
	safe_free(w->key);
	memFree(w, MEM_WORDLIST);
    }
    *list = NULL;
}

const char *
wordlistAdd(wordlist ** list, const char *key)
{
    while (*list)
	list = &(*list)->next;
    *list = memAllocate(MEM_WORDLIST);
    (*list)->key = xstrdup(key);
    (*list)->next = NULL;
    return (*list)->key;
}

void
wordlistJoin(wordlist ** list, wordlist ** wl)
{
    while (*list)
	list = &(*list)->next;
    *list = *wl;
    *wl = NULL;
}

void
wordlistAddWl(wordlist ** list, wordlist * wl)
{
    while (*list)
	list = &(*list)->next;
    for (; wl; wl = wl->next, list = &(*list)->next) {
	*list = memAllocate(MEM_WORDLIST);
	(*list)->key = xstrdup(wl->key);
	(*list)->next = NULL;
    }
}

void
wordlistCat(const wordlist * w, MemBuf * mb)
{
    while (NULL != w) {
	memBufPrintf(mb, "%s\n", w->key);
	w = w->next;
    }
}

wordlist *
wordlistDup(const wordlist * w)
{
    wordlist *D = NULL;
    while (NULL != w) {
	wordlistAdd(&D, w->key);
	w = w->next;
    }
    return D;
}

void
intlistDestroy(intlist ** list)
{
    intlist *w = NULL;
    intlist *n = NULL;
    for (w = *list; w; w = n) {
	n = w->next;
	memFree(w, MEM_INTLIST);
    }
    *list = NULL;
}

int
intlistFind(intlist * list, int i)
{
    intlist *w = NULL;
    for (w = list; w; w = w->next)
	if (w->i == i)
	    return 1;
    return 0;
}

/*
 * These functions is the same as atoi/l/f, except that they check for errors
 */

static long
xatol(const char *token)
{
    char *end;
    long ret = strtol(token, &end, 10);
    if (end == token || *end)
	self_destruct();
    return ret;
}

int
xatoi(const char *token)
{
    return xatol(token);
}

unsigned short
xatos(const char *token)
{
    long port = xatol(token);
    if (port & ~0xFFFF)
	self_destruct();
    return port;
}

static double
xatof(const char *token)
{
    char *end;
    double ret = strtod(token, &end);
    if (ret == 0 && end == token)
	self_destruct();
    return ret;
}

int
GetInteger(void)
{
    char *token = strtok(NULL, w_space);
    char *end;
    int i;
    double d;
    if (token == NULL)
	self_destruct();
    i = strtol(token, &end, 0);
    d = strtod(token, NULL);
    if (d > INT_MAX || end == token)
	self_destruct();
    return i;
}

static u_short
GetShort(void)
{
    char *token = strtok(NULL, w_space);
    if (token == NULL)
	self_destruct();
    return xatos(token);
}

static squid_off_t
GetOffT(void)
{
    char *token = strtok(NULL, w_space);
    char *end;
    squid_off_t i;
    if (token == NULL)
	self_destruct();
    i = strto_off_t(token, &end, 0);
#if SIZEOF_SQUID_OFF_T <= 4
    {
	double d = strtod(token, NULL);
	if (d > INT_MAX)
	    end = token;
    }
#endif
    if (end == token)
	self_destruct();
    return i;
}

static void
update_maxobjsize(void)
{
    int i;
    squid_off_t ms = -1;

    for (i = 0; i < Config.cacheSwap.n_configured; i++) {
	if (Config.cacheSwap.swapDirs[i].max_objsize > ms)
	    ms = Config.cacheSwap.swapDirs[i].max_objsize;
    }
    store_maxobjsize = ms;
}

int
parseConfigFile(const char *file_name)
{
    FILE *fp = NULL;
    char *token = NULL;
    char *tmp_line = NULL;
    int tmp_line_len = 0;
    size_t config_input_line_len;
    int err_count = 0;
    configFreeMemory();
    default_all();
    if ((fp = fopen(file_name, "r")) == NULL)
	fatalf("Unable to open configuration file: %s: %s",
	    file_name, xstrerror());
#ifdef _SQUID_WIN32_
    setmode(fileno(fp), O_TEXT);
#endif
    cfg_filename = file_name;
    if ((token = strrchr(cfg_filename, '/')))
	cfg_filename = token + 1;
    memset(config_input_line, '\0', BUFSIZ);
    config_lineno = 0;
    while (fgets(config_input_line, BUFSIZ, fp)) {
	config_lineno++;
	if ((token = strchr(config_input_line, '\n')))
	    *token = '\0';
	if ((token = strchr(config_input_line, '\r')))
	    *token = '\0';
	if (config_input_line[0] == '#')
	    continue;
	if (config_input_line[0] == '\0')
	    continue;

	config_input_line_len = strlen(config_input_line);
	tmp_line = (char *) xrealloc(tmp_line, tmp_line_len + config_input_line_len + 1);
	strcpy(tmp_line + tmp_line_len, config_input_line);
	tmp_line_len += config_input_line_len;

	if (tmp_line[tmp_line_len - 1] == '\\') {
	    debug(3, 5) ("parseConfigFile: tmp_line='%s'\n", tmp_line);
	    tmp_line[--tmp_line_len] = '\0';
	    continue;
	}
	debug(3, 5) ("Processing: '%s'\n", tmp_line);
	if (!parse_line(tmp_line)) {
	    debug(3, 0) ("parseConfigFile: line %d unrecognized: '%s'\n",
		config_lineno,
		config_input_line);
	    err_count++;
	}
	safe_free(tmp_line);
	tmp_line_len = 0;
    }
    fclose(fp);
    defaults_if_none();
    configDoConfigure();
    if (opt_send_signal == -1) {
	cachemgrRegister("config",
	    "Current Squid Configuration",
	    dump_config,
	    1, 1);
    }
    return err_count;
}

static void
configDoConfigure(void)
{
    memset(&Config2, '\0', sizeof(SquidConfig2));
    /* init memory as early as possible */
    memConfigure();
    /* Sanity checks */
    if (Config.cacheSwap.swapDirs == NULL)
	fatal("No cache_dir's specified in config file");
    /* calculate Config.Swap.maxSize */
    storeDirConfigure();
    if (0 == Config.Swap.maxSize)
	/* people might want a zero-sized cache on purpose */
	(void) 0;
    else if (Config.Swap.maxSize < (Config.memMaxSize >> 10))
	debug(3, 0) ("WARNING cache_mem is larger than total disk cache space!\n");
    if (Config.Announce.period > 0) {
	Config.onoff.announce = 1;
    } else if (Config.Announce.period < 1) {
	Config.Announce.period = 86400 * 365;	/* one year */
	Config.onoff.announce = 0;
    }
    if (Config.onoff.httpd_suppress_version_string)
	visible_appname_string = (char *) appname_string;
    else
	visible_appname_string = (char *) full_appname_string;
#if USE_DNSSERVERS
    if (Config.dnsChildren < 1)
	fatal("No dnsservers allocated");
#endif
    if (Config.Program.url_rewrite.command) {
	if (Config.Program.url_rewrite.children < 1) {
	    Config.Program.url_rewrite.children = 0;
	    wordlistDestroy(&Config.Program.url_rewrite.command);
	}
    }
    if (Config.Program.location_rewrite.command) {
	if (Config.Program.location_rewrite.children < 1) {
	    Config.Program.location_rewrite.children = 0;
	    wordlistDestroy(&Config.Program.location_rewrite.command);
	}
    }
    if (Config.appendDomain)
	if (*Config.appendDomain != '.')
	    fatal("append_domain must begin with a '.'");
    if (Config.errHtmlText == NULL)
	Config.errHtmlText = xstrdup(null_string);
    storeConfigure();
    snprintf(ThisCache, sizeof(ThisCache), "%s:%d (%s)",
	uniqueHostname(),
	getMyPort(),
	visible_appname_string);
    /*
     * the extra space is for loop detection in client_side.c -- we search
     * for substrings in the Via header.
     */
    snprintf(ThisCache2, sizeof(ThisCache), " %s:%d (%s)",
	uniqueHostname(),
	getMyPort(),
	visible_appname_string);
    if (!Config.udpMaxHitObjsz || Config.udpMaxHitObjsz > SQUID_UDP_SO_SNDBUF)
	Config.udpMaxHitObjsz = SQUID_UDP_SO_SNDBUF;
    if (Config.appendDomain)
	Config.appendDomainLen = strlen(Config.appendDomain);
    else
	Config.appendDomainLen = 0;
    safe_free(debug_options)
	debug_options = xstrdup(Config.debugOptions);
    if (Config.retry.maxtries > 10)
	fatal("maximum_single_addr_tries cannot be larger than 10");
    if (Config.retry.maxtries < 1) {
	debug(3, 0) ("WARNING: resetting 'maximum_single_addr_tries to 1\n");
	Config.retry.maxtries = 1;
    }
    requirePathnameExists("MIME Config Table", Config.mimeTablePathname);
#if USE_DNSSERVERS
    requirePathnameExists("cache_dns_program", Config.Program.dnsserver);
#endif
#if USE_UNLINKD
    requirePathnameExists("unlinkd_program", Config.Program.unlinkd);
#endif
    if (Config.Program.url_rewrite.command)
	requirePathnameExists("url_rewrite_program", Config.Program.url_rewrite.command->key);
    if (Config.Program.location_rewrite.command)
	requirePathnameExists("location_rewrite_program", Config.Program.location_rewrite.command->key);
    requirePathnameExists("Icon Directory", Config.icons.directory);
    requirePathnameExists("Error Directory", Config.errorDirectory);
    authenticateConfigure(&Config.authConfig);
    externalAclConfigure();
#if HTTP_VIOLATIONS
    {
	const refresh_t *R;
	for (R = Config.Refresh; R; R = R->next) {
	    if (!R->flags.override_expire)
		continue;
	    debug(22, 1) ("WARNING: use of 'override-expire' in 'refresh_pattern' violates HTTP\n");
	    break;
	}
	for (R = Config.Refresh; R; R = R->next) {
	    if (!R->flags.override_lastmod)
		continue;
	    debug(22, 1) ("WARNING: use of 'override-lastmod' in 'refresh_pattern' violates HTTP\n");
	    break;
	}
    }
#endif
#if !HTTP_VIOLATIONS
    Config.onoff.via = 1;
#else
    if (!Config.onoff.via)
	debug(22, 1) ("WARNING: HTTP requires the use of Via\n");
#endif
    if (Config.Wais.relayHost) {
	if (Config.Wais.peer)
	    cbdataFree(Config.Wais.peer);
	Config.Wais.peer = cbdataAlloc(peer);
	Config.Wais.peer->host = xstrdup(Config.Wais.relayHost);
	Config.Wais.peer->http_port = Config.Wais.relayPort;
    }
    if (aclPurgeMethodInUse(Config.accessList.http))
	Config2.onoff.enable_purge = 1;
    if (geteuid() == 0) {
	if (NULL != Config.effectiveUser) {
	    struct passwd *pwd = getpwnam(Config.effectiveUser);
	    if (NULL == pwd)
		/*
		 * Andres Kroonmaa <andre@online.ee>:
		 * Some getpwnam() implementations (Solaris?) require
		 * an available FD < 256 for opening a FILE* to the
		 * passwd file.
		 * DW:
		 * This should be safe at startup, but might still fail
		 * during reconfigure.
		 */
		fatalf("getpwnam failed to find userid for effective user '%s'",
		    Config.effectiveUser);
	    Config2.effectiveUserID = pwd->pw_uid;
	    Config2.effectiveGroupID = pwd->pw_gid;
#if HAVE_PUTENV
	    if (pwd->pw_dir && *pwd->pw_dir) {
		int len;
		char *env_str = xcalloc((len = strlen(pwd->pw_dir) + 6), 1);
		snprintf(env_str, len, "HOME=%s", pwd->pw_dir);
		putenv(env_str);
	    }
#endif
	}
    } else {
	Config2.effectiveUserID = geteuid();
	Config2.effectiveGroupID = getegid();
    }
    if (NULL != Config.effectiveGroup) {
	struct group *grp = getgrnam(Config.effectiveGroup);
	if (NULL == grp)
	    fatalf("getgrnam failed to find groupid for effective group '%s'",
		Config.effectiveGroup);
	Config2.effectiveGroupID = grp->gr_gid;
    }
    if (0 == Config.onoff.client_db) {
	acl *a;
	for (a = Config.aclList; a; a = a->next) {
	    if (ACL_MAXCONN != a->type)
		continue;
	    debug(22, 0) ("WARNING: 'maxconn' ACL (%s) won't work with client_db disabled\n", a->name);
	}
    }
    if (Config.negativeDnsTtl <= 0) {
	debug(22, 0) ("WARNING: resetting negative_dns_ttl to 1 second\n");
	Config.negativeDnsTtl = 1;
    }
    if (Config.positiveDnsTtl < Config.negativeDnsTtl) {
	debug(22, 0) ("NOTICE: positive_dns_ttl must be larger than negative_dns_ttl. Resetting negative_dns_ttl to match\n");
	Config.positiveDnsTtl = Config.negativeDnsTtl;
    }
#if SIZEOF_SQUID_FILE_SZ <= 4
#if SIZEOF_SQUID_OFF_T <= 4
    if (Config.Store.maxObjectSize > 0x7FFF0000) {
	debug(22, 0) ("NOTICE: maximum_object_size limited to %d KB due to hardware limitations\n", 0x7FFF0000 / 1024);
	Config.Store.maxObjectSize = 0x7FFF0000;
    }
#elif SIZEOF_OFF_T <= 4
    if (Config.Store.maxObjectSize > 0xFFFF0000) {
	debug(22, 0) ("NOTICE: maximum_object_size limited to %d KB due to OS limitations\n", 0xFFFF0000 / 1024);
	Config.Store.maxObjectSize = 0xFFFF0000;
    }
#else
    if (Config.Store.maxObjectSize > 0xFFFF0000) {
	debug(22, 0) ("NOTICE: maximum_object_size limited to %d KB to keep compatibility with existing cache\n", 0xFFFF0000 / 1024);
	Config.Store.maxObjectSize = 0xFFFF0000;
    }
#endif
#endif
    if (Config.Store.maxInMemObjSize > 8 * 1024 * 1024)
	debug(22, 0) ("WARNING: Very large maximum_object_size_in_memory settings can have negative impact on performance\n");
#if USE_SSL
    Config.ssl_client.sslContext = sslCreateClientContext(Config.ssl_client.cert, Config.ssl_client.key, Config.ssl_client.version, Config.ssl_client.cipher, Config.ssl_client.options, Config.ssl_client.flags, Config.ssl_client.cafile, Config.ssl_client.capath, Config.ssl_client.crlfile);
#endif
}

/* Parse a time specification from the config file.  Store the
 * result in 'tptr', after converting it to 'units' */
static void
parseTimeLine(time_t * tptr, const char *units)
{
    char *token;
    double d;
    time_t m;
    time_t u;
    if ((u = parseTimeUnits(units)) == 0)
	self_destruct();
    if ((token = strtok(NULL, w_space)) == NULL)
	self_destruct();
    d = xatof(token);
    m = u;			/* default to 'units' if none specified */
    if (0 == d)
	(void) 0;
    else if ((token = strtok(NULL, w_space)) == NULL)
	debug(3, 0) ("WARNING: No units on '%s', assuming %f %s\n",
	    config_input_line, d, units);
    else if ((m = parseTimeUnits(token)) == 0)
	self_destruct();
    *tptr = m * d / u;
}

static int
parseTimeUnits(const char *unit)
{
    if (!strncasecmp(unit, T_SECOND_STR, strlen(T_SECOND_STR)))
	return 1;
    if (!strncasecmp(unit, T_MINUTE_STR, strlen(T_MINUTE_STR)))
	return 60;
    if (!strncasecmp(unit, T_HOUR_STR, strlen(T_HOUR_STR)))
	return 3600;
    if (!strncasecmp(unit, T_DAY_STR, strlen(T_DAY_STR)))
	return 86400;
    if (!strncasecmp(unit, T_WEEK_STR, strlen(T_WEEK_STR)))
	return 86400 * 7;
    if (!strncasecmp(unit, T_FORTNIGHT_STR, strlen(T_FORTNIGHT_STR)))
	return 86400 * 14;
    if (!strncasecmp(unit, T_MONTH_STR, strlen(T_MONTH_STR)))
	return 86400 * 30;
    if (!strncasecmp(unit, T_YEAR_STR, strlen(T_YEAR_STR)))
	return 86400 * 365.2522;
    if (!strncasecmp(unit, T_DECADE_STR, strlen(T_DECADE_STR)))
	return 86400 * 365.2522 * 10;
    debug(3, 1) ("parseTimeUnits: unknown time unit '%s'\n", unit);
    return 0;
}

static void
parseBytesLine(squid_off_t * bptr, const char *units)
{
    char *token;
    double d;
    squid_off_t m;
    squid_off_t u;
    if ((u = parseBytesUnits(units)) == 0)
	self_destruct();
    if ((token = strtok(NULL, w_space)) == NULL)
	self_destruct();
    if (strcmp(token, "none") == 0 || strcmp(token, "-1") == 0) {
	*bptr = (squid_off_t) - 1;
	return;
    }
    d = xatof(token);
    m = u;			/* default to 'units' if none specified */
    if (0.0 == d)
	(void) 0;
    else if ((token = strtok(NULL, w_space)) == NULL)
	debug(3, 0) ("WARNING: No units on '%s', assuming %f %s\n",
	    config_input_line, d, units);
    else if ((m = parseBytesUnits(token)) == 0)
	self_destruct();
    *bptr = m * d / u;
    if ((double) *bptr * 2 != m * d / u * 2)
	self_destruct();
}

static size_t
parseBytesUnits(const char *unit)
{
    if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)))
	return 1;
    if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
	return 1 << 10;
    if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
	return 1 << 20;
    if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
	return 1 << 30;
    debug(3, 1) ("parseBytesUnits: unknown bytes unit '%s'\n", unit);
    return 0;
}

/*****************************************************************************
 * Max
 *****************************************************************************/

static void
dump_acl(StoreEntry * entry, const char *name, acl * ae)
{
    while (ae != NULL) {
	debug(3, 3) ("dump_acl: %s %s\n", name, ae->name);
	if (strstr(ae->cfgline, " \""))
	    storeAppendPrintf(entry, "%s\n", ae->cfgline);
	else {
	    wordlist *w;
	    wordlist *v;
	    v = w = aclDumpGeneric(ae);
	    while (v != NULL) {
		debug(3, 3) ("dump_acl: %s %s %s\n", name, ae->name, v->key);
		storeAppendPrintf(entry, "%s %s %s %s\n",
		    name,
		    ae->name,
		    aclTypeToStr(ae->type),
		    v->key);
		v = v->next;
	    }
	    wordlistDestroy(&w);
	}
	ae = ae->next;
    }
}

static void
parse_acl(acl ** ae)
{
    aclParseAclLine(ae);
}

static void
free_acl(acl ** ae)
{
    aclDestroyAcls(ae);
}

static void
dump_acl_list(StoreEntry * entry, acl_list * head)
{
    acl_list *l;
    for (l = head; l; l = l->next) {
	storeAppendPrintf(entry, " %s%s",
	    l->op ? null_string : "!",
	    l->acl->name);
    }
}

static void
dump_acl_access(StoreEntry * entry, const char *name, acl_access * head)
{
    acl_access *l;
    for (l = head; l; l = l->next) {
	storeAppendPrintf(entry, "%s %s",
	    name,
	    l->allow ? "Allow" : "Deny");
	dump_acl_list(entry, l->acl_list);
	storeAppendPrintf(entry, "\n");
    }
}

static void
parse_acl_access(acl_access ** head)
{
    aclParseAccessLine(head);
}

static void
free_acl_access(acl_access ** head)
{
    aclDestroyAccessList(head);
}

static void
dump_address(StoreEntry * entry, const char *name, struct in_addr addr)
{
    storeAppendPrintf(entry, "%s %s\n", name, inet_ntoa(addr));
}

static void
parse_address(struct in_addr *addr)
{
    const struct hostent *hp;
    char *token = strtok(NULL, w_space);

    if (token == NULL)
	self_destruct();
    if (safe_inet_addr(token, addr) == 1)
	(void) 0;
    else if ((hp = gethostbyname(token)))	/* dont use ipcache */
	*addr = inaddrFromHostent(hp);
    else
	self_destruct();
}

static void
free_address(struct in_addr *addr)
{
    memset(addr, '\0', sizeof(struct in_addr));
}

CBDATA_TYPE(acl_address);

static void
dump_acl_address(StoreEntry * entry, const char *name, acl_address * head)
{
    acl_address *l;
    for (l = head; l; l = l->next) {
	if (l->addr.s_addr != INADDR_ANY)
	    storeAppendPrintf(entry, "%s %s", name, inet_ntoa(l->addr));
	else
	    storeAppendPrintf(entry, "%s autoselect", name);
	dump_acl_list(entry, l->acl_list);
	storeAppendPrintf(entry, "\n");
    }
}

static void
freed_acl_address(void *data)
{
    acl_address *l = data;
    aclDestroyAclList(&l->acl_list);
}

static void
parse_acl_address(acl_address ** head)
{
    acl_address *l;
    acl_address **tail = head;	/* sane name below */
    CBDATA_INIT_TYPE_FREECB(acl_address, freed_acl_address);
    l = cbdataAlloc(acl_address);
    parse_address(&l->addr);
    aclParseAclList(&l->acl_list);
    while (*tail)
	tail = &(*tail)->next;
    *tail = l;
}

static void
free_acl_address(acl_address ** head)
{
    while (*head) {
	acl_address *l = *head;
	*head = l->next;
	cbdataFree(l);
    }
}

CBDATA_TYPE(acl_tos);

static void
dump_acl_tos(StoreEntry * entry, const char *name, acl_tos * head)
{
    acl_tos *l;
    for (l = head; l; l = l->next) {
	if (l->tos > 0)
	    storeAppendPrintf(entry, "%s 0x%02X", name, l->tos);
	else
	    storeAppendPrintf(entry, "%s none", name);
	dump_acl_list(entry, l->acl_list);
	storeAppendPrintf(entry, "\n");
    }
}

static void
freed_acl_tos(void *data)
{
    acl_tos *l = data;
    aclDestroyAclList(&l->acl_list);
}

static void
parse_acl_tos(acl_tos ** head)
{
    acl_tos *l;
    acl_tos **tail = head;	/* sane name below */
    int tos;
    char junk;
    char *token = strtok(NULL, w_space);
    if (!token)
	self_destruct();
    if (sscanf(token, "0x%x%c", &tos, &junk) != 1)
	self_destruct();
    if (tos < 0 || tos > 255)
	self_destruct();
    CBDATA_INIT_TYPE_FREECB(acl_tos, freed_acl_tos);
    l = cbdataAlloc(acl_tos);
    l->tos = tos;
    aclParseAclList(&l->acl_list);
    while (*tail)
	tail = &(*tail)->next;
    *tail = l;
}

static void
free_acl_tos(acl_tos ** head)
{
    while (*head) {
	acl_tos *l = *head;
	*head = l->next;
	l->next = NULL;
	cbdataFree(l);
    }
}

#if DELAY_POOLS

/* do nothing - free_delay_pool_count is the magic free function.
 * this is why delay_pool_count isn't just marked TYPE: ushort
 */
#define free_delay_pool_class(X)
#define free_delay_pool_access(X)
#define free_delay_pool_rates(X)
#define dump_delay_pool_class(X, Y, Z)
#define dump_delay_pool_access(X, Y, Z)
#define dump_delay_pool_rates(X, Y, Z)

static void
free_delay_pool_count(delayConfig * cfg)
{
    int i;

    if (!cfg->pools)
	return;
    for (i = 0; i < cfg->pools; i++) {
	if (cfg->class[i]) {
	    delayFreeDelayPool(i);
	    safe_free(cfg->rates[i]);
	}
	aclDestroyAccessList(&cfg->access[i]);
    }
    delayFreeDelayData(cfg->pools);
    xfree(cfg->class);
    xfree(cfg->rates);
    xfree(cfg->access);
    memset(cfg, 0, sizeof(*cfg));
}

static void
dump_delay_pool_count(StoreEntry * entry, const char *name, delayConfig cfg)
{
    int i;
    LOCAL_ARRAY(char, nom, 32);

    if (!cfg.pools) {
	storeAppendPrintf(entry, "%s 0\n", name);
	return;
    }
    storeAppendPrintf(entry, "%s %d\n", name, cfg.pools);
    for (i = 0; i < cfg.pools; i++) {
	storeAppendPrintf(entry, "delay_class %d %d\n", i + 1, cfg.class[i]);
	snprintf(nom, 32, "delay_access %d", i + 1);
	dump_acl_access(entry, nom, cfg.access[i]);
	if (cfg.class[i] >= 1)
	    storeAppendPrintf(entry, "delay_parameters %d %d/%d", i + 1,
		cfg.rates[i]->aggregate.restore_bps,
		cfg.rates[i]->aggregate.max_bytes);
	if (cfg.class[i] >= 3)
	    storeAppendPrintf(entry, " %d/%d",
		cfg.rates[i]->network.restore_bps,
		cfg.rates[i]->network.max_bytes);
	if (cfg.class[i] >= 2)
	    storeAppendPrintf(entry, " %d/%d",
		cfg.rates[i]->individual.restore_bps,
		cfg.rates[i]->individual.max_bytes);
	if (cfg.class[i] >= 1)
	    storeAppendPrintf(entry, "\n");
    }
}

static void
parse_delay_pool_count(delayConfig * cfg)
{
    if (cfg->pools) {
	debug(3, 0) ("parse_delay_pool_count: multiple delay_pools lines, aborting all previous delay_pools config\n");
	free_delay_pool_count(cfg);
    }
    parse_ushort(&cfg->pools);
    if (cfg->pools) {
	delayInitDelayData(cfg->pools);
	cfg->class = xcalloc(cfg->pools, sizeof(u_char));
	cfg->rates = xcalloc(cfg->pools, sizeof(delaySpecSet *));
	cfg->access = xcalloc(cfg->pools, sizeof(acl_access *));
    }
}

static void
parse_delay_pool_class(delayConfig * cfg)
{
    ushort pool, class;

    parse_ushort(&pool);
    if (pool < 1 || pool > cfg->pools) {
	debug(3, 0) ("parse_delay_pool_class: Ignoring pool %d not in 1 .. %d\n", pool, cfg->pools);
	return;
    }
    parse_ushort(&class);
    if (class < 1 || class > 3) {
	debug(3, 0) ("parse_delay_pool_class: Ignoring pool %d class %d not in 1 .. 3\n", pool, class);
	return;
    }
    pool--;
    if (cfg->class[pool]) {
	delayFreeDelayPool(pool);
	safe_free(cfg->rates[pool]);
    }
    /* Allocates a "delaySpecSet" just as large as needed for the class */
    cfg->rates[pool] = xmalloc(class * sizeof(delaySpec));
    cfg->class[pool] = class;
    cfg->rates[pool]->aggregate.restore_bps = cfg->rates[pool]->aggregate.max_bytes = -1;
    if (cfg->class[pool] >= 3)
	cfg->rates[pool]->network.restore_bps = cfg->rates[pool]->network.max_bytes = -1;
    if (cfg->class[pool] >= 2)
	cfg->rates[pool]->individual.restore_bps = cfg->rates[pool]->individual.max_bytes = -1;
    delayCreateDelayPool(pool, class);
}

static void
parse_delay_pool_rates(delayConfig * cfg)
{
    ushort pool, class;
    int i;
    delaySpec *ptr;
    char *token;

    parse_ushort(&pool);
    if (pool < 1 || pool > cfg->pools) {
	debug(3, 0) ("parse_delay_pool_rates: Ignoring pool %d not in 1 .. %d\n", pool, cfg->pools);
	return;
    }
    pool--;
    class = cfg->class[pool];
    if (class == 0) {
	debug(3, 0) ("parse_delay_pool_rates: Ignoring pool %d attempt to set rates with class not set\n", pool + 1);
	return;
    }
    ptr = (delaySpec *) cfg->rates[pool];
    /* read in "class" sets of restore,max pairs */
    while (class--) {
	token = strtok(NULL, "/");
	if (token == NULL)
	    self_destruct();
	if (sscanf(token, "%d", &i) != 1)
	    self_destruct();
	ptr->restore_bps = i;
	i = GetInteger();
	ptr->max_bytes = i;
	ptr++;
    }
    class = cfg->class[pool];
    /* if class is 3, swap around network and individual */
    if (class == 3) {
	delaySpec tmp;

	tmp = cfg->rates[pool]->individual;
	cfg->rates[pool]->individual = cfg->rates[pool]->network;
	cfg->rates[pool]->network = tmp;
    }
    /* initialize the delay pools */
    delayInitDelayPool(pool, class, cfg->rates[pool]);
}

static void
parse_delay_pool_access(delayConfig * cfg)
{
    ushort pool;

    parse_ushort(&pool);
    if (pool < 1 || pool > cfg->pools) {
	debug(3, 0) ("parse_delay_pool_rates: Ignoring pool %d not in 1 .. %d\n", pool, cfg->pools);
	return;
    }
    aclParseAccessLine(&cfg->access[pool - 1]);
}
#endif

#ifdef HTTP_VIOLATIONS
static void
dump_http_header_access(StoreEntry * entry, const char *name, header_mangler header[])
{
    int i;
    header_mangler *other;
    for (i = 0; i < HDR_ENUM_END; i++) {
	if (header[i].access_list == NULL)
	    continue;
	storeAppendPrintf(entry, "%s ", name);
	dump_acl_access(entry, httpHeaderNameById(i),
	    header[i].access_list);
    }
    for (other = header[HDR_OTHER].next; other; other = other->next) {
	if (other->access_list == NULL)
	    continue;
	storeAppendPrintf(entry, "%s ", name);
	dump_acl_access(entry, other->name,
	    other->access_list);
    }
}

static void
parse_http_header_access(header_mangler header[])
{
    int id, i;
    char *t = NULL;
    if ((t = strtok(NULL, w_space)) == NULL) {
	debug(3, 0) ("%s line %d: %s\n",
	    cfg_filename, config_lineno, config_input_line);
	debug(3, 0) ("parse_http_header_access: missing header name.\n");
	return;
    }
    /* Now lookup index of header. */
    id = httpHeaderIdByNameDef(t, strlen(t));
    if (strcmp(t, "All") == 0)
	id = HDR_ENUM_END;
    else if (strcmp(t, "Other") == 0)
	id = HDR_OTHER;
    else if (id == -1) {
	header_mangler *hdr = header[HDR_OTHER].next;
	while (hdr && strcasecmp(hdr->name, t) != 0)
	    hdr = hdr->next;
	if (!hdr) {
	    hdr = xcalloc(1, sizeof *hdr);
	    hdr->name = xstrdup(t);
	    hdr->next = header[HDR_OTHER].next;
	    header[HDR_OTHER].next = hdr;
	}
	parse_acl_access(&hdr->access_list);
	return;
    }
    if (id != HDR_ENUM_END) {
	parse_acl_access(&header[id].access_list);
    } else {
	char *next_string = t + strlen(t) - 1;
	*next_string = 'A';
	*(next_string + 1) = ' ';
	for (i = 0; i < HDR_ENUM_END; i++) {
	    char *new_string = xstrdup(next_string);
	    strtok(new_string, w_space);
	    parse_acl_access(&header[i].access_list);
	    safe_free(new_string);
	}
    }
}

static void
free_http_header_access(header_mangler header[])
{
    int i;
    header_mangler **hdrp;
    for (i = 0; i < HDR_ENUM_END; i++) {
	free_acl_access(&header[i].access_list);
    }
    hdrp = &header[HDR_OTHER].next;
    while (*hdrp) {
	header_mangler *hdr = *hdrp;
	free_acl_access(&hdr->access_list);
	if (!hdr->replacement) {
	    *hdrp = hdr->next;
	    safe_free(hdr->name);
	    safe_free(hdr);
	} else {
	    hdrp = &hdr->next;
	}
    }
}

static void
dump_http_header_replace(StoreEntry * entry, const char *name, header_mangler
    header[])
{
    int i;
    header_mangler *other;
    for (i = 0; i < HDR_ENUM_END; i++) {
	if (NULL == header[i].replacement)
	    continue;
	storeAppendPrintf(entry, "%s %s %s\n", name, httpHeaderNameById(i),
	    header[i].replacement);
    }
    for (other = header[HDR_OTHER].next; other; other = other->next) {
	if (other->replacement == NULL)
	    continue;
	storeAppendPrintf(entry, "%s %s %s\n", name, other->name, other->replacement);
    }
}

static void
parse_http_header_replace(header_mangler header[])
{
    int id, i;
    char *t = NULL;
    if ((t = strtok(NULL, w_space)) == NULL) {
	debug(3, 0) ("%s line %d: %s\n",
	    cfg_filename, config_lineno, config_input_line);
	debug(3, 0) ("parse_http_header_replace: missing header name.\n");
	return;
    }
    /* Now lookup index of header. */
    id = httpHeaderIdByNameDef(t, strlen(t));
    if (strcmp(t, "All") == 0)
	id = HDR_ENUM_END;
    else if (strcmp(t, "Other") == 0)
	id = HDR_OTHER;
    else if (id == -1) {
	header_mangler *hdr = header[HDR_OTHER].next;
	while (hdr && strcasecmp(hdr->name, t) != 0)
	    hdr = hdr->next;
	if (!hdr) {
	    hdr = xcalloc(1, sizeof *hdr);
	    hdr->name = xstrdup(t);
	    hdr->next = header[HDR_OTHER].next;
	    header[HDR_OTHER].next = hdr;
	}
	if (hdr->replacement != NULL)
	    safe_free(hdr->replacement);
	hdr->replacement = xstrdup(t + strlen(t) + 1);
	return;
    }
    if (id != HDR_ENUM_END) {
	if (header[id].replacement != NULL)
	    safe_free(header[id].replacement);
	header[id].replacement = xstrdup(t + strlen(t) + 1);
    } else {
	for (i = 0; i < HDR_ENUM_END; i++) {
	    if (header[i].replacement != NULL)
		safe_free(header[i].replacement);
	    header[i].replacement = xstrdup(t + strlen(t) + 1);
	}
    }
}

static void
free_http_header_replace(header_mangler header[])
{
    int i;
    header_mangler **hdrp;
    for (i = 0; i < HDR_ENUM_END; i++) {
	if (header[i].replacement != NULL)
	    safe_free(header[i].replacement);
    }
    hdrp = &header[HDR_OTHER].next;
    while (*hdrp) {
	header_mangler *hdr = *hdrp;
	free_acl_access(&hdr->access_list);
	if (!hdr->access_list) {
	    *hdrp = hdr->next;
	    safe_free(hdr->name);
	    safe_free(hdr);
	} else {
	    hdrp = &hdr->next;
	}
    }
}
#endif

void
dump_cachedir_options(StoreEntry * entry, struct cache_dir_option *options, SwapDir * sd)
{
    struct cache_dir_option *option;
    if (!options)
	return;
    for (option = options; option->name; option++)
	option->dump(entry, option->name, sd);
}

static void
dump_cachedir(StoreEntry * entry, const char *name, cacheSwap swap)
{
    SwapDir *s;
    int i;
    for (i = 0; i < swap.n_configured; i++) {
	s = swap.swapDirs + i;
	storeAppendPrintf(entry, "%s %s %s", name, s->type, s->path);
	if (s->dump)
	    s->dump(entry, s);
	dump_cachedir_options(entry, common_cachedir_options, s);
	storeAppendPrintf(entry, "\n");
    }
}

static int
check_null_cachedir(cacheSwap swap)
{
    return swap.swapDirs == NULL;
}

static int
check_null_string(char *s)
{
    return s == NULL;
}

static void
allocate_new_authScheme(authConfig * cfg)
{
    if (cfg->schemes == NULL) {
	cfg->n_allocated = 4;
	cfg->schemes = xcalloc(cfg->n_allocated, sizeof(authScheme));
    }
    if (cfg->n_allocated == cfg->n_configured) {
	authScheme *tmp;
	cfg->n_allocated <<= 1;
	tmp = xcalloc(cfg->n_allocated, sizeof(authScheme));
	xmemcpy(tmp, cfg->schemes, cfg->n_configured * sizeof(authScheme));
	xfree(cfg->schemes);
	cfg->schemes = tmp;
    }
}

static void
parse_authparam(authConfig * config)
{
    char *type_str;
    char *param_str;
    authScheme *scheme = NULL;
    int type, i;

    if ((type_str = strtok(NULL, w_space)) == NULL)
	self_destruct();

    if ((param_str = strtok(NULL, w_space)) == NULL)
	self_destruct();

    if ((type = authenticateAuthSchemeId(type_str)) == -1) {
	debug(3, 0) ("Parsing Config File: Unknown authentication scheme '%s'.\n", type_str);
	return;
    }
    for (i = 0; i < config->n_configured; i++) {
	if (config->schemes[i].Id == type) {
	    scheme = config->schemes + i;
	}
    }

    if (scheme == NULL) {
	allocate_new_authScheme(config);
	scheme = config->schemes + config->n_configured;
	config->n_configured++;
	scheme->Id = type;
	scheme->typestr = authscheme_list[type].typestr;
    }
    authscheme_list[type].parse(scheme, config->n_configured, param_str);
}

static void
free_authparam(authConfig * cfg)
{
    authScheme *scheme;
    int i;
    /* DON'T FREE THESE FOR RECONFIGURE */
    if (reconfiguring)
	return;
    for (i = 0; i < cfg->n_configured; i++) {
	scheme = cfg->schemes + i;
	authscheme_list[scheme->Id].freeconfig(scheme);
    }
    safe_free(cfg->schemes);
    cfg->schemes = NULL;
    cfg->n_allocated = 0;
    cfg->n_configured = 0;
}

static void
dump_authparam(StoreEntry * entry, const char *name, authConfig cfg)
{
    authScheme *scheme;
    int i;
    for (i = 0; i < cfg.n_configured; i++) {
	scheme = cfg.schemes + i;
	authscheme_list[scheme->Id].dump(entry, name, scheme);
    }
}

void
allocate_new_swapdir(cacheSwap * swap)
{
    if (swap->swapDirs == NULL) {
	swap->n_allocated = 4;
	swap->swapDirs = xcalloc(swap->n_allocated, sizeof(SwapDir));
    }
    if (swap->n_allocated == swap->n_configured) {
	SwapDir *tmp;
	swap->n_allocated <<= 1;
	tmp = xcalloc(swap->n_allocated, sizeof(SwapDir));
	xmemcpy(tmp, swap->swapDirs, swap->n_configured * sizeof(SwapDir));
	xfree(swap->swapDirs);
	swap->swapDirs = tmp;
    }
}

static int
find_fstype(char *type)
{
    int i;
    for (i = 0; storefs_list[i].typestr != NULL; i++) {
	if (strcasecmp(type, storefs_list[i].typestr) == 0) {
	    return i;
	}
    }
    return (-1);
}

static void
parse_cachedir(cacheSwap * swap)
{
    char *type_str;
    char *path_str;
    SwapDir *sd;
    int i;
    int fs;

    if ((type_str = strtok(NULL, w_space)) == NULL)
	self_destruct();

    if ((path_str = strtok(NULL, w_space)) == NULL)
	self_destruct();

    fs = find_fstype(type_str);
    if (fs < 0)
	self_destruct();

    /* reconfigure existing dir */
    for (i = 0; i < swap->n_configured; i++) {
	if ((strcasecmp(path_str, swap->swapDirs[i].path) == 0)) {
	    sd = swap->swapDirs + i;
	    if (sd->type != storefs_list[fs].typestr) {
		debug(3, 0) ("ERROR: Can't change type of existing cache_dir %s %s to %s. Restart required\n", sd->type, sd->path, type_str);
		return;
	    }
	    storefs_list[fs].reconfigurefunc(sd, i, path_str);
	    update_maxobjsize();
	    return;
	}
    }

    /* new cache_dir */
    assert(swap->n_configured < 63);	/* 7 bits, signed */

    allocate_new_swapdir(swap);
    sd = swap->swapDirs + swap->n_configured;
    sd->type = storefs_list[fs].typestr;
    /* defaults in case fs implementation fails to set these */
    sd->min_objsize = 0;
    sd->max_objsize = -1;
    sd->fs.blksize = 1024;
    /* parse the FS parameters and options */
    storefs_list[fs].parsefunc(sd, swap->n_configured, path_str);
    swap->n_configured++;
    /* Update the max object size */
    update_maxobjsize();
}

static void
parse_cachedir_option_readonly(SwapDir * sd, const char *option, const char *value, int reconfiguring)
{
    int read_only = 0;
    if (value)
	read_only = xatoi(value);
    else
	read_only = 1;
    sd->flags.read_only = read_only;
}

static void
dump_cachedir_option_readonly(StoreEntry * e, const char *option, SwapDir * sd)
{
    if (sd->flags.read_only)
	storeAppendPrintf(e, " %s", option);
}

static void
parse_cachedir_option_minsize(SwapDir * sd, const char *option, const char *value, int reconfiguring)
{
    squid_off_t size;

    if (!value)
	self_destruct();

    size = strto_off_t(value, NULL, 10);

    if (reconfiguring && sd->min_objsize != size)
	debug(3, 1) ("Cache dir '%s' min object size now %ld\n", sd->path, (long int) size);

    sd->min_objsize = size;
}

static void
dump_cachedir_option_minsize(StoreEntry * e, const char *option, SwapDir * sd)
{
    if (sd->min_objsize != 0)
	storeAppendPrintf(e, " %s=%ld", option, (long int) sd->min_objsize);
}

static void
parse_cachedir_option_maxsize(SwapDir * sd, const char *option, const char *value, int reconfiguring)
{
    squid_off_t size;

    if (!value)
	self_destruct();

    size = strto_off_t(value, NULL, 10);

    if (reconfiguring && sd->max_objsize != size)
	debug(3, 1) ("Cache dir '%s' max object size now %ld\n", sd->path, (long int) size);

    sd->max_objsize = size;
}

static void
dump_cachedir_option_maxsize(StoreEntry * e, const char *option, SwapDir * sd)
{
    if (sd->max_objsize != -1)
	storeAppendPrintf(e, " %s=%ld", option, (long int) sd->max_objsize);
}

void
parse_cachedir_options(SwapDir * sd, struct cache_dir_option *options, int reconfiguring)
{
    int old_read_only = sd->flags.read_only;
    char *name, *value;
    struct cache_dir_option *option, *op;

    while ((name = strtok(NULL, w_space)) != NULL) {
	value = strchr(name, '=');
	if (value)
	    *value++ = '\0';	/* cut on = */
	option = NULL;
	if (options) {
	    for (op = options; !option && op->name; op++) {
		if (strcmp(op->name, name) == 0) {
		    option = op;
		    break;
		}
	    }
	}
	for (op = common_cachedir_options; !option && op->name; op++) {
	    if (strcmp(op->name, name) == 0) {
		option = op;
		break;
	    }
	}
	if (!option || !option->parse)
	    self_destruct();
	option->parse(sd, name, value, reconfiguring);
    }
    /*
     * Handle notifications about reconfigured single-options with no value
     * where the removal of the option cannot be easily detected in the
     * parsing...
     */
    if (reconfiguring) {
	if (old_read_only != sd->flags.read_only) {
	    debug(3, 1) ("Cache dir '%s' now %s\n",
		sd->path, sd->flags.read_only ? "Read-Only" : "Read-Write");
	}
    }
}

static void
free_cachedir(cacheSwap * swap)
{
    SwapDir *s;
    int i;
    /* DON'T FREE THESE FOR RECONFIGURE */
    if (reconfiguring)
	return;
    for (i = 0; i < swap->n_configured; i++) {
	s = swap->swapDirs + i;
	s->freefs(s);
	xfree(s->path);
    }
    safe_free(swap->swapDirs);
    swap->swapDirs = NULL;
    swap->n_allocated = 0;
    swap->n_configured = 0;
}

static const char *
peer_type_str(const peer_t type)
{
    switch (type) {
    case PEER_PARENT:
	return "parent";
	break;
    case PEER_SIBLING:
	return "sibling";
	break;
    case PEER_MULTICAST:
	return "multicast";
	break;
    default:
	return "unknown";
	break;
    }
}

static void
dump_peer(StoreEntry * entry, const char *name, peer * p)
{
    domain_ping *d;
    domain_type *t;
    LOCAL_ARRAY(char, xname, 128);
    while (p != NULL) {
	storeAppendPrintf(entry, "%s %s %s %d %d",
	    name,
	    p->host,
	    neighborTypeStr(p),
	    p->http_port,
	    p->icp.port);
	dump_peer_options(entry, p);
	for (d = p->peer_domain; d; d = d->next) {
	    storeAppendPrintf(entry, "cache_peer_domain %s %s%s\n",
		p->host,
		d->do_ping ? null_string : "!",
		d->domain);
	}
	if (p->access) {
	    snprintf(xname, 128, "cache_peer_access %s", p->name);
	    dump_acl_access(entry, xname, p->access);
	}
	for (t = p->typelist; t; t = t->next) {
	    storeAppendPrintf(entry, "neighbor_type_domain %s %s %s\n",
		p->host,
		peer_type_str(t->type),
		t->domain);
	}
	p = p->next;
    }
}

static void
parse_peer(peer ** head)
{
    char *token = NULL;
    peer *p;
    p = cbdataAlloc(peer);
    p->http_port = CACHE_HTTP_PORT;
    p->icp.port = CACHE_ICP_PORT;
    p->weight = 1;
    p->stats.logged_state = PEER_ALIVE;
    p->monitor.state = PEER_ALIVE;
    p->monitor.interval = 300;
    p->tcp_up = PEER_TCP_MAGIC_COUNT;
    if ((token = strtok(NULL, w_space)) == NULL)
	self_destruct();
    p->host = xstrdup(token);
    p->name = xstrdup(token);
    if ((token = strtok(NULL, w_space)) == NULL)
	self_destruct();
    p->type = parseNeighborType(token);
    if (p->type == PEER_MULTICAST) {
	p->options.no_digest = 1;
	p->options.no_netdb_exchange = 1;
    }
    p->http_port = GetShort();
    if (!p->http_port)
	self_destruct();
    p->icp.port = GetShort();
    p->connection_auth = -1;	/* auto */
    while ((token = strtok(NULL, w_space))) {
	if (!strcasecmp(token, "proxy-only")) {
	    p->options.proxy_only = 1;
	} else if (!strcasecmp(token, "no-query")) {
	    p->options.no_query = 1;
	} else if (!strcasecmp(token, "no-digest")) {
	    p->options.no_digest = 1;
	} else if (!strcasecmp(token, "multicast-responder")) {
	    p->options.mcast_responder = 1;
	} else if (!strncasecmp(token, "weight=", 7)) {
	    p->weight = xatoi(token + 7);
	} else if (!strcasecmp(token, "closest-only")) {
	    p->options.closest_only = 1;
	} else if (!strncasecmp(token, "ttl=", 4)) {
	    p->mcast.ttl = xatoi(token + 4);
	    if (p->mcast.ttl < 0)
		p->mcast.ttl = 0;
	    if (p->mcast.ttl > 128)
		p->mcast.ttl = 128;
	} else if (!strcasecmp(token, "default")) {
	    p->options.default_parent = 1;
	} else if (!strcasecmp(token, "round-robin")) {
	    p->options.roundrobin = 1;
	} else if (!strcasecmp(token, "userhash")) {
	    p->options.userhash = 1;
	} else if (!strcasecmp(token, "sourcehash")) {
	    p->options.sourcehash = 1;
#if USE_HTCP
	} else if (!strcasecmp(token, "htcp")) {
	    p->options.htcp = 1;
	} else if (!strcasecmp(token, "htcp-oldsquid")) {
	    p->options.htcp = 1;
	    p->options.htcp_oldsquid = 1;
#endif
	} else if (!strcasecmp(token, "no-netdb-exchange")) {
	    p->options.no_netdb_exchange = 1;
#if USE_CARP
	} else if (!strcasecmp(token, "carp")) {
	    if (p->type != PEER_PARENT)
		fatalf("parse_peer: non-parent carp peer %s/%d\n", p->host, p->http_port);
	    p->options.carp = 1;
#endif
#if DELAY_POOLS
	} else if (!strcasecmp(token, "no-delay")) {
	    p->options.no_delay = 1;
#endif
	} else if (!strncasecmp(token, "login=", 6)) {
	    p->login = xstrdup(token + 6);
	    rfc1738_unescape(p->login);
	} else if (!strncasecmp(token, "connect-timeout=", 16)) {
	    p->connect_timeout = xatoi(token + 16);
#if USE_CACHE_DIGESTS
	} else if (!strncasecmp(token, "digest-url=", 11)) {
	    p->digest_url = xstrdup(token + 11);
#endif
	} else if (!strcasecmp(token, "allow-miss")) {
	    p->options.allow_miss = 1;
	} else if (!strncasecmp(token, "max-conn=", 9)) {
	    p->max_conn = xatoi(token + 9);
	} else if (!strcasecmp(token, "originserver")) {
	    p->options.originserver = 1;
	} else if (!strncasecmp(token, "name=", 5)) {
	    safe_free(p->name);
	    if (token[5])
		p->name = xstrdup(token + 5);
	} else if (!strncasecmp(token, "monitorurl=", 11)) {
	    char *url = token + 11;
	    safe_free(p->monitor.url);
	    if (*url == '/') {
		int size = strlen("http://") + strlen(p->host) + 16 + strlen(url);
		p->monitor.url = xmalloc(size);
		snprintf(p->monitor.url, size, "http://%s:%d%s", p->host, p->http_port, url);
	    } else {
		p->monitor.url = xstrdup(url);
	    }
	} else if (!strncasecmp(token, "monitorsize=", 12)) {
	    char *token2 = strchr(token + 12, ',');
	    if (!token2)
		token2 = strchr(token + 12, '-');
	    if (token2)
		*token2++ = '\0';
	    p->monitor.min = xatoi(token + 12);
	    p->monitor.max = token2 ? xatoi(token2) : -1;
	} else if (!strncasecmp(token, "monitorinterval=", 16)) {
	    p->monitor.interval = xatoi(token + 16);
	} else if (!strncasecmp(token, "monitortimeout=", 15)) {
	    p->monitor.timeout = xatoi(token + 15);
	} else if (!strncasecmp(token, "forceddomain=", 13)) {
	    safe_free(p->domain);
	    if (token[13])
		p->domain = xstrdup(token + 13);
#if USE_SSL
	} else if (strcmp(token, "ssl") == 0) {
	    p->use_ssl = 1;
	} else if (strncmp(token, "sslcert=", 8) == 0) {
	    safe_free(p->sslcert);
	    p->sslcert = xstrdup(token + 8);
	} else if (strncmp(token, "sslkey=", 7) == 0) {
	    safe_free(p->sslkey);
	    p->sslkey = xstrdup(token + 7);
	} else if (strncmp(token, "sslversion=", 11) == 0) {
	    p->sslversion = xatoi(token + 11);
	} else if (strncmp(token, "ssloptions=", 11) == 0) {
	    safe_free(p->ssloptions);
	    p->ssloptions = xstrdup(token + 11);
	} else if (strncmp(token, "sslcipher=", 10) == 0) {
	    safe_free(p->sslcipher);
	    p->sslcipher = xstrdup(token + 10);
	} else if (strncmp(token, "sslcafile=", 10) == 0) {
	    safe_free(p->sslcafile);
	    p->sslcafile = xstrdup(token + 10);
	} else if (strncmp(token, "sslcapath=", 10) == 0) {
	    safe_free(p->sslcapath);
	    p->sslcapath = xstrdup(token + 10);
	} else if (strncmp(token, "sslcrlfile=", 11) == 0) {
	    safe_free(p->sslcrlfile);
	    p->sslcrlfile = xstrdup(token + 11);
	} else if (strncmp(token, "sslflags=", 9) == 0) {
	    safe_free(p->sslflags);
	    p->sslflags = xstrdup(token + 9);
	} else if (strncmp(token, "ssldomain=", 10) == 0) {
	    safe_free(p->ssldomain);
	    p->ssldomain = xstrdup(token + 10);
#endif
	} else if (strcmp(token, "front-end-https=off") == 0) {
	    p->front_end_https = 0;
	} else if (strcmp(token, "front-end-https") == 0) {
	    p->front_end_https = 1;
	} else if (strcmp(token, "front-end-https=on") == 0) {
	    p->front_end_https = 1;
	} else if (strcmp(token, "front-end-https=auto") == 0) {
	    p->front_end_https = -1;
	} else if (strcmp(token, "connection-auth=off") == 0) {
	    p->connection_auth = 0;
	} else if (strcmp(token, "connection-auth") == 0) {
	    p->connection_auth = 1;
	} else if (strcmp(token, "connection-auth=on") == 0) {
	    p->connection_auth = 1;
	} else if (strcmp(token, "connection-auth=auto") == 0) {
	    p->connection_auth = -1;
	} else {
	    debug(3, 0) ("parse_peer: token='%s'\n", token);
	    self_destruct();
	}
    }
    if (peerFindByName(p->name))
	fatalf("ERROR: cache_peer %s specified twice\n", p->name);
    if (p->weight < 1)
	p->weight = 1;
    p->icp.version = ICP_VERSION_CURRENT;
    p->test_fd = -1;
#if USE_CACHE_DIGESTS
    if (!p->options.no_digest) {
	p->digest = peerDigestCreate(p);
	cbdataLock(p->digest);	/* so we know when/if digest disappears */
    }
#endif
#if USE_SSL
    if (p->use_ssl) {
	p->sslContext = sslCreateClientContext(p->sslcert, p->sslkey, p->sslversion, p->sslcipher, p->ssloptions, p->sslflags, p->sslcafile, p->sslcapath, p->sslcrlfile);
    }
#endif
    while (*head != NULL)
	head = &(*head)->next;
    *head = p;
    Config.npeers++;
    peerClearRRStart();
}

static void
free_peer(peer ** P)
{
    peer *p;
    while ((p = *P) != NULL) {
	*P = p->next;
#if USE_CACHE_DIGESTS
	if (p->digest) {
	    PeerDigest *pd = p->digest;
	    p->digest = NULL;
	    peerDigestNotePeerGone(pd);
	    cbdataUnlock(pd);
	}
#endif
	cbdataFree(p);
    }
    Config.npeers = 0;
}

static void
dump_cachemgrpasswd(StoreEntry * entry, const char *name, cachemgr_passwd * list)
{
    wordlist *w;
    while (list != NULL) {
	if (strcmp(list->passwd, "none") && strcmp(list->passwd, "disable"))
	    storeAppendPrintf(entry, "%s XXXXXXXXXX", name);
	else
	    storeAppendPrintf(entry, "%s %s", name, list->passwd);
	for (w = list->actions; w != NULL; w = w->next) {
	    storeAppendPrintf(entry, " %s", w->key);
	}
	storeAppendPrintf(entry, "\n");
	list = list->next;
    }
}

static void
parse_cachemgrpasswd(cachemgr_passwd ** head)
{
    char *passwd = NULL;
    wordlist *actions = NULL;
    cachemgr_passwd *p;
    cachemgr_passwd **P;
    parse_string(&passwd);
    parse_wordlist(&actions);
    p = xcalloc(1, sizeof(cachemgr_passwd));
    p->passwd = passwd;
    p->actions = actions;
    for (P = head; *P; P = &(*P)->next) {
	/*
	 * See if any of the actions from this line already have a
	 * password from previous lines.  The password checking
	 * routines in cache_manager.c take the the password from
	 * the first cachemgr_passwd struct that contains the
	 * requested action.  Thus, we should warn users who might
	 * think they can have two passwords for the same action.
	 */
	wordlist *w;
	wordlist *u;
	for (w = (*P)->actions; w; w = w->next) {
	    for (u = actions; u; u = u->next) {
		if (strcmp(w->key, u->key))
		    continue;
		debug(0, 0) ("WARNING: action '%s' (line %d) already has a password\n",
		    u->key, config_lineno);
	    }
	}
    }
    *P = p;
}

static void
free_cachemgrpasswd(cachemgr_passwd ** head)
{
    cachemgr_passwd *p;
    while ((p = *head) != NULL) {
	*head = p->next;
	xfree(p->passwd);
	wordlistDestroy(&p->actions);
	xfree(p);
    }
}

static void
dump_denyinfo(StoreEntry * entry, const char *name, acl_deny_info_list * var)
{
    acl_name_list *a;
    while (var != NULL) {
	storeAppendPrintf(entry, "%s %s", name, var->err_page_name);
	for (a = var->acl_list; a != NULL; a = a->next)
	    storeAppendPrintf(entry, " %s", a->name);
	storeAppendPrintf(entry, "\n");
	var = var->next;
    }
}

static void
parse_denyinfo(acl_deny_info_list ** var)
{
    aclParseDenyInfoLine(var);
}

void
free_denyinfo(acl_deny_info_list ** list)
{
    acl_deny_info_list *a = NULL;
    acl_deny_info_list *a_next = NULL;
    acl_name_list *l = NULL;
    acl_name_list *l_next = NULL;
    for (a = *list; a; a = a_next) {
	for (l = a->acl_list; l; l = l_next) {
	    l_next = l->next;
	    memFree(l, MEM_ACL_NAME_LIST);
	    l = NULL;
	}
	a_next = a->next;
	memFree(a, MEM_ACL_DENY_INFO_LIST);
	a = NULL;
    }
    *list = NULL;
}

static void
parse_peer_access(void)
{
    char *host = NULL;
    peer *p;
    if (!(host = strtok(NULL, w_space)))
	self_destruct();
    if ((p = peerFindByName(host)) == NULL) {
	debug(15, 0) ("%s, line %d: No cache_peer '%s'\n",
	    cfg_filename, config_lineno, host);
	return;
    }
    aclParseAccessLine(&p->access);
}

static void
parse_hostdomain(void)
{
    char *host = NULL;
    char *domain = NULL;
    if (!(host = strtok(NULL, w_space)))
	self_destruct();
    while ((domain = strtok(NULL, list_sep))) {
	domain_ping *l = NULL;
	domain_ping **L = NULL;
	peer *p;
	if ((p = peerFindByName(host)) == NULL) {
	    debug(15, 0) ("%s, line %d: No cache_peer '%s'\n",
		cfg_filename, config_lineno, host);
	    continue;
	}
	l = xcalloc(1, sizeof(domain_ping));
	l->do_ping = 1;
	if (*domain == '!') {	/* check for !.edu */
	    l->do_ping = 0;
	    domain++;
	}
	l->domain = xstrdup(domain);
	for (L = &(p->peer_domain); *L; L = &((*L)->next));
	*L = l;
    }
}

static void
parse_hostdomaintype(void)
{
    char *host = NULL;
    char *type = NULL;
    char *domain = NULL;
    if (!(host = strtok(NULL, w_space)))
	self_destruct();
    if (!(type = strtok(NULL, w_space)))
	self_destruct();
    while ((domain = strtok(NULL, list_sep))) {
	domain_type *l = NULL;
	domain_type **L = NULL;
	peer *p;
	if ((p = peerFindByName(host)) == NULL) {
	    debug(15, 0) ("%s, line %d: No cache_peer '%s'\n",
		cfg_filename, config_lineno, host);
	    return;
	}
	l = xcalloc(1, sizeof(domain_type));
	l->type = parseNeighborType(type);
	l->domain = xstrdup(domain);
	for (L = &(p->typelist); *L; L = &((*L)->next));
	*L = l;
    }
}

#if UNUSED_CODE
static void
dump_ushortlist(StoreEntry * entry, const char *name, ushortlist * u)
{
    while (u) {
	storeAppendPrintf(entry, "%s %d\n", name, (int) u->i);
	u = u->next;
    }
}

static int
check_null_ushortlist(ushortlist * u)
{
    return u == NULL;
}

static void
parse_ushortlist(ushortlist ** P)
{
    char *token;
    u_short i;
    ushortlist *u;
    ushortlist **U;
    while ((token = strtok(NULL, w_space))) {
	i = GetShort();
	u = xcalloc(1, sizeof(ushortlist));
	u->i = i;
	for (U = P; *U; U = &(*U)->next);
	*U = u;
    }
}

static void
free_ushortlist(ushortlist ** P)
{
    ushortlist *u;
    while ((u = *P) != NULL) {
	*P = u->next;
	xfree(u);
    }
}
#endif

static void
dump_int(StoreEntry * entry, const char *name, int var)
{
    storeAppendPrintf(entry, "%s %d\n", name, var);
}

void
parse_int(int *var)
{
    int i;
    i = GetInteger();
    *var = i;
}

static void
free_int(int *var)
{
    *var = 0;
}

static void
dump_onoff(StoreEntry * entry, const char *name, int var)
{
    storeAppendPrintf(entry, "%s %s\n", name, var ? "on" : "off");
}

void
parse_onoff(int *var)
{
    char *token = strtok(NULL, w_space);

    if (token == NULL)
	self_destruct();
    if (!strcasecmp(token, "on") || !strcasecmp(token, "enable"))
	*var = 1;
    else
	*var = 0;
}

#define free_onoff free_int

static void
dump_tristate(StoreEntry * entry, const char *name, int var)
{
    const char *state;
    if (var > 0)
	state = "on";
    else if (var < 0)
	state = "warn";
    else
	state = "off";
    storeAppendPrintf(entry, "%s %s\n", name, state);
}

static void
parse_tristate(int *var)
{
    char *token = strtok(NULL, w_space);

    if (token == NULL)
	self_destruct();
    if (!strcasecmp(token, "on") || !strcasecmp(token, "enable"))
	*var = 1;
    else if (!strcasecmp(token, "warn"))
	*var = -1;
    else
	*var = 0;
}

#define free_tristate free_int

static void
dump_refreshpattern(StoreEntry * entry, const char *name, refresh_t * head)
{
    while (head != NULL) {
	storeAppendPrintf(entry, "%s%s %s %d %d%% %d\n",
	    name,
	    head->flags.icase ? " -i" : null_string,
	    head->pattern,
	    (int) head->min / 60,
	    (int) (100.0 * head->pct + 0.5),
	    (int) head->max / 60);
#if HTTP_VIOLATIONS
	if (head->flags.override_expire)
	    storeAppendPrintf(entry, " override-expire");
	if (head->flags.override_lastmod)
	    storeAppendPrintf(entry, " override-lastmod");
	if (head->flags.reload_into_ims)
	    storeAppendPrintf(entry, " reload-into-ims");
	if (head->flags.ignore_reload)
	    storeAppendPrintf(entry, " ignore-reload");
	if (head->flags.ignore_no_cache)
	    storeAppendPrintf(entry, " ignore-no-cache");
	if (head->flags.ignore_private)
	    storeAppendPrintf(entry, " ignore-private");
	if (head->flags.ignore_auth)
	    storeAppendPrintf(entry, " ignore-auth");
#endif
	storeAppendPrintf(entry, "\n");
	head = head->next;
    }
}

static void
parse_refreshpattern(refresh_t ** head)
{
    char *token;
    char *pattern;
    time_t min = 0;
    double pct = 0.0;
    time_t max = 0;
#if HTTP_VIOLATIONS
    int override_expire = 0;
    int override_lastmod = 0;
    int reload_into_ims = 0;
    int ignore_reload = 0;
    int ignore_no_cache = 0;
    int ignore_private = 0;
    int ignore_auth = 0;
#endif
    int i;
    refresh_t *t;
    regex_t comp;
    int errcode;
    int flags = REG_EXTENDED | REG_NOSUB;
    if ((token = strtok(NULL, w_space)) == NULL)
	self_destruct();
    if (strcmp(token, "-i") == 0) {
	flags |= REG_ICASE;
	token = strtok(NULL, w_space);
    } else if (strcmp(token, "+i") == 0) {
	flags &= ~REG_ICASE;
	token = strtok(NULL, w_space);
    }
    if (token == NULL)
	self_destruct();
    pattern = xstrdup(token);
    i = GetInteger();		/* token: min */
    min = (time_t) (i * 60);	/* convert minutes to seconds */
    i = GetInteger();		/* token: pct */
    pct = (double) i / 100.0;
    i = GetInteger();		/* token: max */
    max = (time_t) (i * 60);	/* convert minutes to seconds */
    /* Options */
    while ((token = strtok(NULL, w_space)) != NULL) {
#if HTTP_VIOLATIONS
	if (!strcmp(token, "override-expire"))
	    override_expire = 1;
	else if (!strcmp(token, "override-lastmod"))
	    override_lastmod = 1;
	else if (!strcmp(token, "ignore-no-cache"))
	    ignore_no_cache = 1;
	else if (!strcmp(token, "ignore-private"))
	    ignore_private = 1;
	else if (!strcmp(token, "ignore-auth"))
	    ignore_auth = 1;
	else if (!strcmp(token, "reload-into-ims")) {
	    reload_into_ims = 1;
	    refresh_nocache_hack = 1;
	    /* tell client_side.c that this is used */
	} else if (!strcmp(token, "ignore-reload")) {
	    ignore_reload = 1;
	    refresh_nocache_hack = 1;
	    /* tell client_side.c that this is used */
	} else
#endif
	    debug(22, 0) ("redreshAddToList: Unknown option '%s': %s\n",
		pattern, token);
    }
    if ((errcode = regcomp(&comp, pattern, flags)) != 0) {
	char errbuf[256];
	regerror(errcode, &comp, errbuf, sizeof errbuf);
	debug(22, 0) ("%s line %d: %s\n",
	    cfg_filename, config_lineno, config_input_line);
	debug(22, 0) ("refreshAddToList: Invalid regular expression '%s': %s\n",
	    pattern, errbuf);
	return;
    }
    pct = pct < 0.0 ? 0.0 : pct;
    max = max < 0 ? 0 : max;
    t = xcalloc(1, sizeof(refresh_t));
    t->pattern = (char *) xstrdup(pattern);
    t->compiled_pattern = comp;
    t->min = min;
    t->pct = pct;
    t->max = max;
    if (flags & REG_ICASE)
	t->flags.icase = 1;
#if HTTP_VIOLATIONS
    if (override_expire)
	t->flags.override_expire = 1;
    if (override_lastmod)
	t->flags.override_lastmod = 1;
    if (reload_into_ims)
	t->flags.reload_into_ims = 1;
    if (ignore_reload)
	t->flags.ignore_reload = 1;
    if (ignore_no_cache)
	t->flags.ignore_no_cache = 1;
    if (ignore_private)
	t->flags.ignore_private = 1;
    if (ignore_auth)
	t->flags.ignore_auth = 1;
#endif
    t->next = NULL;
    while (*head)
	head = &(*head)->next;
    *head = t;
    safe_free(pattern);
}

#if UNUSED_CODE
static int
check_null_refreshpattern(refresh_t * data)
{
    return data == NULL;
}
#endif

static void
free_refreshpattern(refresh_t ** head)
{
    refresh_t *t;
    while ((t = *head) != NULL) {
	*head = t->next;
	safe_free(t->pattern);
	regfree(&t->compiled_pattern);
	safe_free(t);
    }
}

static void
dump_string(StoreEntry * entry, const char *name, char *var)
{
    if (var != NULL)
	storeAppendPrintf(entry, "%s %s\n", name, var);
}

static void
parse_string(char **var)
{
    char *token = strtok(NULL, w_space);
    safe_free(*var);
    if (token == NULL)
	self_destruct();
    *var = xstrdup(token);
}

static void
free_string(char **var)
{
    safe_free(*var);
}

void
parse_eol(char *volatile *var)
{
    unsigned char *token = (unsigned char *) strtok(NULL, null_string);
    safe_free(*var);
    if (token == NULL)
	self_destruct();
    while (*token && xisspace(*token))
	token++;
    if (!*token)
	self_destruct();
    *var = xstrdup((char *) token);
}

#define dump_eol dump_string
#define free_eol free_string


static void
dump_time_t(StoreEntry * entry, const char *name, time_t var)
{
    storeAppendPrintf(entry, "%s %d seconds\n", name, (int) var);
}

void
parse_time_t(time_t * var)
{
    parseTimeLine(var, T_SECOND_STR);
}

static void
free_time_t(time_t * var)
{
    *var = 0;
}

#if UNUSED_CODE
static void
dump_size_t(StoreEntry * entry, const char *name, squid_off_t var)
{
    storeAppendPrintf(entry, "%s %" PRINTF_OFF_T "\n", name, var);
}

#endif

static void
dump_b_size_t(StoreEntry * entry, const char *name, squid_off_t var)
{
    storeAppendPrintf(entry, "%s %" PRINTF_OFF_T " %s\n", name, var, B_BYTES_STR);
}

static void
dump_kb_size_t(StoreEntry * entry, const char *name, squid_off_t var)
{
    storeAppendPrintf(entry, "%s %" PRINTF_OFF_T " %s\n", name, var, B_KBYTES_STR);
}

static void
parse_b_size_t(squid_off_t * var)
{
    parseBytesLine(var, B_BYTES_STR);
}

CBDATA_TYPE(body_size);

static void
parse_body_size_t(dlink_list * bodylist)
{
    body_size *bs;
    CBDATA_INIT_TYPE(body_size);
    bs = cbdataAlloc(body_size);
    bs->maxsize = GetOffT();
    aclParseAccessLine(&bs->access_list);

    dlinkAddTail(bs, &bs->node, bodylist);
}

static void
dump_body_size_t(StoreEntry * entry, const char *name, dlink_list bodylist)
{
    body_size *bs;
    bs = (body_size *) bodylist.head;
    while (bs) {
	acl_list *l;
	acl_access *head = bs->access_list;
	while (head != NULL) {
	    storeAppendPrintf(entry, "%s %" PRINTF_OFF_T " %s", name, bs->maxsize,
		head->allow ? "Allow" : "Deny");
	    for (l = head->acl_list; l != NULL; l = l->next) {
		storeAppendPrintf(entry, " %s%s",
		    l->op ? null_string : "!",
		    l->acl->name);
	    }
	    storeAppendPrintf(entry, "\n");
	    head = head->next;
	}
	bs = (body_size *) bs->node.next;
    }
}

static void
free_body_size_t(dlink_list * bodylist)
{
    body_size *bs, *tempnode;
    bs = (body_size *) bodylist->head;
    while (bs) {
	bs->maxsize = 0;
	aclDestroyAccessList(&bs->access_list);
	tempnode = (body_size *) bs->node.next;
	dlinkDelete(&bs->node, bodylist);
	cbdataFree(bs);
	bs = tempnode;
    }
}

static int
check_null_body_size_t(dlink_list bodylist)
{
    return bodylist.head == NULL;
}


static void
parse_kb_size_t(squid_off_t * var)
{
    parseBytesLine(var, B_KBYTES_STR);
}

static void
free_size_t(squid_off_t * var)
{
    *var = 0;
}

#define free_b_size_t free_size_t
#define free_kb_size_t free_size_t
#define free_mb_size_t free_size_t
#define free_gb_size_t free_size_t

static void
dump_ushort(StoreEntry * entry, const char *name, u_short var)
{
    storeAppendPrintf(entry, "%s %d\n", name, var);
}

static void
free_ushort(u_short * u)
{
    *u = 0;
}

static void
parse_ushort(u_short * var)
{
    *var = GetShort();
}

static void
dump_wordlist(StoreEntry * entry, const char *name, const wordlist * list)
{
    while (list != NULL) {
	storeAppendPrintf(entry, "%s %s\n", name, list->key);
	list = list->next;
    }
}

void
parse_wordlist(wordlist ** list)
{
    char *token;
    char *t = strtok(NULL, "");
    while ((token = strwordtok(NULL, &t)))
	wordlistAdd(list, token);
}

static int
check_null_wordlist(wordlist * w)
{
    return w == NULL;
}

static int
check_null_acl_access(acl_access * a)
{
    return a == NULL;
}

#define free_wordlist wordlistDestroy

#define free_uri_whitespace free_int

static void
parse_uri_whitespace(int *var)
{
    char *token = strtok(NULL, w_space);
    if (token == NULL)
	self_destruct();
    if (!strcasecmp(token, "strip"))
	*var = URI_WHITESPACE_STRIP;
    else if (!strcasecmp(token, "deny"))
	*var = URI_WHITESPACE_DENY;
    else if (!strcasecmp(token, "allow"))
	*var = URI_WHITESPACE_ALLOW;
    else if (!strcasecmp(token, "encode"))
	*var = URI_WHITESPACE_ENCODE;
    else if (!strcasecmp(token, "chop"))
	*var = URI_WHITESPACE_CHOP;
    else
	self_destruct();
}


static void
dump_uri_whitespace(StoreEntry * entry, const char *name, int var)
{
    const char *s;
    if (var == URI_WHITESPACE_ALLOW)
	s = "allow";
    else if (var == URI_WHITESPACE_ENCODE)
	s = "encode";
    else if (var == URI_WHITESPACE_CHOP)
	s = "chop";
    else if (var == URI_WHITESPACE_DENY)
	s = "deny";
    else
	s = "strip";
    storeAppendPrintf(entry, "%s %s\n", name, s);
}

static void
free_removalpolicy(RemovalPolicySettings ** settings)
{
    if (!*settings)
	return;
    free_string(&(*settings)->type);
    free_wordlist(&(*settings)->args);
    safe_free(*settings);
}

static void
parse_removalpolicy(RemovalPolicySettings ** settings)
{
    if (*settings)
	free_removalpolicy(settings);
    *settings = xcalloc(1, sizeof(**settings));
    parse_string(&(*settings)->type);
    parse_wordlist(&(*settings)->args);
}

static void
dump_removalpolicy(StoreEntry * entry, const char *name, RemovalPolicySettings * settings)
{
    wordlist *args;
    storeAppendPrintf(entry, "%s %s", name, settings->type);
    args = settings->args;
    while (args) {
	storeAppendPrintf(entry, " %s", args->key);
	args = args->next;
    }
    storeAppendPrintf(entry, "\n");
}

static void
parse_errormap(errormap ** head)
{
    errormap *m = xcalloc(1, sizeof(*m));
    char *url = strtok(NULL, w_space);
    char *token;
    struct error_map_entry **tail = &m->map;
    if (!url)
	self_destruct();
    m->url = xstrdup(url);
    while ((token = strtok(NULL, w_space))) {
	struct error_map_entry *e = xcalloc(1, sizeof(*e));
	e->value = xstrdup(token);
	e->status = xatoi(token);
	if (!e->status)
	    e->status = -errorPageId(token);
	if (!e->status)
	    debug(15, 0) ("WARNING: Unknown errormap code: %s\n", token);
	*tail = e;
	tail = &e->next;
    }
    while (*head)
	head = &(*head)->next;
    *head = m;
}

static void
dump_errormap(StoreEntry * entry, const char *name, errormap * map)
{
    while (map) {
	struct error_map_entry *me;
	storeAppendPrintf(entry, "%s %s",
	    name, map->url);
	for (me = map->map; me; me = me->next)
	    storeAppendPrintf(entry, " %s", me->value);
	storeAppendPrintf(entry, "\n");
	map = map->next;
    }
}

static void
free_errormap(errormap ** head)
{
    while (*head) {
	errormap *map = *head;
	*head = map->next;
	while (map->map) {
	    struct error_map_entry *me = map->map;
	    map->map = me->next;
	    safe_free(me->value);
	    safe_free(me);
	}
	safe_free(map->url);
	safe_free(map);
    }
}

#include "cf_parser.h"

peer_t
parseNeighborType(const char *s)
{
    if (!strcasecmp(s, "parent"))
	return PEER_PARENT;
    if (!strcasecmp(s, "neighbor"))
	return PEER_SIBLING;
    if (!strcasecmp(s, "neighbour"))
	return PEER_SIBLING;
    if (!strcasecmp(s, "sibling"))
	return PEER_SIBLING;
    if (!strcasecmp(s, "multicast"))
	return PEER_MULTICAST;
    debug(15, 0) ("WARNING: Unknown neighbor type: %s\n", s);
    return PEER_SIBLING;
}

#if USE_WCCPv2
static void
parse_sockaddr_in_list(sockaddr_in_list ** head)
{
    char *token;
    char *t;
    char *host;
    char *tmp;
    const struct hostent *hp;
    unsigned short port = 0;
    sockaddr_in_list *s;
    while ((token = strtok(NULL, w_space))) {
	host = NULL;
	port = 0;
	if ((t = strchr(token, ':'))) {
	    /* host:port */
	    host = token;
	    *t = '\0';
	    port = xatos(t + 1);
	    if (0 == port)
		self_destruct();
	} else if ((port = strtol(token, &tmp, 10)), !*tmp) {
	    /* port */
	} else {
	    host = token;
	    port = 0;
	}
	s = xcalloc(1, sizeof(*s));
	s->s.sin_port = htons(port);
	if (NULL == host)
	    s->s.sin_addr = any_addr;
	else if (1 == safe_inet_addr(host, &s->s.sin_addr))
	    (void) 0;
	else if ((hp = gethostbyname(host)))	/* dont use ipcache */
	    s->s.sin_addr = inaddrFromHostent(hp);
	else
	    self_destruct();
	while (*head)
	    head = &(*head)->next;
	*head = s;
    }
}

static void
dump_sockaddr_in_list(StoreEntry * e, const char *n, const sockaddr_in_list * s)
{
    while (s) {
	storeAppendPrintf(e, "%s %s:%d\n",
	    n,
	    inet_ntoa(s->s.sin_addr),
	    ntohs(s->s.sin_port));
	s = s->next;
    }
}

static void
free_sockaddr_in_list(sockaddr_in_list ** head)
{
    sockaddr_in_list *s;
    while ((s = *head) != NULL) {
	*head = s->next;
	xfree(s);
    }
}

#if UNUSED_CODE
static int
check_null_sockaddr_in_list(const sockaddr_in_list * s)
{
    return NULL == s;
}
#endif
#endif /* USE_WCCPv2 */

static void
parse_http_port_specification(http_port_list * s, char *token)
{
    char *host = NULL;
    const struct hostent *hp;
    unsigned short port = 0;
    char *t;
    if ((t = strchr(token, ':'))) {
	/* host:port */
	host = token;
	*t = '\0';
	port = xatos(t + 1);
    } else {
	/* port */
	port = xatos(token);
    }
    if (port == 0)
	self_destruct();
    s->s.sin_port = htons(port);
    if (NULL == host)
	s->s.sin_addr = any_addr;
    else if (1 == safe_inet_addr(host, &s->s.sin_addr))
	(void) 0;
    else if ((hp = gethostbyname(host))) {
	/* dont use ipcache */
	s->s.sin_addr = inaddrFromHostent(hp);
	s->defaultsite = xstrdup(host);
    } else
	self_destruct();
}

static void
parse_http_port_option(http_port_list * s, char *token)
{
    if (strncmp(token, "defaultsite=", 12) == 0) {
	safe_free(s->defaultsite);
	s->defaultsite = xstrdup(token + 12);
	s->accel = 1;
    } else if (strncmp(token, "name=", 5) == 0) {
	safe_free(s->name);
	s->name = xstrdup(token + 5);
    } else if (strcmp(token, "transparent") == 0) {
	s->transparent = 1;
    } else if (strcmp(token, "vhost") == 0) {
	s->vhost = 1;
	s->accel = 1;
    } else if (strcmp(token, "vport") == 0) {
	s->vport = ntohs(s->s.sin_port);
	s->accel = 1;
    } else if (strncmp(token, "vport=", 6) == 0) {
	s->vport = xatos(token + 6);
	s->accel = 1;
    } else if (strcmp(token, "accel") == 0) {
	s->accel = 1;
    } else if (strcmp(token, "no-connection-auth") == 0) {
	s->no_connection_auth = 1;
    } else if (strncmp(token, "urlgroup=", 9) == 0) {
	s->urlgroup = xstrdup(token + 9);
    } else if (strncmp(token, "protocol=", 9) == 0) {
	s->protocol = xstrdup(token + 9);
#if LINUX_TPROXY
    } else if (strcmp(token, "tproxy") == 0) {
	s->tproxy = 1;
	need_linux_tproxy = 1;
#endif
    } else {
	self_destruct();
    }
}

static void
verify_http_port_options(http_port_list * s)
{
    if (s->accel && s->transparent) {
	debug(28, 0) ("Can't be both a transparent proxy and web server accelerator on the same port\n");
	self_destruct();
    }
    if (s->accel && !s->vhost && !s->defaultsite && !s->vport) {
	debug(28, 0) ("Accelerator mode requires at least one of vhost/vport/defaultsite\n");
	self_destruct();
    }
}

static void
free_generic_http_port_data(http_port_list * s)
{
    safe_free(s->name);
    safe_free(s->protocol);
    safe_free(s->defaultsite);
}

static void
cbdataFree_http_port(void *data)
{
    free_generic_http_port_data(data);
}


static void
parse_http_port_list(http_port_list ** head)
{
    CBDATA_TYPE(http_port_list);
    char *token;
    http_port_list *s;
    CBDATA_INIT_TYPE_FREECB(http_port_list, cbdataFree_http_port);
    token = strtok(NULL, w_space);
    if (!token)
	self_destruct();
    s = cbdataAlloc(http_port_list);
    s->protocol = xstrdup("http");
    parse_http_port_specification(s, token);
    /* parse options ... */
    while ((token = strtok(NULL, w_space))) {
	parse_http_port_option(s, token);
    }
    verify_http_port_options(s);
    while (*head)
	head = &(*head)->next;
    *head = s;
}

static void
dump_generic_http_port(StoreEntry * e, const char *n, const http_port_list * s)
{
    storeAppendPrintf(e, "%s %s:%d",
	n,
	inet_ntoa(s->s.sin_addr),
	ntohs(s->s.sin_port));
    if (s->transparent)
	storeAppendPrintf(e, " transparent");
    if (s->accel)
	storeAppendPrintf(e, " accel");
    if (s->defaultsite)
	storeAppendPrintf(e, " defaultsite=%s", s->defaultsite);
    if (s->vhost)
	storeAppendPrintf(e, " vhost");
    if (s->vport == ntohs(s->s.sin_port))
	storeAppendPrintf(e, " vport");
    else if (s->vport)
	storeAppendPrintf(e, " vport=%d", s->vport);
    if (s->urlgroup)
	storeAppendPrintf(e, " urlgroup=%s", s->urlgroup);
    if (s->protocol)
	storeAppendPrintf(e, " protocol=%s", s->protocol);
    if (s->no_connection_auth)
	storeAppendPrintf(e, " no-connection-auth");
#if LINUX_TPROXY
    if (s->tproxy)
	storeAppendPrintf(e, " tproxy");
#endif
}
static void
dump_http_port_list(StoreEntry * e, const char *n, const http_port_list * s)
{
    while (s) {
	dump_generic_http_port(e, n, s);
	storeAppendPrintf(e, "\n");
	s = s->next;
    }
}

static void
free_http_port_list(http_port_list ** head)
{
    http_port_list *s;
    while ((s = *head) != NULL) {
	*head = s->next;
	cbdataFree(s);
    }
}

#if UNUSED_CODE
static int
check_null_http_port_list(const http_port_list * s)
{
    return NULL == s;
}
#endif

#if USE_SSL
static void
cbdataFree_https_port(void *data)
{
    https_port_list *s = data;
    free_generic_http_port_data(&s->http);
    safe_free(s->cert);
    safe_free(s->key);
    safe_free(s->cipher);
    safe_free(s->options);
    safe_free(s->clientca);
    safe_free(s->cafile);
    safe_free(s->capath);
    safe_free(s->crlfile);
    safe_free(s->dhfile);
    safe_free(s->sslflags);
    safe_free(s->sslcontext);
    if (s->sslContext)
	SSL_CTX_free(s->sslContext);
    s->sslContext = NULL;
}

static void
parse_https_port_list(https_port_list ** head)
{
    CBDATA_TYPE(https_port_list);
    char *token;
    https_port_list *s;
    CBDATA_INIT_TYPE_FREECB(https_port_list, cbdataFree_https_port);
    token = strtok(NULL, w_space);
    if (!token)
	self_destruct();
    s = cbdataAlloc(https_port_list);
    s->http.protocol = xstrdup("https");
    parse_http_port_specification(&s->http, token);
    /* parse options ... */
    while ((token = strtok(NULL, w_space))) {
	if (strncmp(token, "cert=", 5) == 0) {
	    safe_free(s->cert);
	    s->cert = xstrdup(token + 5);
	} else if (strncmp(token, "key=", 4) == 0) {
	    safe_free(s->key);
	    s->key = xstrdup(token + 4);
	} else if (strncmp(token, "version=", 8) == 0) {
	    s->version = xatoi(token + 8);
	    if (s->version < 1 || s->version > 4)
		self_destruct();
	} else if (strncmp(token, "options=", 8) == 0) {
	    safe_free(s->options);
	    s->options = xstrdup(token + 8);
	} else if (strncmp(token, "cipher=", 7) == 0) {
	    safe_free(s->cipher);
	    s->cipher = xstrdup(token + 7);
	} else if (strncmp(token, "clientca=", 9) == 0) {
	    safe_free(s->clientca);
	    s->clientca = xstrdup(token + 9);
	} else if (strncmp(token, "cafile=", 7) == 0) {
	    safe_free(s->cafile);
	    s->cafile = xstrdup(token + 7);
	} else if (strncmp(token, "capath=", 7) == 0) {
	    safe_free(s->capath);
	    s->capath = xstrdup(token + 7);
	} else if (strncmp(token, "crlfile=", 8) == 0) {
	    safe_free(s->crlfile);
	    s->crlfile = xstrdup(token + 8);
	} else if (strncmp(token, "dhparams=", 9) == 0) {
	    safe_free(s->dhfile);
	    s->dhfile = xstrdup(token + 9);
	} else if (strncmp(token, "sslflags=", 9) == 0) {
	    safe_free(s->sslflags);
	    s->sslflags = xstrdup(token + 9);
	} else if (strncmp(token, "sslcontext=", 11) == 0) {
	    safe_free(s->sslcontext);
	    s->sslcontext = xstrdup(token + 11);
	} else {
	    parse_http_port_option(&s->http, token);
	}
    }
    verify_http_port_options(&s->http);
    while (*head)
	head = (https_port_list **) (void *) (&(*head)->http.next);
    s->sslContext = sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslcontext);
#if WE_DONT_CARE_ABOUT_THIS_ERROR
    if (!s->sslContext)
	self_destruct();
#endif
    *head = s;
}

static void
dump_https_port_list(StoreEntry * e, const char *n, const https_port_list * s)
{
    while (s) {
	dump_generic_http_port(e, n, &s->http);
	if (s->cert)
	    storeAppendPrintf(e, " cert=%s", s->cert);
	if (s->key)
	    storeAppendPrintf(e, " key=%s", s->key);
	if (s->version)
	    storeAppendPrintf(e, " version=%d", s->version);
	if (s->options)
	    storeAppendPrintf(e, " options=%s", s->options);
	if (s->cipher)
	    storeAppendPrintf(e, " cipher=%s", s->cipher);
	if (s->cafile)
	    storeAppendPrintf(e, " cafile=%s", s->cafile);
	if (s->capath)
	    storeAppendPrintf(e, " capath=%s", s->capath);
	if (s->crlfile)
	    storeAppendPrintf(e, " crlfile=%s", s->crlfile);
	if (s->dhfile)
	    storeAppendPrintf(e, " dhparams=%s", s->dhfile);
	if (s->sslflags)
	    storeAppendPrintf(e, " sslflags=%s", s->sslflags);
	storeAppendPrintf(e, "\n");
	s = (https_port_list *) s->http.next;
    }
}

static void
free_https_port_list(https_port_list ** head)
{
    https_port_list *s;
    while ((s = *head) != NULL) {
	*head = (https_port_list *) s->http.next;
	cbdataFree(s);
    }
}

#if 0
static int
check_null_https_port_list(const https_port_list * s)
{
    return NULL == s;
}
#endif

#endif /* USE_SSL */

void
configFreeMemory(void)
{
    free_all();
#if USE_SSL
    if (Config.ssl_client.sslContext)
	SSL_CTX_free(Config.ssl_client.sslContext);
    Config.ssl_client.sslContext = NULL;
#endif
}

void
requirePathnameExists(const char *name, const char *path)
{
    struct stat sb;
    char pathbuf[BUFSIZ];
    assert(path != NULL);
    if (Config.chroot_dir && (geteuid() == 0)) {
	snprintf(pathbuf, BUFSIZ, "%s/%s", Config.chroot_dir, path);
	path = pathbuf;
    }
    if (stat(path, &sb) < 0) {
	if ((opt_send_signal == -1 || opt_send_signal == SIGHUP) && !opt_parse_cfg_only)
	    fatalf("%s %s: %s", name, path, xstrerror());
	else
	    fprintf(stderr, "WARNING: %s %s: %s\n", name, path, xstrerror());
    }
}

char *
strtokFile(void)
{
    static int fromFile = 0;
    static FILE *wordFile = NULL;

    char *t, *fn;
    LOCAL_ARRAY(char, buf, 256);

  strtok_again:
    if (!fromFile) {
	t = (strtok(NULL, w_space));
	if (!t || *t == '#') {
	    return NULL;
	} else if (*t == '\"' || *t == '\'') {
	    /* quote found, start reading from file */
	    fn = ++t;
	    while (*t && *t != '\"' && *t != '\'')
		t++;
	    *t = '\0';
	    if ((wordFile = fopen(fn, "r")) == NULL) {
		debug(28, 0) ("strtokFile: %s not found\n", fn);
		return (NULL);
	    }
#ifdef _SQUID_WIN32_
	    setmode(fileno(wordFile), O_TEXT);
#endif
	    fromFile = 1;
	} else {
	    return t;
	}
    }
    /* fromFile */
    if (fgets(buf, 256, wordFile) == NULL) {
	/* stop reading from file */
	fclose(wordFile);
	wordFile = NULL;
	fromFile = 0;
	goto strtok_again;
    } else {
	char *t2, *t3;
	t = buf;
	/* skip leading and trailing white space */
	t += strspn(buf, w_space);
	t2 = t + strcspn(t, w_space);
	t3 = t2 + strspn(t2, w_space);
	while (*t3 && *t3 != '#') {
	    t2 = t3 + strcspn(t3, w_space);
	    t3 = t2 + strspn(t2, w_space);
	}
	*t2 = '\0';
	/* skip comments */
	if (*t == '#')
	    goto strtok_again;
	/* skip blank lines */
	if (!*t)
	    goto strtok_again;
	return t;
    }
}

static void
parse_logformat(logformat ** logformat_definitions)
{
    logformat *nlf;
    char *name, *def;

    if ((name = strtok(NULL, w_space)) == NULL)
	self_destruct();
    if ((def = strtok(NULL, "\r\n")) == NULL)
	self_destruct();

    debug(3, 1) ("Logformat for '%s' is '%s'\n", name, def);

    nlf = xcalloc(1, sizeof(logformat));
    nlf->name = xstrdup(name);
    if (!accessLogParseLogFormat(&nlf->format, def))
	self_destruct();
    nlf->next = *logformat_definitions;
    *logformat_definitions = nlf;
}

static void
parse_access_log(customlog ** logs)
{
    const char *filename, *logdef_name;
    customlog *cl;
    logformat *lf;

    cl = xcalloc(1, sizeof(*cl));

    if ((filename = strtok(NULL, w_space)) == NULL)
	self_destruct();

    if (strcmp(filename, "none") == 0) {
	cl->type = CLF_NONE;
	goto done;
    }
    if ((logdef_name = strtok(NULL, w_space)) == NULL)
	logdef_name = "auto";

    debug(3, 9) ("Log definition name '%s' file '%s'\n", logdef_name, filename);

    cl->filename = xstrdup(filename);

    /* look for the definition pointer corresponding to this name */
    lf = Config.Log.logformats;
    while (lf != NULL) {
	debug(3, 9) ("Comparing against '%s'\n", lf->name);
	if (strcmp(lf->name, logdef_name) == 0)
	    break;
	lf = lf->next;
    }
    if (lf != NULL) {
	cl->type = CLF_CUSTOM;
	cl->logFormat = lf;
    } else if (strcmp(logdef_name, "auto") == 0) {
	cl->type = CLF_AUTO;
    } else if (strcmp(logdef_name, "squid") == 0) {
	cl->type = CLF_SQUID;
    } else if (strcmp(logdef_name, "common") == 0) {
	cl->type = CLF_COMMON;
    } else {
	debug(3, 0) ("Log format '%s' is not defined\n", logdef_name);
	self_destruct();
    }

  done:
    aclParseAclList(&cl->aclList);

    while (*logs)
	logs = &(*logs)->next;
    *logs = cl;
}

static void
dump_logformat(StoreEntry * entry, const char *name, logformat * definitions)
{
    accessLogDumpLogFormat(entry, name, definitions);
}

static void
dump_access_log(StoreEntry * entry, const char *name, customlog * logs)
{
    customlog *log;
    for (log = logs; log; log = log->next) {
	storeAppendPrintf(entry, "%s ", name);
	switch (log->type) {
	case CLF_CUSTOM:
	    storeAppendPrintf(entry, "%s %s", log->filename, log->logFormat->name);
	    break;
	case CLF_NONE:
	    storeAppendPrintf(entry, "none");
	    break;
	case CLF_SQUID:
	    storeAppendPrintf(entry, "%s squid", log->filename);
	    break;
	case CLF_COMMON:
	    storeAppendPrintf(entry, "%s squid", log->filename);
	    break;
	case CLF_AUTO:
	    if (log->aclList)
		storeAppendPrintf(entry, "%s auto", log->filename);
	    else
		storeAppendPrintf(entry, "%s", log->filename);
	    break;
	case CLF_UNKNOWN:
	    break;
	}
	if (log->aclList)
	    dump_acl_list(entry, log->aclList);
	storeAppendPrintf(entry, "\n");
    }
}

static void
free_logformat(logformat ** definitions)
{
    while (*definitions) {
	logformat *format = *definitions;
	*definitions = format->next;
	accessLogFreeLogFormat(&format->format);
	xfree(format);
    }
}

static void
free_access_log(customlog ** definitions)
{
    while (*definitions) {
	customlog *log = *definitions;
	*definitions = log->next;

	log->logFormat = NULL;
	log->type = CLF_UNKNOWN;
	if (log->aclList)
	    aclDestroyAclList(&log->aclList);
	safe_free(log->filename);
	xfree(log);
    }
}

static void
parse_programline(wordlist ** line)
{
    if (*line)
	self_destruct();
    parse_wordlist(line);
}

static void
free_programline(wordlist ** line)
{
    free_wordlist(line);
}

static void
dump_programline(StoreEntry * entry, const char *name, const wordlist * line)
{
    dump_wordlist(entry, name, line);
}