Blob Blame History Raw
/*
 * Soft:        Keepalived is a failover program for the LVS project
 *              <www.linuxvirtualserver.org>. It monitor & manipulate
 *              a loadbalanced server pool using multi-layer checks.
 *
 * Part:        Configuration file parser/reader. Place into the dynamic
 *              data structure representation the conf file representing
 *              the loadbalanced server pool.
 *
 * Author:      Alexandre Cassen, <acassen@linux-vs.org>
 *
 *              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.
 *
 *              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.
 *
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
 */

#include "config.h"

#include <glob.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
#include <linux/version.h>
#include <pwd.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <inttypes.h>

#include "parser.h"
#include "memory.h"
#include "logger.h"
#include "list_head.h"
#include "rttables.h"
#include "scheduler.h"
#include "notify.h"
#include "bitops.h"
#include "utils.h"
#include "process.h"


#define DEF_LINE_END	"\n"

#define BOB "{"
#define EOB "}"
#define WHITE_SPACE_STR " \t\f\n\r\v"

typedef struct _defs {
	const char *name;
	size_t name_len;
	const char *value;
	size_t value_len;
	bool multiline;
	const char *(*fn)(const struct _defs *);
	unsigned max_params;
	const char *params;
	const char *params_end;

	/* Linked list member */
	list_head_t e_list;
} def_t;

typedef struct _multiline_stack_ent {
	const char *ptr;
	size_t seq_depth;

	/* Linked list member */
	list_head_t e_list;
} multiline_stack_ent;

/* Structures used for ~LST */
typedef struct param {
	const char	*name;
	list_head_t	e_list;
} param_t;

typedef struct value {
	const char	*val;
	list_head_t	e_list;
} value_t;

typedef struct value_set {
	list_head_t	values;		/* value_t */
	list_head_t	e_list;
} value_set_t;

/* Structure for ~SEQ or ~LST */
typedef struct _seq {
	const char *var;
	long next;
	value_set_t *next_var;
	long last;
	long step;
	bool hex;
	const char *text;
	list_head_t lst_params;		/* param_t */
	list_head_t lst_values;		/* value_set_t */

	/* Linked list member */
	list_head_t e_list;
} seq_t;

/* Structure for include file stack */
typedef struct _include_file {
	glob_t globbuf;
	unsigned glob_next;
	const char *file_name;
	int curdir_fd;
	FILE *stream;
	unsigned num_matches;
	const char *current_file_name;  //can be derived from globbuf_gl_pathv[glob_next-1]
	size_t current_line_no;

	list_head_t e_list;
} include_file_t;


/* global vars */
vector_t *keywords;
const char *config_id;
const char *WHITE_SPACE = WHITE_SPACE_STR;
#ifdef _PARSER_DEBUG_
bool do_parser_debug;
#endif
#ifdef _DUMP_KEYWORDS_
bool do_dump_keywords;
#endif

/* local vars */
static vector_t *current_keywords;
static int sublevel = 0;
static int skip_sublevel = 0;
static LIST_HEAD_INITIALIZE(multiline_stack); /* multiline_stack_ent */
static size_t multiline_seq_depth = 0;
static char *buf_extern;
static config_err_t config_err = CONFIG_OK; /* Highest level of config error for --config-test */
static unsigned int random_seed;
static bool random_seed_configured;
static LIST_HEAD_INITIALIZE(seq_list);	/* seq_t */
static unsigned seq_list_count = 0;

/* recursive configuration stream handler */
static int kw_level;
static int block_depth;

/* Parameter definitions */
static LIST_HEAD_INITIALIZE(defs); /* def_t */

/* Forward declarations for recursion */
static bool replace_param(char *, size_t, char const **);

/* Stack of include files */
LIST_HEAD_INITIALIZE(include_stack);


void
report_config_error(config_err_t err, const char *format, ...)
{
	va_list args;
	char *format_buf = NULL;
	include_file_t *file = NULL;
	
	if (!list_empty(&include_stack))
		file = list_first_entry(&include_stack, include_file_t, e_list);

	/* current_file_name will be set if there is more than one config file, in which
	 * case we need to specify the file name. */
	if (file) {
	       	if (file->current_file_name) {
			/* "(file_name: Line line_no) format" + '\0' */
			format_buf = MALLOC(1 + strlen(file->current_file_name) + 1 + 6 + 10 + 1 + 1 + strlen(format) + 1);
			sprintf(format_buf, "(%s: Line %zu) %s", file->current_file_name, file->current_line_no, format);
		} else if (file->current_line_no) {	/* Set while reading from config files */
			/* "(Line line_no) format" + '\0' */
			format_buf = MALLOC(1 + 5 + 10 + 1 + 1 + strlen(format) + 1);
			sprintf(format_buf, "(%s %zu) %s", "Line", file->current_line_no, format);
		}
	}

	va_start(args, format);

	if (__test_bit(CONFIG_TEST_BIT, &debug)) {
		vfprintf(stderr, format_buf ? format_buf : format, args);
		fputc('\n', stderr);

		if (config_err == CONFIG_OK || config_err < err)
			config_err = err;
	}
	else
		vlog_message(LOG_INFO, format_buf ? format_buf : format, args);

	va_end(args);

	if (format_buf)
		FREE(format_buf);
}

config_err_t __attribute__ ((pure))
get_config_status(void)
{
	return config_err;
}

static void __attribute__ ((noreturn))
null_strvec(const vector_t *strvec, size_t index)
{
	if (index > 0 && index - 1 < vector_size(strvec) && vector_slot(strvec, index - 1))
		report_config_error(CONFIG_MISSING_PARAMETER, "*** Configuration line starting `%s` is missing a parameter after keyword `%s` at word position %zu", vector_slot(strvec, 0) ? (char *)vector_slot(strvec, 0) : "***MISSING ***", (char *)vector_slot(strvec, index - 1), index + 1);
	else
		report_config_error(CONFIG_MISSING_PARAMETER, "*** Configuration line starting `%s` is missing a parameter at word position %zu", vector_slot(strvec, 0) ? (char *)vector_slot(strvec, 0) : "***MISSING ***", index + 1);

	exit(KEEPALIVED_EXIT_CONFIG);
}

static bool
read_int_func(const char *number, int base, int *res, int min_val, int max_val, __attribute__((unused)) bool ignore_error)
{
	long val;
	char *endptr;
	const char *warn = "";

#ifndef _STRICT_CONFIG_
	if (ignore_error && !__test_bit(CONFIG_TEST_BIT, &debug))
		warn = "WARNING - ";
#endif

	errno = 0;
	val = strtol(number, &endptr, base);
	*res = (int)val;

	if (*endptr)
		report_config_error(CONFIG_INVALID_NUMBER, "%sinvalid number '%s'", warn, number);
	else if (errno == ERANGE || val < INT_MIN || val > INT_MAX)
		report_config_error(CONFIG_INVALID_NUMBER, "%snumber '%s' outside integer range", warn, number);
	else if (val < min_val || val > max_val)
		report_config_error(CONFIG_INVALID_NUMBER, "number '%s' outside range [%d, %d]", number, min_val, max_val);
	else
		return true;

#ifdef _STRICT_CONFIG_
	return false;
#else
	return ignore_error && val >= min_val && val <= max_val && !__test_bit(CONFIG_TEST_BIT, &debug);
#endif
}

static bool
read_unsigned_func(const char *number, int base, unsigned *res, unsigned min_val, unsigned max_val, __attribute__((unused)) bool ignore_error)
{
	unsigned long val;
	char *endptr;
	const char *warn = "";
	size_t offset;

#ifndef _STRICT_CONFIG_
	if (ignore_error && !__test_bit(CONFIG_TEST_BIT, &debug))
		warn = "WARNING - ";
#endif

	/* In case the string starts with spaces (even in the configuration this
	 * can be achieved by enclosing the number in quotes - e.g. weight "  -100")
	 * skip any leading whitespace */
	offset = strspn(number, WHITE_SPACE);

	errno = 0;
	val = strtoul(number + offset, &endptr, base);
	*res = (unsigned)val;

	if (number[offset] == '-')
		report_config_error(CONFIG_INVALID_NUMBER, "%snegative number '%s'", warn, number);
	else if (*endptr)
		report_config_error(CONFIG_INVALID_NUMBER, "%sinvalid number '%s'", warn, number);
	else if (errno == ERANGE || val > UINT_MAX)
		report_config_error(CONFIG_INVALID_NUMBER, "%snumber '%s' outside unsigned integer range", warn, number);
	else if (val < min_val || val > max_val)
		report_config_error(CONFIG_INVALID_NUMBER, "%snumber '%s' outside range [%u, %u]", warn, number, min_val, max_val);
	else
		return true;

#ifdef _STRICT_CONFIG_
	return false;
#else
	return ignore_error && val >= min_val && val <= max_val && !__test_bit(CONFIG_TEST_BIT, &debug);
#endif
}

static bool
read_unsigned64_func(const char *number, int base, uint64_t *res, uint64_t min_val, uint64_t max_val, __attribute__((unused)) bool ignore_error)
{
	unsigned long long val;
	char *endptr;
	const char *warn = "";
	size_t offset;

#ifndef _STRICT_CONFIG_
	if (ignore_error && !__test_bit(CONFIG_TEST_BIT, &debug))
		warn = "WARNING - ";
#endif

	/* In case the string starts with spaces (even in the configuration this
	 * can be achieved by enclosing the number in quotes - e.g. weight "  -100")
	 * skip any leading whitespace */
	offset = strspn(number, WHITE_SPACE);

	errno = 0;
	val = strtoull(number + offset, &endptr, base);
	*res = (unsigned)val;

	if (number[offset] == '-')
		report_config_error(CONFIG_INVALID_NUMBER, "%snegative number '%s'", warn, number);
	else if (*endptr)
		report_config_error(CONFIG_INVALID_NUMBER, "%sinvalid number '%s'", warn, number);
	else if (errno == ERANGE)
		report_config_error(CONFIG_INVALID_NUMBER, "%snumber '%s' outside unsigned 64 bit range", warn, number);
	else if (val < min_val || val > max_val)
		report_config_error(CONFIG_INVALID_NUMBER, "number '%s' outside range [%" PRIu64 ", %" PRIu64 "]", number, min_val, max_val);
	else
		return true;

#ifdef _STRICT_CONFIG_
	return false;
#else
	return ignore_error && val >= min_val && val <= max_val && !__test_bit(CONFIG_TEST_BIT, &debug);
#endif
}

static bool
read_double_func(const char *number, double *res, double min_val, double max_val, __attribute__((unused)) bool ignore_error)
{
	double val;
	char *endptr;
	const char *warn = "";
	int ftype;

#ifndef _STRICT_CONFIG_
	if (ignore_error && !__test_bit(CONFIG_TEST_BIT, &debug))
		warn = "WARNING - ";
#endif

	errno = 0;
	val = strtod(number, &endptr);
	*res = val;

	if (*endptr)
		report_config_error(CONFIG_INVALID_NUMBER, "%sinvalid number '%s'", warn, number);
	else if (errno == ERANGE)
		report_config_error(CONFIG_INVALID_NUMBER, "%snumber '%s' out of range", warn, number);
	else {
		ftype = fpclassify(val);
		if (ftype == FP_INFINITE)	/* +/- Inf */
			report_config_error(CONFIG_INVALID_NUMBER, "infinite number '%s'", number);
		else if (ftype == FP_NAN)	/* NaN */
			report_config_error(CONFIG_INVALID_NUMBER, "not a number '%s'", number);
		else if (ftype == FP_SUBNORMAL)	{ /* to small */
			*res = 0.0F;
			return true;
		}
		else if (val < min_val || val > max_val)
			report_config_error(CONFIG_INVALID_NUMBER, "number '%s' outside range [%g, %g]", number, min_val, max_val);
		else /* FP_NORMAL or FP_ZERO */
			return true;
	}

#ifdef _STRICT_CONFIG_
	return false;
#else
	return ignore_error && val >= min_val && val <= max_val && !__test_bit(CONFIG_TEST_BIT, &debug);
#endif
}

bool
read_int(const char *str, int *res, int min_val, int max_val, bool ignore_error)
{
	return read_int_func(str, 10, res, min_val, max_val, ignore_error);
}

bool
read_unsigned(const char *str, unsigned *res, unsigned min_val, unsigned max_val, bool ignore_error)
{
	return read_unsigned_func(str, 10, res, min_val, max_val, ignore_error);
}

bool
read_unsigned64(const char *str, uint64_t *res, uint64_t min_val, uint64_t max_val, bool ignore_error)
{
	return read_unsigned64_func(str, 10, res, min_val, max_val, ignore_error);
}

bool
read_double(const char *str, double *res, double min_val, double max_val, bool ignore_error)
{
	return read_double_func(str, res, min_val, max_val, ignore_error);
}

bool
read_int_strvec(const vector_t *strvec, size_t index, int *res, int min_val, int max_val, bool ignore_error)
{
	return read_int_func(strvec_slot(strvec, index), 10, res, min_val, max_val, ignore_error);
}

bool
read_unsigned_strvec(const vector_t *strvec, size_t index, unsigned *res, unsigned min_val, unsigned max_val, bool ignore_error)
{
	return read_unsigned_func(strvec_slot(strvec, index), 10, res, min_val, max_val, ignore_error);
}

bool
read_unsigned64_strvec(const vector_t *strvec, size_t index, uint64_t *res, uint64_t min_val, uint64_t max_val, bool ignore_error)
{
	return read_unsigned64_func(strvec_slot(strvec, index), 10, res, min_val, max_val, ignore_error);
}

bool
read_double_strvec(const vector_t *strvec, size_t index, double *res, double min_val, double max_val, bool ignore_error)
{
	return read_double_func(strvec_slot(strvec, index), res, min_val, max_val, ignore_error);
}

bool
read_unsigned_base_strvec(const vector_t *strvec, size_t index, int base, unsigned *res, unsigned min_val, unsigned max_val, bool ignore_error)
{
	return read_unsigned_func(strvec_slot(strvec, index), base, res, min_val, max_val, ignore_error);
}

void
set_random_seed(unsigned int seed)
{
	random_seed = seed;
	random_seed_configured = true;
}

static void
keyword_alloc(vector_t *keywords_vec, const char *string, void (*handler) (const vector_t *), bool active)
{
	keyword_t *keyword;

	vector_alloc_slot(keywords_vec);

	keyword = (keyword_t *) MALLOC(sizeof(keyword_t));
	keyword->string = string;
	keyword->handler = handler;
	keyword->active = active;

	vector_set_slot(keywords_vec, keyword);
}

static void
keyword_alloc_sub(vector_t *keywords_vec, const char *string, void (*handler) (const vector_t *))
{
	int i = 0;
	keyword_t *keyword;

	/* fetch last keyword */
	keyword = vector_slot(keywords_vec, vector_size(keywords_vec) - 1);

	/* Don't install subordinate keywords if configuration block inactive */
	if (!keyword->active)
		return;

	/* position to last sub level */
	for (i = 0; i < sublevel; i++)
		keyword = vector_slot(keyword->sub, vector_size(keyword->sub) - 1);

	/* First sub level allocation */
	if (!keyword->sub)
		keyword->sub = vector_alloc();

	/* add new sub keyword */
	keyword_alloc(keyword->sub, string, handler, true);
}

/* Exported helpers */
void
install_sublevel(void)
{
	sublevel++;
}

void
install_sublevel_end(void)
{
	sublevel--;
}

void
install_keyword_root(const char *string, void (*handler) (const vector_t *), bool active)
{
	/* If the root keyword is inactive, the handler will still be called,
	 * but with a NULL strvec */
	keyword_alloc(keywords, string, handler, active);
}

void
install_root_end_handler(void (*handler) (void))
{
	keyword_t *keyword;

	/* fetch last keyword */
	keyword = vector_slot(keywords, vector_size(keywords) - 1);

	if (!keyword->active)
		return;

	keyword->sub_close_handler = handler;
}

void
install_keyword(const char *string, void (*handler) (const vector_t *))
{
	keyword_alloc_sub(keywords, string, handler);
}

void
install_sublevel_end_handler(void (*handler) (void))
{
	int i = 0;
	keyword_t *keyword;

	/* fetch last keyword */
	keyword = vector_slot(keywords, vector_size(keywords) - 1);

	if (!keyword->active)
		return;

	/* position to last sub level */
	for (i = 0; i < sublevel; i++)
		keyword = vector_slot(keyword->sub, vector_size(keyword->sub) - 1);
	keyword->sub_close_handler = handler;
}

#ifdef _DUMP_KEYWORDS_
static void
dump_keywords(vector_t *keydump, int level, FILE *fp)
{
	unsigned int i;
	keyword_t *keyword_vec;
	char file_name[1 + 3 + 1 + 8 + 1 + PID_MAX_DIGITS + 1];

	if (!level) {
		snprintf(file_name, sizeof(file_name), "/tmp/keywords.%d", getpid());
		fp = fopen_safe(file_name, "w");
		if (!fp)
			return;
	}

	for (i = 0; i < vector_size(keydump); i++) {
		keyword_vec = vector_slot(keydump, i);
		fprintf(fp, "%*sKeyword : %s (%s)\n", level * 2, "", keyword_vec->string, keyword_vec->active ? "active": "disabled");
		if (keyword_vec->sub)
			dump_keywords(keyword_vec->sub, level + 1, fp);
	}

	if (!level)
		fclose(fp);
}
#endif

static void
free_keywords(vector_t *keywords_vec)
{
	keyword_t *keyword_vec;
	unsigned int i;

	for (i = 0; i < vector_size(keywords_vec); i++) {
		keyword_vec = vector_slot(keywords_vec, i);
		if (keyword_vec->sub)
			free_keywords(keyword_vec->sub);
		FREE(keyword_vec);
	}
	vector_free(keywords_vec);
}

/* Functions used for standard definitions */
static const char *
get_cwd(__attribute__((unused))const def_t *def)
{
	char *dir = MALLOC(PATH_MAX);

	/* Since keepalived doesn't do a chroot(), we don't need to be concerned
	 * about (unreachable) - see getcwd(3) man page. */
	return getcwd(dir, PATH_MAX);
}

static const char * __attribute__((malloc))
get_instance(__attribute__((unused))const def_t *def)
{
	return STRDUP(config_id);
}

static const char *
get_random(const def_t *def)
{
	unsigned long min = 0;
	unsigned long max = 32767;
	long val;
	char *endp;
	char *rand_str;
	size_t rand_str_len = 0;

	/* We have already checked that the parameter string comprises
	 * only spaces and decimal digits */
	if (def->params) {
		min = strtoul(def->params, &endp, 10);
		if (endp < def->params_end) {
			max = strtoul(endp, &endp, 10);
			if (endp != def->params_end + 1)
				log_message(LOG_INFO, "Too many parameters or extra text for ${_RANDOM %.*s}", (int)(def->params_end - def->params + 1), def->params);
		}
	}

	val = max;
	do {
		rand_str_len++;
	} while (val /= 10);
	rand_str = MALLOC(rand_str_len + 1);

	/* coverity[dont_call] */
	val = random() % (max - min + 1) + min;
	snprintf(rand_str, rand_str_len + 1, "%ld", val);

	return rand_str;
}

const vector_t *
alloc_strvec_quoted_escaped(const char *src)
{
	vector_t *strvec;
	char cur_quote = 0;
	char *ofs_op;
	char *op_buf;
	const char *ofs, *ofs1;
	char op_char;

	if (!src) {
		if (!buf_extern)
			return NULL;
		src = buf_extern;
	}

	/* Create a vector and alloc each command piece */
	strvec = vector_alloc();
	op_buf = MALLOC(MAXBUF);

	ofs = src;
	while (*ofs) {
		/* Find the next 'word' */
		ofs += strspn(ofs, WHITE_SPACE);
		if (!*ofs)
			break;

		ofs_op = op_buf;

		while (*ofs) {
			ofs1 = strpbrk(ofs, cur_quote == '"' ? "\"\\" : cur_quote == '\'' ? "'\\" : WHITE_SPACE_STR "'\"\\");

			if (!ofs1) {
				size_t len;
				if (cur_quote) {
					report_config_error(CONFIG_UNMATCHED_QUOTE, "String '%s': missing terminating %c", src, cur_quote);
					goto err_exit;
				}
				strcpy(ofs_op, ofs);
				len =  strlen(ofs);
				ofs += len;
				ofs_op += len;
				break;
			}

			/* Save the wanted text */
			strncpy(ofs_op, ofs, ofs1 - ofs);
			ofs_op += ofs1 - ofs;
			ofs = ofs1;

			if (*ofs == '\\') {
				/* It is a '\' */
				ofs++;

				if (!*ofs) {
					log_message(LOG_INFO, "Missing escape char at end: '%s'", src);
					goto err_exit;
				}

				if (*ofs == 'x' && isxdigit(ofs[1])) {
					op_char = 0;
					ofs++;
					while (isxdigit(*ofs)) {
						op_char <<= 4;
						op_char |= isdigit(*ofs) ? *ofs - '0' : (10 + *ofs - (isupper(*ofs)  ? 'A' : 'a'));
						ofs++;
					}
				}
				else if (*ofs == 'c' && ofs[1]) {
					op_char = *++ofs & 0x1f;	/* Convert to control character */
					ofs++;
				}
				else if (*ofs >= '0' && *ofs <= '7') {
					op_char = *ofs++ - '0';
					if (*ofs >= '0' && *ofs <= '7') {
						op_char <<= 3;
						op_char += *ofs++ - '0';
					}
					if (*ofs >= '0' && *ofs <= '7') {
						op_char <<= 3;
						op_char += *ofs++ - '0';
					}
				}
				else {
					switch (*ofs) {
					case 'a':
						op_char = '\a';
						break;
					case 'b':
						op_char = '\b';
						break;
					case 'E':
						op_char = 0x1b;
						break;
					case 'f':
						op_char = '\f';
						break;
					case 'n':
						op_char = '\n';
						break;
					case 'r':
						op_char = '\r';
						break;
					case 't':
						op_char = '\t';
						break;
					case 'v':
						op_char = '\v';
						break;
					default: /* \"'  */
						op_char = *ofs;
						break;
					}
					ofs++;
				}

				*ofs_op++ = op_char;
				continue;
			}

			if (cur_quote) {
				/* It's the close quote */
				ofs++;
				cur_quote = 0;
				continue;
			}

			if (*ofs == '"' || *ofs == '\'') {
				cur_quote = *ofs++;
				continue;
			}

			break;
		}

		/* Alloc & set the slot */
		vector_alloc_slot(strvec);
		vector_set_slot(strvec, STRNDUP(op_buf, ofs_op - op_buf));
	}

	FREE(op_buf);

	if (!vector_size(strvec)) {
		free_strvec(strvec);
		return NULL;
	}

	return strvec;

err_exit:
	free_strvec(strvec);
	FREE(op_buf);
	return NULL;
}

vector_t *
alloc_strvec_r(const char *string)
{
	const char *cp, *start;
	size_t str_len;
	vector_t *strvec;

	if (!string)
		return NULL;

	/* Create a vector and alloc each command piece */
	strvec = vector_alloc();

	cp = string;
	while (true) {
		cp += strspn(cp, WHITE_SPACE);
		if (!*cp)
			break;

		start = cp;

		/* Save a quoted string without the ""s as a single string */
		if (*start == '"') {
			start++;
			if (!(cp = strchr(start, '"'))) {
				report_config_error(CONFIG_UNMATCHED_QUOTE, "Unmatched quote: '%s'", string);
				break;
			}
			str_len = (size_t)(cp - start);
			cp++;
		} else {
			cp += strcspn(start, WHITE_SPACE_STR "\"");
			str_len = (size_t)(cp - start);
		}

		/* Alloc & set the slot */
		vector_alloc_slot(strvec);
		vector_set_slot(strvec, STRNDUP(start, str_len));
	}

	if (!vector_size(strvec)) {
		free_strvec(strvec);
		return NULL;
	}

	return strvec;
}

#ifdef _PARSER_DEBUG_
static void
dump_seq_lst(const seq_t *seq)
{
	param_t *param;
	value_set_t *value_set;
	value_t *value;
	char *buf = MALLOC(1024);
	char *p;

	/* List the parameters */
	p = buf;
	list_for_each_entry(param, &seq->lst_params, e_list)
		p += snprintf(p, buf + 1024 - p, "%s%s", p == buf ? "" : ", ", param->name);
	log_message(LOG_INFO, "LST parameters: %s", buf);

	/* List the values */
	list_for_each_entry(value_set, &seq->lst_values, e_list) {
		/* List the values in the value set */
		buf[0] = '\0';
		p = buf;
		list_for_each_entry(value, &value_set->values, e_list)
			p += snprintf(p, buf + 1024 - p, "%s%s", p == buf ? "" : ", ", value->val);
		log_message(LOG_INFO, "    values:     %s", buf);
	}

	FREE(buf);
}

static void
dump_seqs(void)
{
	seq_t *seq;

	list_for_each_entry(seq, &seq_list, e_list) {
		if (!list_empty(&seq->lst_params)) {
			dump_seq_lst(seq);
		} else if (seq->hex)
			log_message(LOG_INFO, "SEQ: %s => 0x%lx -> 0x%lx step %ld: '%s'", seq->var, (unsigned long)seq->next, (unsigned long)seq->last, seq->step, seq->text);
		else
			log_message(LOG_INFO, "SEQ: %s => %ld -> %ld step %ld: '%s'", seq->var, seq->next, seq->last, seq->step, seq->text);
	}
	log_message(LOG_INFO, "%s", "");
}
#endif

static void
free_seq(seq_t *seq)
{
	list_del_init(&seq->e_list);
	FREE_CONST(seq->var);
	FREE_CONST(seq->text);
	FREE(seq);
	seq_list_count--;
}

static void
free_seq_lst(seq_t *seq)
{
	param_t *param, *param_tmp;
	value_set_t *value_set, *value_set_tmp;
	value_t *value, *value_tmp;

	list_del_init(&seq->e_list);

	/* Free the parameters */
	list_for_each_entry_safe(param, param_tmp, &seq->lst_params, e_list) {
		list_del_init(&param->e_list);
		FREE_CONST(param->name);
		FREE(param);
	}

	/* Free the values */
	list_for_each_entry_safe(value_set, value_set_tmp, &seq->lst_values, e_list) {
		/* Free the values in a value set */
		list_for_each_entry_safe(value, value_tmp, &value_set->values, e_list) {
			list_del_init(&value->e_list);
			FREE_CONST(value->val);
			FREE(value);
		}
		list_del_init(&value_set->e_list);
		FREE(value_set);
	}

	FREE_CONST(seq->text);
	FREE(seq);
	seq_list_count--;
}

static void
free_seq_list(list_head_t *l)
{
	seq_t *seq, *seq_tmp;

	list_for_each_entry_safe(seq, seq_tmp, l, e_list) {
		if (list_empty(&seq->lst_params))
			free_seq(seq);
		else
			free_seq_lst(seq);
	}
}

static bool
add_seq(char *buf)
{
	char *p = buf + 4;	/* Skip ~SEQ */
	bool hex;
	long one, two, three;
	long start, step, end;
	seq_t *seq_ent;
	const char *var;
	const char *var_end;
	const char *multiline = NULL;
	char seq_buf[3 * 20 + 3 + 1]; /* 3 longs, each with , or ) after plus terminating nul */
	char *end_seq;

	/* Do we want the output in hex format - e.g. for IPv6 addresses */
	if (*p == 'x') {
		p++;
		hex = true;
	} else
		hex = false;

	p += strspn(p, " \t");
	if (*p++ != '(')
		return false;
	p += strspn(p, " \t");

	var = p;

	p += strcspn(p, " \t,)");
	var_end = p;
	p += strspn(p, " \t");
	if (!*p || *p == ')' || p == var) {
		report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ definition '%s'", buf);
		return false;
	}

	/* Convert any parameters of ~SEQ which are definitions */
	p++;
	p += strspn(p, " \t");
	end_seq = strchr(p, ')');
	if ((size_t)(end_seq + 1 - p + 1) > sizeof(seq_buf)) {
		report_config_error(CONFIG_GENERAL_ERROR, "~SEQ parameter strings too long '%s'", buf);
		return false;
	}
	strncpy(seq_buf, p, end_seq + 1 - p);
	seq_buf[end_seq + 1 - p] = '\0';
	replace_param(seq_buf, sizeof(seq_buf), &multiline);
	if (multiline) {
		report_config_error(CONFIG_GENERAL_ERROR, "~SEQ parameter is multiline definition '%s'", buf);
		return false;
	}

	p = seq_buf;
	do {
		// Handle missing number
		one = strtol(p, &p, 0);
		p += strspn(p, " \t");
		if (*p == ')') {
			end = one;
			step = (end < 1) ? -1 : 1;
			start = (end < 0) ? -1 : 1;

			break;
		}

		if (*p != ',') {
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ definition '%s'", buf);
			return false;
		}

		two = strtol(p + 1, &p, 0);
		p += strspn(p, " \t");
		if (*p == ')') {
			start = one;
			end = two;
			step = start <= end ? 1 : -1;

			break;
		}

		if (*p != ',') {
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ definition '%s'", buf);
			return false;
		}

		three = strtol(p + 1, &p, 0);
		p += strspn(p, " \t");
		if (*p != ')') {
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ definition '%s'", buf);
			return false;
		}

		start = one;
		step = two;
		end = three;

		if (!step ||
		    (start < end && step < 0) ||
		    (start > end && step > 0))
		{
			report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ values '%s'", buf);
			return false;
		}
	} while (false);

	if (hex && (start < 0 || end < 0)) {
		report_config_error(CONFIG_GENERAL_ERROR, "~SEQx is only valid for positive numbers '%s'", buf);
		return false;
	}

	p = end_seq;
	p += strspn(p + 1, " \t") + 1;

	PMALLOC(seq_ent);
	INIT_LIST_HEAD(&seq_ent->e_list);
	INIT_LIST_HEAD(&seq_ent->lst_params);
	INIT_LIST_HEAD(&seq_ent->lst_values);
	seq_ent->var = STRNDUP(var, var_end - var);
	seq_ent->next = start;
	seq_ent->step = step;
	seq_ent->last = end;
	seq_ent->hex = hex;
	seq_ent->text = STRDUP(p);

	list_add_tail(&seq_ent->e_list, &seq_list);
	seq_list_count++;

	return true;
}

static bool
add_lst(char *buf)
{
	char *p = buf + 4;	/* Skip ~LST */
	seq_t *seq_ent;
	const char *var;
	const char *var_end;
	param_t *param;
	value_set_t *value_set;
	value_t *value;
	unsigned num_vars = 0;
	unsigned num_values;
	char end_char;

	PMALLOC(seq_ent);
	INIT_LIST_HEAD(&seq_ent->e_list);
	INIT_LIST_HEAD(&seq_ent->lst_params);
	INIT_LIST_HEAD(&seq_ent->lst_values);

	p += strspn(p, " \t");
	if (*p++ != '(') {
		free_seq_lst(seq_ent);
		return false;
	}

	p += strspn(p, " \t");

	if (*p == '{') {
		end_char = '}';
		p++;
		p += strspn(p, " \t");
	} else
		end_char = ',';

	while (true) {
		var = p;
		var_end = p += strcspn(p, " \t,}");
		PMALLOC(param);
		INIT_LIST_HEAD(&param->e_list);

		param->name = STRNDUP(var, var_end - var);
		list_add_tail(&param->e_list, &seq_ent->lst_params);

		p += strspn(p, " \t");
		if (*p == end_char)
			break;
		if (*p != ',') {
			free_seq_lst(seq_ent);
			return false;
		}
		p += strspn(p + 1, " \t") + 1;
		num_vars++;
	}
	if (*p == '}')
		p += strspn(p + 1, " \t") + 1;
	if (*p++ != ',') {
		free_seq_lst(seq_ent);
		return false;
	}

	/* Read the values */
	p += strspn(p, " \t");

	while (true) {
		PMALLOC(value_set);
		INIT_LIST_HEAD(&value_set->e_list);
		INIT_LIST_HEAD(&value_set->values);

		if (*p == '{') {
			end_char = '}';
			p++;
			p += strspn(p, " \t");
		} else
			end_char = ',';

		/* Read one set of values */
		num_values = 0;
		while (true) {
			var = p;
			var_end = p += strcspn(p, " \t,})");
			PMALLOC(value);
			INIT_LIST_HEAD(&value->e_list);

			value->val = STRNDUP(var, var_end - var);
			list_add_tail(&value->e_list, &value_set->values);

			p += strspn(p, " \t");
			if (*p == end_char || (*p == ')' && end_char == ','))
				break;
			if (*p != ',') {
				free_seq_lst(seq_ent);
				return false;
			}
			p += strspn(p + 1, " \t") + 1;

			if (++num_values > num_vars) {
				report_config_error(CONFIG_GENERAL_ERROR, "~LST specification has too many values '%s'", buf);
				free_seq_lst(seq_ent);
				return false;
			}
		}

		/* Any missing parameters are blank */
		for (; num_values < num_vars; num_values++) {
			PMALLOC(value);
			value->val = STRDUP("");
			INIT_LIST_HEAD(&value->e_list);
		}

		/* Add the value_set to the list of value_sets */
		list_add_tail(&value_set->e_list, &seq_ent->lst_values);

		if (*p == '}' && end_char == '}')
			p += strspn(p + 1, " \t") + 1;
		if (*p == ')')
			break;
		if (*p != ',') {
			free_seq_lst(seq_ent);
			return false;
		}

		p += strspn(p + 1, " \t") + 1;
	}

	if (list_empty(&seq_ent->lst_params) || list_empty(&seq_ent->lst_values)) {
		free_seq_lst(seq_ent);
		return false;
	}

	p += strspn(p + 1, " \t") + 1;
	seq_ent->next_var = list_first_entry(&seq_ent->lst_values, value_set_t, e_list);
	seq_ent->text = STRDUP(p);
	list_add_tail(&seq_ent->e_list, &seq_list);
	seq_list_count++;

	return true;
}

#ifdef _PARSER_DEBUG_
static void
dump_definitions(void)
{
	def_t *def;

	list_for_each_entry(def, &defs, e_list)
		log_message(LOG_INFO, "Defn %s = '%s'", def->name, def->value);
	log_message(LOG_INFO, "%s", "");
}
#endif

bool
check_conf_file(const char *conf_file)
{
	glob_t globbuf;
	size_t i;
	bool ret = true;
	int res;
	struct stat stb;
	unsigned num_matches = 0;

	globbuf.gl_offs = 0;
	res = glob(conf_file, GLOB_MARK
#if HAVE_DECL_GLOB_BRACE
					| GLOB_BRACE
#endif
						    , NULL, &globbuf);
	if (res) {
		report_config_error(CONFIG_FILE_NOT_FOUND, "Unable to find configuration file %s (glob returned %d)", conf_file, res);
		return false;
	}

	for (i = 0; i < globbuf.gl_pathc; i++) {
		if (globbuf.gl_pathv[i][strlen(globbuf.gl_pathv[i])-1] == '/') {
			/* This is a directory - so skip */
			continue;
		}

		if (access(globbuf.gl_pathv[i], R_OK)) {
			log_message(LOG_INFO, "Unable to read configuration file %s", globbuf.gl_pathv[i]);
			ret = false;
			break;
		}

		/* Make sure that the file is a regular file, and not for example a directory or executable */
		if (stat(globbuf.gl_pathv[i], &stb) ||
		    !S_ISREG(stb.st_mode) ||
		     (stb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
			log_message(LOG_INFO, "Configuration file '%s' is not a regular non-executable file", globbuf.gl_pathv[i]);
			ret = false;
			break;
		}

		num_matches++;
	}

	if (ret) {
		if (num_matches > 1)
			report_config_error(CONFIG_MULTIPLE_FILES, "WARNING, more than one file matches configuration file %s, using %s", conf_file, globbuf.gl_pathv[0]);
		else if (num_matches == 0) {
			report_config_error(CONFIG_FILE_NOT_FOUND, "Unable to find configuration file %s", conf_file);
			ret = false;
		}
	}

	globfree(&globbuf);

	return ret;
}

static def_t * __attribute__ ((pure))
find_definition(const char *name, size_t len, bool definition)
{
	def_t *def;
	const char *p;
	bool using_braces = false;
	bool allow_multiline;
	const char *param_start = NULL;
	const char *param_end = NULL;

	if (list_empty(&defs))
		return NULL;

	if (!definition && *name == BOB[0]) {
		using_braces = true;
		name++;
	}

	if (!isalpha(*name) && *name != '_')
		return NULL;

	if (!len) {
		for (len = 1, p = name + 1; *p != '\0' && (isalnum(*p) || *p == '_'); len++, p++);

		/* Check we have a suitable end character */
		if (using_braces) {
			if (!definition) {
				/* Allow for parameters to the definition */
				while (*p && (*p == ' ' || isdigit (*p))) {
					if (*p != ' ') {
					       if (!param_start)
						       param_start = p;
					       param_end = p;
					}
					p++;
				}
				/* Ensure don't end with a space */
				if (param_start && param_end + 1 != p)
					return NULL;
			}
			if (*p != EOB[0])
				return NULL;
		} else if (!definition && *p != ' ' && *p != '\t' && *p != ',' && *p != ')' && *p != '\0')
			return NULL;
	}

	if (definition ||
	    (!using_braces && name[len] == '\0') ||
	    (using_braces && name[len+1] == '\0'))
		allow_multiline = true;
	else
		allow_multiline = false;

	list_for_each_entry(def, &defs, e_list) {
		if (def->name_len == len &&
		    (allow_multiline || !def->multiline) &&
		    !strncmp(def->name, name, len)) {
			if (param_start && !def->max_params)
				return NULL;
			if (param_start) {
				def->params = param_start;
				def->params_end = param_end;
			}
			else
				def->params = NULL;
			return def;
		}
	}

	return NULL;
}

static void
free_multiline_stack_list(list_head_t *l)
{
	multiline_stack_ent *stack, *stack_tmp;

	list_for_each_entry_safe(stack, stack_tmp, l, e_list) {
		list_del_init(&stack->e_list);
		FREE(stack);
	}
}

static void
multiline_stack_push(const char *ptr)
{
	multiline_stack_ent *stack_ent;

	PMALLOC(stack_ent);
	INIT_LIST_HEAD(&stack_ent->e_list);
	stack_ent->ptr = ptr;
	stack_ent->seq_depth = multiline_seq_depth;

	list_add_tail(&stack_ent->e_list, &multiline_stack);
}

static const char *
multiline_stack_pop(void)
{
	multiline_stack_ent *stack_ent;
	const char *next_ptr;

	if (list_empty(&multiline_stack))
		return NULL;

	stack_ent = list_last_entry(&multiline_stack, multiline_stack_ent, e_list);
	next_ptr = stack_ent->ptr;
	multiline_seq_depth = stack_ent->seq_depth;

	list_del_init(&stack_ent->e_list);
	FREE(stack_ent);

	return next_ptr;
}

static bool
replace_param(char *buf, size_t max_len, char const **multiline_ptr_ptr)
{
	char *cur_pos = buf;
	size_t len_used = strlen(buf);
	def_t *def;
	char *s, *d;
	const char *e;
	ssize_t i;
	size_t extra_braces;
	size_t replacing_len;
	size_t replaced_len;
	const char *next_ptr = NULL;
	bool found_defn = false;
	const char *multiline_ptr = *multiline_ptr_ptr;

	while ((cur_pos = strchr(cur_pos, '$')) && cur_pos[1] != '\0') {
		if ((def = find_definition(cur_pos + 1, 0, false))) {
			found_defn = true;
			extra_braces = cur_pos[1] == BOB[0] ? 2 : 0;
			next_ptr = multiline_ptr;

			/* We are in a multiline expansion, and now have another
			 * one, so save the previous state on the multiline stack */
			if (def->multiline && multiline_ptr)
				multiline_stack_push(multiline_ptr);

			if (def->multiline)
				multiline_seq_depth = seq_list_count;

			if (def->fn) {
				/* This is a standard definition that uses a function for the replacement text */
				if (def->value)
					FREE_CONST(def->value);
				def->value = (*def->fn)(def);
				def->value_len = strlen(def->value);
			}

			/* Ensure there is enough room to replace $PARAM or ${PARAM} with value */
			replaced_len = def->name_len;
			if (def->multiline) {
				replacing_len = strcspn(def->value, DEF_LINE_END);
				next_ptr = def->value + replacing_len + 1;
				multiline_ptr = next_ptr;
			}
			else {
				if (def->params)
					replaced_len = def->params_end - (cur_pos + 1 )+ (extra_braces ? 0 : 1);
				replacing_len = def->value_len;
			}

			if (len_used + replacing_len - (replaced_len + 1 + extra_braces) >= max_len) {
				log_message(LOG_INFO, "Parameter substitution on line '%s' would exceed maximum line length", buf);
				return NULL;
			}

			if (replaced_len + 1 + extra_braces != replacing_len) {
				/* We need to move the existing text */
				if (replaced_len + 1 + extra_braces < replacing_len) {
					/* We are lengthening the buf text */
					s = cur_pos + strlen(cur_pos);
					d = s - (replaced_len + 1 + extra_braces) + replacing_len;
					e = cur_pos;
					i = -1;
				} else {
					/* We are shortening the buf text */
					s = cur_pos + (replaced_len + 1 + extra_braces) - replacing_len;
					d = cur_pos;
					if (def->params)
						e = def->params_end + (extra_braces ? 2 : 1);
					else
						e = cur_pos + strlen(cur_pos);
					i = 1;
				}
				do {
					*d = *s;
					if (s == e)
						break;
					d += i;
					s += i;
				} while (true);

				len_used = len_used + replacing_len - (replaced_len + 1 + extra_braces);
			}

			/* Now copy the replacement text */
			strncpy(cur_pos, def->value, replacing_len);

			if (def->value[strspn(def->value, " \t")] == '~')
				break;
		}
		else
			cur_pos++;
	}

	/* If we did a replacement, update the multiline_ptr */
	if (found_defn)
		*multiline_ptr_ptr = next_ptr;

	return found_defn;
}

static void
free_def(def_t *def)
{
	list_del_init(&def->e_list);
	FREE_CONST(def->name);
	FREE_CONST_PTR(def->value);
	FREE(def);
}
static void
free_def_list(list_head_t *l)
{
	def_t *def, *def_tmp;

	list_for_each_entry_safe(def, def_tmp, l, e_list)
		free_def(def);
}

static def_t*
set_definition(const char *name, const char *value)
{
	def_t *def;
	size_t name_len = strlen(name);

	if ((def = find_definition(name, name_len, false))) {
		FREE_CONST(def->value);
		def->fn = NULL;		/* Allow a standard definition to be overridden */
	}
	else {
		PMALLOC(def);
		INIT_LIST_HEAD(&def->e_list);
		def->name_len = name_len;
		def->name = STRNDUP(name, def->name_len);

		list_add_tail(&def->e_list, &defs);
	}
	def->value_len = strlen(value);
	def->value = STRNDUP(value, def->value_len);

#ifdef _PARSER_DEBUG_
	if (do_parser_debug)
		log_message(LOG_INFO, "Definition %s now '%s'", def->name, def->value);
#endif

	return def;
}

/* A definition is of the form $NAME=TEXT */
static def_t*
check_definition(const char *buf)
{
	const char *p;
	def_t* def;
	size_t def_name_len;
	char *str;

	if (buf[0] != '$')
		return false;

	if (!isalpha(buf[1]) && buf[1] != '_')
		return NULL;

	for (p = buf + 2; *p; p++) {
		if (*p == '=')
			break;
		if (!isalnum(*p) &&
		    !isdigit(*p) &&
		    *p != '_')
			return NULL;
	}

	def_name_len = (size_t)(p - &buf[1]);

	p += strspn(p, " \t");
	if (*p != '=')
		return NULL;

	if ((def = find_definition(&buf[1], def_name_len, true))) {
		FREE_CONST(def->value);
		def->fn = NULL;		/* Allow a standard definition to be overridden */
	}
	else {
		PMALLOC(def);
		INIT_LIST_HEAD(&def->e_list);
		def->name_len = def_name_len;
		def->name = STRNDUP(buf + 1, def->name_len);

		list_add_tail(&def->e_list, &defs);
	}

	/* Skip leading whitespace */
	p += strspn(p + 1, " \t") + 1;
	def->value_len = strlen(p);
	if (p[def->value_len - 1] == '\\') {
		/* Remove trailing whitespace */
		while (def->value_len >= 2 &&
		       isblank(p[def->value_len - 2]))
			def->value_len--;

		if (def->value_len < 2) {
			/* If the string has nothing except spaces and terminating '\'
			 * point to the string terminator. */
			p += def->value_len;
			def->value_len = 0;
		}
		def->multiline = true;
	} else
		def->multiline = false;

	str = STRNDUP(p, def->value_len);

	/* If it a multiline definition, we need to mark the end of the first line
	 * by overwriting the '\' with the line end marker. */
	if (def->value_len >= 2 && def->multiline)
		str[def->value_len - 1] = DEF_LINE_END[0];

	def->value = str;

	return def;
}

static void
add_std_definition(const char *name, const char *value, const char *(*fn)(const def_t *), unsigned max_params)
{
	def_t* def;

	PMALLOC(def);
	INIT_LIST_HEAD(&def->e_list);
	def->name_len = strlen(name);
	def->name = STRNDUP(name, def->name_len);
	if (value) {
		def->value_len = strlen(value);
		def->value = STRNDUP(value, def->value_len);
	}
	def->fn = fn;
	def->max_params = max_params;

	list_add_tail(&def->e_list, &defs);
}

static void
set_std_definitions(void)
{
	time_t tim;

	add_std_definition("_PWD", NULL, get_cwd, 0);
	add_std_definition("_INSTANCE", NULL, get_instance, 0);
	add_std_definition("_RANDOM", NULL, get_random, 2);
	add_std_definition("_HASH", "#", NULL, 0);
	add_std_definition("_BANG", "!", NULL, 0);

	/* In case $_RANDOM is used, seed the pseudo RNG */
	if (random_seed_configured)
		srandom(random_seed);
	else {
		time(&tim);
		srandom((unsigned int)tim);
	}
}

static void
free_parser_data(void)
{
	free_def_list(&defs);
	free_multiline_stack_list(&multiline_stack);
}

/* decomment() removes comments, the escaping of comment start characters,
 * and leading and trailing whitespace, including whitespace before a
 * terminating \ character */
static void
decomment(char *str)
{
	bool quote = false;
	bool cont = false;
	char *skip = NULL;
	char *p = str + strspn(str, " \t");

	/* Remove leading whitespace */
	if (p != str)
		memmove(str, p, strlen(p) + 1);

	p = str;
	while ((p = strpbrk(p, "!#\"\\"))) {
		if (*p == '"') {
			if (!skip)
				quote = !quote;
			p++;
			continue;
		}
		if (*p == '\\') {
			if (p[1]) {
				/* Don't modify quoted strings */
				if (!quote && (p[1] == '#' || p[1] == '!')) {
					memmove(p, p + 1, strlen(p + 1) + 1);
					p++;
				} else
					p += 2;
				continue;
			}
			*p = '\0';
			cont = true;
			break;
		}
		if (!quote && !skip && (*p == '!' || *p == '#'))
			skip = p;
		p++;
	}

	if (quote)
		report_config_error(CONFIG_GENERAL_ERROR, "Unterminated quote '%s'", str);

	if (skip)
		*skip = '\0';

	/* Remove trailing whitespace */
	p = str + strlen(str) - 1;
	while (p >= str && isblank(*p))		// This line causes a strict-overflow=4 warning in gcc 5.4.0
		*p-- = '\0';
	if (cont) {
		*++p = '\\';
		*++p = '\0';
	}
}

static vector_t *read_value_block_vec;
static void
read_value_block_line(const vector_t *strvec)
{
	size_t word;
	const char *str;

	if (!read_value_block_vec)
		read_value_block_vec = vector_alloc();

	vector_foreach_slot(strvec, str, word) {
		vector_alloc_slot(read_value_block_vec);
		vector_set_slot(read_value_block_vec, STRDUP(str));
	}
}

const vector_t *
read_value_block(const vector_t *strvec)
{
	vector_t *ret_vec;

	alloc_value_block(read_value_block_line, strvec);

	ret_vec = read_value_block_vec;
	read_value_block_vec = NULL;

	return ret_vec;
}

/* min_time and max_time are in micro-seconds. The returned value is also in micro-seconds */
bool
read_timer(const vector_t *strvec, size_t index, unsigned long *res, unsigned long min_time, unsigned long max_time, bool ignore_error)
{
	double timer;
	bool ret;
	double fmin_time, fmax_time;

	fmin_time = (double)min_time / TIMER_HZ;
	fmax_time = (double)((max_time) ? max_time : TIMER_MAXIMUM) / TIMER_HZ;

	ret = read_double_strvec(strvec, index, &timer, fmin_time, fmax_time, ignore_error);
	*res = timer * TIMER_HZ > TIMER_MAXIMUM ? TIMER_MAXIMUM : (unsigned long)(timer * TIMER_HZ);

	return ret;
}

/* Checks for on/true/yes or off/false/no */
int __attribute__ ((pure))
check_true_false(const char *str)
{
	if (!strcmp(str, "true") || !strcmp(str, "on") || !strcmp(str, "yes"))
		return true;
	if (!strcmp(str, "false") || !strcmp(str, "off") || !strcmp(str, "no"))
		return false;

	return -1;	/* error */
}

void skip_block(bool need_block_start)
{
	/* Don't process the rest of the configuration block */
	if (need_block_start)
		skip_sublevel = -1;
	else
		skip_sublevel = 1;
}

static bool
open_conf_file(include_file_t *file)
{
	struct stat stb;
	unsigned i;
	FILE *stream;

	while (file->glob_next < file->globbuf.gl_pathc) {
		i = file->glob_next++;

		if (file->globbuf.gl_pathv[i][strlen(file->globbuf.gl_pathv[i])-1] == '/') {
			/* This is a directory - so skip */
			continue;
		}

		log_message(LOG_INFO, "Opening file '%s'.", file->globbuf.gl_pathv[i]);
		stream = fopen(file->globbuf.gl_pathv[i], "r");
		if (!stream) {
			log_message(LOG_INFO, "Configuration file '%s' open problem (%s) - skipping"
				       , file->globbuf.gl_pathv[i], strerror(errno));
			continue;
		}

		/* Make sure what we have opened is a regular file, and not for example a directory or executable */
		if (fstat(fileno(stream), &stb) ||
		    !S_ISREG(stb.st_mode) ||
		    (stb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
			log_message(LOG_INFO, "Configuration file '%s' is not a regular non-executable file - skipping", file->globbuf.gl_pathv[i]);
			fclose(stream);
			continue;
		}

		file->stream = stream;
		file->num_matches++;

		/* We only want to report the file name if there is more than one file used */
		if (!list_is_last(&include_stack, &file->e_list) || file->globbuf.gl_pathc > 1)
			file->current_file_name = file->globbuf.gl_pathv[i];
		file->current_line_no = 0;

		if (strchr(file->globbuf.gl_pathv[i], '/')) {
			/* If the filename contains a directory element, change to that directory.
			   The man page open(2) states that fchdir() didn't support O_PATH until Linux 3.5,
			   even though testing on Linux 3.1 shows it appears to work. To be safe, don't
			   use it until Linux 3.5. */
			file->curdir_fd = open(".", O_RDONLY | O_DIRECTORY
#if HAVE_DECL_O_PATH && LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
								     | O_PATH
#endif
									     );

			char *confpath = STRDUP(file->globbuf.gl_pathv[i]);
			dirname(confpath);
			if (chdir(confpath) < 0)
				log_message(LOG_INFO, "chdir(%s) error (%s)", confpath, strerror(errno));
			FREE(confpath);
		} else
			file->curdir_fd = -1;

		return true;
	}

	return false;
}

static bool
open_glob_file(const char *conf_file)
{
	int	res;
	include_file_t *file;

	PMALLOC(file);
	INIT_LIST_HEAD(&file->e_list);

	file->globbuf.gl_offs = 0;
	res = glob(conf_file, GLOB_MARK
#if HAVE_DECL_GLOB_BRACE
					| GLOB_BRACE
#endif
						    , NULL, &file->globbuf);

	if (res) {
		if (res == GLOB_NOMATCH)
			log_message(LOG_INFO, "No config files matched '%s'.", conf_file);
		else
			log_message(LOG_INFO, "Error reading config file(s): glob(\"%s\") returned %d, skipping.", conf_file, res);
		FREE(file);
		return false;
	}

	if (!open_conf_file(file)) {
		log_message(LOG_INFO, "%s - no matching file", conf_file);

		globfree(&file->globbuf);
		FREE(file);
		return false;
	}

	file->file_name = STRDUP(conf_file);
	list_head_add(&file->e_list, &include_stack);

	return true;
}

static bool
end_file(include_file_t *file)
{
	int res;

	fclose(file->stream);

// WHY??
//	free_seq_list(&seq_list);

	/* If we changed directory, restore the previous directory */
	if (file->curdir_fd != -1) {
		if ((res = fchdir(file->curdir_fd)))
			log_message(LOG_INFO, "Failed to restore previous directory after include");
		close(file->curdir_fd);
		if (res)
			return false;
	}

	return true;
}

static void
end_glob(include_file_t *file)
{
	if (!file->num_matches)
		log_message(LOG_INFO, "No config files matched '%s'.", file->file_name);

	globfree(&file->globbuf);
	FREE_CONST_PTR(file->file_name);

	list_del_init(&file->e_list);
	FREE(file);
}

static bool
get_next_file(void)
{
	include_file_t *file = list_first_entry(&include_stack, include_file_t, e_list);

	end_file(file);

	if (open_conf_file(file))
		return true;

	end_glob(file);

	if (list_empty(&include_stack))
		return false;

	file = list_first_entry(&include_stack, include_file_t, e_list);

	return true;
}

static bool
check_include(const char *buf)
{
	const char *p;

	if (strncmp(buf, "include", 7) ||
	    (buf[7] != ' ' && buf[7] != '\t'))
		return false;

	p = buf + 8;
	p += strspn(p, " \t");

	open_glob_file(p);

	return true;
}

static bool
read_line(char *buf, size_t size)
{
	static def_t *def = NULL;
	static const char *next_ptr = NULL;
	static char *line_residue = NULL;
	size_t len ;
	bool eof = false;
	size_t config_id_len;
	char *buf_start;
	bool rev_cmp;
	size_t ofs;
	bool recheck;
	bool multiline_param_def = false;
	char *end;
	size_t skip;
	char *p;
	list_head_t *next_value;
	value_t *value;
	param_t *param;
	include_file_t *file;

	config_id_len = config_id ? strlen(config_id) : 0;
	do {
		if (line_residue) {
			strcpy(buf, line_residue);
			FREE(line_residue);
			line_residue = NULL;
		} else if (!list_empty(&seq_list) &&
			seq_list_count > multiline_seq_depth) {
			seq_t *seq = list_last_entry(&seq_list, seq_t, e_list);
			if (list_empty(&seq->lst_params)) {
				char val[21];
				if (seq->hex)
					snprintf(val, sizeof(val), "%lx", (unsigned long)seq->next);
				else
					snprintf(val, sizeof(val), "%ld", seq->next);
#ifdef _PARSER_DEBUG_
				if (do_parser_debug)
					log_message(LOG_INFO, "Processing seq %ld of %s for '%s'",  seq->next, seq->var, seq->text);
#endif
				set_definition(seq->var, val);
				strcpy(buf, seq->text);
				seq->next += seq->step;
				if ((seq->step > 0 && seq->next > seq->last) ||
				    (seq->step < 0 && seq->next < seq->last)) {
#ifdef _PARSER_DEBUG_
					if (do_parser_debug)
						log_message(LOG_INFO, "Removing seq %s for '%s'", seq->var, seq->text);
#endif
					free_seq(seq);
				}
			} else {
				next_value = seq->next_var->values.next;
				list_for_each_entry(param, &seq->lst_params, e_list) {
					value = list_entry(next_value, value_t, e_list);
#ifdef _PARSER_DEBUG_
					if (do_parser_debug)
						log_message(LOG_INFO, "Processing lst %s = '%s'",  param->name, value->val);
#endif
					set_definition(param->name, value->val);
					strcpy(buf, seq->text);
					next_value = next_value->next;
				}
				if (list_is_last(&seq->next_var->e_list, &seq->lst_values)) {
#ifdef _PARSER_DEBUG_
					if (do_parser_debug)
						log_message(LOG_INFO, "Removing lst");
#endif
					free_seq_lst(seq);
				} else
					seq->next_var = list_entry(seq->next_var->e_list.next, value_set_t, e_list);
			}
		} else if (next_ptr) {
			/* We are expanding a multiline parameter, so copy next line */
			end = strchr(next_ptr, DEF_LINE_END[0]);
			if (!end) {
				strcpy(buf, next_ptr);
				if (!list_empty(&multiline_stack))
					next_ptr = multiline_stack_pop();
				else {
					next_ptr = NULL;
					multiline_seq_depth = 0;
				}
			} else {
				strncpy(buf, next_ptr, (size_t)(end - next_ptr));
				buf[end - next_ptr] = '\0';
				next_ptr = end + 1;
			}
		} else {
			/* Get the next non-blank line */
			file = list_first_entry(&include_stack, include_file_t, e_list);

			do {
				if (!fgets(buf, (int)size, file->stream))
				{
					if (get_next_file()) {
						file = list_first_entry(&include_stack, include_file_t, e_list);
						buf[0] = '\0';
						continue;
					}

					eof = true;
					buf[0] = '\0';
					break;
				}

				/* Check if we have read the end of a line */
				len = strlen(buf);
				if (len && buf[len-1] == '\n') {
					file->current_line_no++;
					len--;
				}

				/* Remove end of line chars */
				while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
					len--;

				if (!len && multiline_param_def) {
					multiline_param_def = false;
					if (!def->value_len)
						def->multiline = false;
				}

				if (!len)
					continue;

				buf[len] = '\0';
				decomment(buf);
			} while (!buf[0]);

			if (!buf[0])
				break;
		}

		len = strlen(buf);

		/* Handle multi-line definitions */
		if (multiline_param_def) {
			/* Remove trailing whitespace */
			if (len && buf[len-1] == '\\') {
				len--;
				while (len >= 1 && isblank(buf[len - 1]))
					len--;
				buf[len++] = DEF_LINE_END[0];
			} else {
				multiline_param_def = false;
				if (!def->value_len)
					def->multiline = false;
			}

			/* Don't add blank lines */
			if (len >= 2 ||
			    (len && !multiline_param_def)) {
				/* Add the line to the definition */
				char *str = REALLOC_CONST(def->value, def->value_len + len + 1);
				strncpy(str + def->value_len, buf, len);
				def->value_len += len;
				str[def->value_len] = '\0';
				def->value = str;
			}

			buf[0] = '\0';
			continue;
		}

		if (len == 0)
			continue;

		do {
			recheck = false;
			if (buf[0] == '@') {
				/* If the line starts '@', check the following word matches the system id.
				   @^ reverses the sense of the match */
				if (buf[1] == '^') {
					rev_cmp = true;
					ofs = 2;
				} else {
					rev_cmp = false;
					ofs = 1;
				}

				/* We need something after the system_id */
				if (!(buf_start = strpbrk(buf + ofs, " \t"))) {
					buf[0] = '\0';
					break;
				}

				/* Check if config_id matches/doesn't match as appropriate */
				if ((!config_id ||
				     (size_t)(buf_start - (buf + ofs)) != config_id_len ||
				     strncmp(buf + ofs, config_id, config_id_len)) != rev_cmp) {
					buf[0] = '\0';
					break;
				}

				/* Remove the @config_id from start of line */
				buf_start += strspn(buf_start, " \t");
				len -= (buf_start - buf);
				memmove(buf, buf_start, len + 1);
			}

			if (buf[0] == '$' && (def = check_definition(buf))) {
				/* check_definition() saves the definition */
				if (def->multiline)
					multiline_param_def = true;
				buf[0] = '\0';
				break;
			}

// TODO TODO TODO - how do we deal with multiple ~SEQ on one line?
// Do we need to find closing ) and process rest of line?
			if (!strncmp(buf, "~SEQ", 4) || !strncmp(buf, "~LST", 4)) {
				if (buf[1] == 'S') {
					if (!add_seq(buf))
						report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~SEQ specification '%s'", buf);
				} else {
					if (!add_lst(buf))
						report_config_error(CONFIG_GENERAL_ERROR, "Invalid ~LST specification '%s'", buf);
				}
#ifdef _PARSER_DEBUG_
				if (do_parser_debug) {
					dump_definitions();
					dump_seqs();
				}
#endif
				buf[0] = '\0';
				continue;
			}

			if (buf[0] == '~')
				break;

			if (!list_empty(&defs) && (p = strchr(buf, '$'))) {
				if (!replace_param(buf, size, &next_ptr)) {
					/* If nothing has changed, we don't need to do any more processing */
					break;
				}

				decomment(buf);

				if (buf[0] == '@')
					recheck = true;
				if (strchr(buf, '$'))
					recheck = true;
			}
		} while (recheck);
	} while (buf[0] == '\0' || check_include(buf));

	/* Search for BOB[0] or EOB[0] not in "" */
	if (buf[0]) {
		p = buf;
		if (p[0] != BOB[0] && p[0] != EOB[0]) {
			while ((p = strpbrk(p, BOB EOB "\""))) {
				if (*p != '"')
					break;

				/* Skip over anything in ""s */
				if (!(p = strchr(p + 1, '"')))
					break;

				p++;
			}
		}

		if (p && (p[0] == BOB[0] || p[0] == EOB[0])) {
			if (p == buf)
				skip = strspn(p + 1, " \t") + 1;
			else
				skip = 0;

			if (p[skip]) {
				/* Skip trailing whitespace */
				len = strlen(p + skip);
				while (len && (p[skip+len-1] == ' ' || p[skip+len-1] == '\t'))
					len--;
				line_residue = MALLOC(len + 1);
				p[skip+len] = '\0';
				strcpy(line_residue, p + skip);
				p[skip] = '\0';
			}
		}

		/* Skip trailing whitespace */
		len = strlen(buf);
		while (len && (buf[len-1] == ' ' || buf[len-1] == '\t'))
			len--;
		buf[len] = '\0';

		/* Check that we haven't got too many '}'s */
		if (!strcmp(buf, BOB))
			block_depth++;
		else if (!strcmp(buf, EOB)) {
			if (block_depth-- < 1) {
				report_config_error(CONFIG_UNEXPECTED_EOB, "Extra '}' found");
				block_depth = 0;
			}
		}
	}

#ifdef _PARSER_DEBUG_
	if (do_parser_debug)
		log_message(LOG_INFO, "read_line(%d): '%s'", block_depth, buf);
#endif

#if defined _MEM_CHECK_ && 0
	log_mem_check_message("read_line returns (eof %d) '%s'", eof, buf);
#endif

	return !eof;
}

void
alloc_value_block(void (*alloc_func) (const vector_t *), const vector_t *strvec)
{
	char *buf;
	const char *str;
	vector_t *vec;
	vector_t *first_vec = NULL;
	bool need_bob = true;
	bool had_eob = false;

	if (vector_active(strvec) > 1) {
		if (!strcmp(strvec_slot(strvec, 1), BOB)) {
			need_bob = false;
			if (vector_active(strvec) > 2) {
				first_vec = vector_copy(strvec);
				vector_unset(first_vec, 0);
				vector_unset(first_vec, 1);
				if (!strcmp(strvec_slot(strvec, vector_active(first_vec) - 1), EOB)) {
					vector_unset(first_vec, vector_active(first_vec) - 1);
					had_eob = true;
				}
				first_vec = vector_compact(first_vec);
			}
		} else
			report_config_error(CONFIG_GENERAL_ERROR, "Block %s has extra parameters %s ..."
								, strvec_slot(strvec, 0), strvec_slot(strvec, 1));
	}

	buf = (char *) MALLOC(MAXBUF);
	while (first_vec || read_line(buf, MAXBUF)) {
		if (first_vec)
			vec = first_vec;
		else if (!(vec = alloc_strvec(buf)))
			continue;

		if (!first_vec) {
			if (need_bob) {
				need_bob = false;

				if (!strcmp(vector_slot(vec, 0), BOB)) {
					if (vector_size(vec) == 1) {
						free_strvec(vec);
						continue;
					}

					/* Remove the BOB */
					vec = strvec_remove_slot(vec, 0);
				} else
					log_message(LOG_INFO, "'%s' missing from beginning of block %s", BOB, strvec_slot(strvec, 0));
			}

			/* Check if line read ends with EOB */
			str = vector_slot(vec, vector_active(vec) - 1);
			if (!strcmp(str, EOB)) {
				if (vector_active(vec) == 1) {
					free_strvec(vec);
					break;
				}

				had_eob = true;
				vec = strvec_remove_slot(vec, vector_active(vec) - 1);
			}
		}

		if (vector_size(vec))
			(*alloc_func)(vec);

		if (first_vec) {
			vector_free(first_vec);
			first_vec = NULL;
		} else
			free_strvec(vec);

		if (had_eob)
			break;
	}

	FREE(buf);
}

static bool
process_stream(vector_t *keywords_vec, int need_bob)
{
	unsigned int i;
	keyword_t *keyword_vec;
	const char *str;
	char *buf;
	vector_t *strvec;
	vector_t *prev_keywords = current_keywords;
	current_keywords = keywords_vec;
	int bob_needed = 0;
	bool ret_err = false;
	bool ret;

	buf = MALLOC(MAXBUF);
	while (read_line(buf, MAXBUF)) {
		strvec = alloc_strvec(buf);

		if (!strvec)
			continue;

		str = vector_slot(strvec, 0);

		if (skip_sublevel == -1) {
			/* There wasn't a '{' on the keyword line */
			if (!strcmp(str, BOB)) {
				/* We've got the opening '{' now */
				skip_sublevel = 1;
				need_bob = 0;
				free_strvec(strvec);
				continue;
			}

			/* The skipped keyword doesn't have a {} block, so we no longer want to skip */
			skip_sublevel = 0;
		}
		if (skip_sublevel) {
			for (i = 0; i < vector_size(strvec); i++) {
				str = vector_slot(strvec,i);
				if (!strcmp(str,BOB))
					skip_sublevel++;
				else if (!strcmp(str,EOB)) {
					if (--skip_sublevel == 0)
						break;
				}
			}

			/* If we have reached the outer level of the block and we have
			 * nested keyword level, then we need to return to restore the
			 * next level up of keywords. */
			if (!strcmp(str, EOB) && skip_sublevel == 0 && kw_level > 0) {
				ret_err = true;
				free_strvec(strvec);
				break;
			}

			free_strvec(strvec);
			continue;
		}

		if (need_bob) {
			need_bob = 0;
			if (!strcmp(str, BOB) && kw_level > 0) {
				free_strvec(strvec);
				continue;
			}
			else
				report_config_error(CONFIG_MISSING_BOB, "Missing '%s' at beginning of configuration block", BOB);
		}
		else if (!strcmp(str, BOB)) {
			report_config_error(CONFIG_UNEXPECTED_BOB, "Unexpected '%s' - ignoring", BOB);
			free_strvec(strvec);
			continue;
		}

		if (!strcmp(str, EOB) && kw_level > 0) {
			free_strvec(strvec);
			break;
		}

		for (i = 0; i < vector_size(keywords_vec); i++) {
			keyword_vec = vector_slot(keywords_vec, i);

			if (!strcmp(keyword_vec->string, str)) {
				if (!keyword_vec->active) {
					if (!strcmp(vector_slot(strvec, vector_size(strvec)-1), BOB))
						skip_sublevel = 1;
					else
						skip_sublevel = -1;

					/* Sometimes a process wants to know if another process
					 * has any of a type of configuration. For example, there
					 * is no point starting the VRRP process of there are no
					 * vrrp instances, and so the parent process would be
					 * interested in that. */
					if (keyword_vec->handler)
						(*keyword_vec->handler)(NULL);
				}

				/* There is an inconsistency here. 'static_ipaddress' for example
				 * does not have sub levels, but needs a '{' */
				if (keyword_vec->sub) {
					/* Remove a trailing '{' */
					char *bob = vector_slot(strvec, vector_size(strvec)-1) ;
					if (!strcmp(bob, BOB)) {
						vector_unset(strvec, vector_size(strvec)-1);
						FREE(bob);
						bob_needed = 0;
					}
					else
						bob_needed = 1;
				}

				if (keyword_vec->active && keyword_vec->handler) {
					buf_extern = buf;	/* In case the raw line wants to be accessed */
					(*keyword_vec->handler) (strvec);
				}

				if (keyword_vec->sub) {
					kw_level++;
					ret = process_stream(keyword_vec->sub, bob_needed);
					kw_level--;

					/* We mustn't run any close handler if the block was skipped */
					if (!ret && keyword_vec->active && keyword_vec->sub_close_handler)
						(*keyword_vec->sub_close_handler) ();
				}
				break;
			}
		}

		if (i >= vector_size(keywords_vec))
			report_config_error(CONFIG_UNKNOWN_KEYWORD, "Unknown keyword '%s'", str);

		free_strvec(strvec);
	}

	current_keywords = prev_keywords;
	FREE(buf);
	return ret_err;
}

/* Data initialization */
void
init_data(const char *conf_file, const vector_t * (*init_keywords) (void))
{
	/* A parent process or previous config load may have left these set */
	block_depth = 0;
	kw_level = 0;
	sublevel = 0;
	skip_sublevel = 0;
	multiline_seq_depth = 0;
	config_err = CONFIG_OK;
	random_seed = 0;
	random_seed_configured = false;

	/* Init Keywords structure */
	keywords = vector_alloc();

	(*init_keywords) ();

	/* Add out standard definitions */
	set_std_definitions();

#ifdef _DUMP_KEYWORDS_
	/* Dump configuration */
	if (do_dump_keywords)
		dump_keywords(keywords, 0, NULL);
#endif

	/* Stream handling */
	current_keywords = keywords;

	/* Open the first file */
	if (open_glob_file(conf_file)) {

		register_null_strvec_handler(null_strvec);
		process_stream(current_keywords, 0);
		unregister_null_strvec_handler();

/* Is this right - the seq_list should be empty ???? */
		free_seq_list(&seq_list);

		/* Report if there are missing '}'s. If there are missing '{'s it will already have been reported */
		if (block_depth > 0)
			report_config_error(CONFIG_MISSING_EOB, "There are %d missing '%s's or extra '%s's"
						      , block_depth, EOB, BOB);
	}

	/* Close the password database if it was opened */
	endpwent();

	free_keywords(keywords);
	free_parser_data();
#ifdef _WITH_VRRP_
	clear_rt_names();
#endif
	notify_resource_release();
}