/*
 * Standalone Configuration File Parser
 *
 * Copyright (C) 2008 iptelorg GmbH
 * Written by Jan Janak <jan@iptel.org>
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio 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.
 *
 * Kamailio 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
/*!
 * \file
 * \brief Kamailio core :: Standalone Configuration File Parser
 * \author Jan Janak <jan@iptel.org>
 * \ingroup core
 *
 * Module: \ref core
 *
 * See \ref ConfigEngine
 *
 * \page ConfigEngine
 * In file \ref cfg_parser.c
 * Configuration examples
 * - \ref ConfigExample1
 * - \ref ConfigExample2
 * - \ref ConfigExample3
 * - \ref ConfigExample4
 * - \ref ConfigExample5
 * - \ref ConfigExample6
 * - \ref ConfigExample7
 * - \ref ConfigExample8
 *
 * <b>Run-time Allocated Destination Variables</b>
 * - the destination variable pointers in arrays are assigned at compile time
 * - The address of variables allocated on the heap (typically in dynamically allocated
 *   structures) is not know and thus we need to assign NULL initially and change the pointer
 *   at runtime.
 *   (provide an example).
 *
 * <b>Built-in parsing functions</b>
 *
 * *_val functions parse the whole option body (including =)
 */


/*! \page ConfigExample1  Configuration engine Example 1: Options without values
 *
\verbatim
 *	str file = STR_STATIC_INIT("test.cfg");
 *	cfg_parser_t* parser;
 *	int feature_a = 0, feature_b = 0;
 *
 *	cfg_option_t options[] = {
 *		{"enable_feature_a",  .param = &feature_a, .val = 1},
 *		{"disable_feature_a", .param = &feature_a, .val = 0},
 *		{"feature_b",         .param = &feature_b, .val = 1},
 *      {0}
 *	};
 *
 *	if ((parser = cfg_parser_init(&file)) == NULL) {
 *		ERR("Error while creating parser\n");
 *		return -1;
 *	}
 *
 *	cfg_set_options(parser, options);
 *
 *	if (sr_cfg_parse(parser) < 0) {
 *		ERR("Error while parsing configuration file\n");
 *      cfg_parser_close(parser);
 *		return -1;
 *	}
 *
 *	cfg_parser_close(parser);
\endverbatim
 */

/*! \page ConfigExample2  Configuration engine Example 2: Options with integer values
\verbatim
 * 	cfg_option_t options[] = {
 *		{"max_number",   .param = &max_number,   .f = cfg_parse_int_val },
 *		{"extra_checks", .param = &extra_checks, .f = cfg_parse_bool_val},
 *		{0}
 *	};
\endverbatim
 */

/*! \page ConfigExample3 Configuration engine Example 3: Enum options
\verbatim
 * int scope;
 *
 *	cfg_option_t scopes[] = {
 *	    {"base",     .param = &scope, .val = 1},
 *	    {"onelevel", .param = &scope, .val = 2},
 *	    {"one",      .param = &scope, .val = 3},
 *	    {"subtree",  .param = &scope, .val = 4},
 *	    {"sub",      .param = &scope, .val = 5},
 *	    {"children", .param = &scope, .val = 6},
 *	    {0}
 *  };
 *
 *	cfg_option_t options[] = {
 *		{"scope", .param = scopes, .f = cfg_parse_enum_val},
 *		{0}
 *	};
\endverbatim
 */

/*! \page ConfigExample4 Configuration engine Example 4: Options with string values
\verbatim
* 	str filename = STR_NULL;
 *
 *	cfg_option_t options[] = {
 *		{"filename", .param = &filename, .f = cfg_parse_str_val},
 *		{0}
 *	};
 *
 * - By default the function returns a pointer to an internal buffer which will be destroyed
 *   by a subsequent call
 * - There are flags to tell the function to copy the resuting string in a pkg, shm, glibc,
 *   or static buffers
\endverbatim
 */

/*! \page ConfigExample5 Configuration engine Example 5: Custom value parsing
 * TBD
 */

/*! \page ConfigExample6 Configuration engine Example 6: Parsing Sections
 * TBD
 */

/*! \page ConfigExample7 Configuration engine Example 7: Default Options
 * TBD
 */

/*! \page ConfigExample8 Configuration engine Example 8: Memory management of strings
 *
 * Data types with fixed size are easy, they can be copied into a pre-allocated memory
 * buffer, strings cannot because their length is unknown.
 */

#include "cfg_parser.h"

#include "mem/mem.h"
#include "mem/shm_mem.h"
#include "dprint.h"
#include "trim.h"
#include "ut.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>


/*! \brief The states of the lexical scanner */
enum st
{
	ST_S,  /*!< Begin */
	ST_A,  /*!< Alphanumeric */
	ST_AE, /*!< Alphanumeric escaped */
	ST_Q,  /*!< Quoted */
	ST_QE, /*!< Quoted escaped */
	ST_C,  /*!< Comment */
	ST_CE, /*!< Comment escaped */
	ST_E,  /*!< Escaped */
};


/*! \brief Test for alphanumeric characters */
#define IS_ALPHA(c)                                           \
	(((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') \
			|| ((c) >= '0' && (c) <= '9') || (c) == '_')


/*! \brief Test for delimiter characters */
#define IS_DELIM(c)                                                     \
	((c) == '=' || (c) == ':' || (c) == ';' || (c) == '.' || (c) == ',' \
			|| (c) == '?' || (c) == '[' || (c) == ']' || (c) == '/'     \
			|| (c) == '@' || (c) == '!' || (c) == '$' || (c) == '%'     \
			|| (c) == '&' || (c) == '*' || (c) == '(' || (c) == ')'     \
			|| (c) == '-' || (c) == '+' || (c) == '|' || (c) == '\'')


/*! \brief Whitespace characters */
#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r')
#define IS_QUOTE(c) ((c) == '\"')  /* Quote characters */
#define IS_COMMENT(c) ((c) == '#') /* Characters that start comments */
#define IS_ESCAPE(c) ((c) == '\\') /* Escape characters */
#define IS_EOL(c) ((c) == '\n')	   /* End of line */


/*! \brief
 * Append character to the value of current token
 */
#define PUSH(c)                                                         \
	if(token->val.len >= MAX_TOKEN_LEN) {                               \
		ERR("%s:%d:%d: Token too long\n", st->file, st->line, st->col); \
		return -1;                                                      \
	}                                                                   \
	if(token->val.len == 0) {                                           \
		token->start.line = st->line;                                   \
		token->start.col = st->col;                                     \
	}                                                                   \
	token->val.s[token->val.len++] = (c);


/*! \brief
 * Return current token from the lexical analyzer
 */
#define RETURN(c)               \
	token->end.line = st->line; \
	token->end.col = st->col;   \
	token->type = (c);          \
	print_token(token);         \
	return 0;


/*! \brief
 * Get next character and update counters
 */
#define READ_CHAR     \
	c = fgetc(st->f); \
	if(IS_EOL(c)) {   \
		st->line++;   \
		st->col = 0;  \
	} else {          \
		st->col++;    \
	}


cfg_option_t cfg_bool_values[] = {{"yes", .val = 1}, {"true", .val = 1},
		{"enable", .val = 1}, {"enabled", .val = 1}, {"1", .val = 1},
		{"on", .val = 1}, {"no", .val = 0}, {"false", .val = 0},
		{"disable", .val = 0}, {"disabled", .val = 0}, {"0", .val = 0},
		{"off", .val = 0}, {0}};


static void print_token(cfg_token_t *token)
{
#ifdef EXTRA_DEBUG
	int i, j;
	char *buf;

	if((buf = pkg_malloc(token->val.len * 2)) == NULL) {
		LM_DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", token->type,
				STR_FMT(&token->val), token->start.line, token->start.col,
				token->end.line, token->end.col);
		PKG_MEM_ERROR;
	} else {
		for(i = 0, j = 0; i < token->val.len; i++) {
			switch(token->val.s[i]) {
				case '\n':
					buf[j++] = '\\';
					buf[j++] = 'n';
					break;
				case '\r':
					buf[j++] = '\\';
					buf[j++] = 'r';
					break;
				case '\t':
					buf[j++] = '\\';
					buf[j++] = 't';
					break;
				case '\0':
					buf[j++] = '\\';
					buf[j++] = '0';
					break;
				case '\\':
					buf[j++] = '\\';
					buf[j++] = '\\';
					break;
				default:
					buf[j++] = token->val.s[i];
			}
		}
		LM_DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", token->type, j, buf,
				token->start.line, token->start.col, token->end.line,
				token->end.col);
		pkg_free(buf);
	}
#endif /* EXTRA_DEBUG */
}


int cfg_get_token(cfg_token_t *token, cfg_parser_t *st, unsigned int flags)
{
	static int look_ahead = EOF;
	int c;
	enum st state;

	state = ST_S;

	token->val.s = token->buf;
	token->val.len = 0;

	if(look_ahead != EOF) {
		c = look_ahead;
		look_ahead = EOF;
	} else {
		READ_CHAR;
	}

	while(c != EOF) {
		switch(state) {
			case ST_S:
				if(flags & CFG_EXTENDED_ALPHA) {
					if(IS_WHITESPACE(c)) {
						/* Do nothing */
					} else if(IS_ALPHA(c) || IS_ESCAPE(c) || IS_DELIM(c)) {
						PUSH(c);
						state = ST_A;
					} else if(IS_QUOTE(c)) {
						state = ST_Q;
					} else if(IS_COMMENT(c)) {
						state = ST_C;
					} else if(IS_EOL(c)) {
						PUSH(c);
						RETURN(c);
					} else {
						ERR("%s:%d:%d: Invalid character 0x%x\n", st->file,
								st->line, st->col, c);
						return -1;
					}
				} else {
					if(IS_WHITESPACE(c)) {
						/* Do nothing */
					} else if(IS_ALPHA(c)) {
						PUSH(c);
						state = ST_A;
					} else if(IS_QUOTE(c)) {
						state = ST_Q;
					} else if(IS_COMMENT(c)) {
						state = ST_C;
					} else if(IS_ESCAPE(c)) {
						state = ST_E;
					} else if(IS_DELIM(c) || IS_EOL(c)) {
						PUSH(c);
						RETURN(c);
					} else {
						ERR("%s:%d:%d: Invalid character 0x%x\n", st->file,
								st->line, st->col, c);
						return -1;
					}
				}
				break;

			case ST_A:
				if(flags & CFG_EXTENDED_ALPHA) {
					if(IS_ALPHA(c) || IS_DELIM(c) || IS_QUOTE(c)) {
						PUSH(c);
					} else if(IS_ESCAPE(c)) {
						state = ST_AE;
					} else if(IS_COMMENT(c) || IS_EOL(c) || IS_WHITESPACE(c)) {
						look_ahead = c;
						RETURN(CFG_TOKEN_ALPHA);
					} else {
						ERR("%s:%d:%d: Invalid character 0x%x\n", st->file,
								st->line, st->col, c);
						return -1;
					}
				} else {
					if(IS_ALPHA(c)) {
						PUSH(c);
					} else if(IS_ESCAPE(c)) {
						state = ST_AE;
					} else if(IS_WHITESPACE(c) || IS_DELIM(c) || IS_QUOTE(c)
							  || IS_COMMENT(c) || IS_EOL(c)) {
						look_ahead = c;
						RETURN(CFG_TOKEN_ALPHA);
					} else {
						ERR("%s:%d:%d: Invalid character 0x%x\n", st->file,
								st->line, st->col, c);
						return -1;
					}
				}
				break;

			case ST_AE:
				if(IS_COMMENT(c) || IS_QUOTE(c) || IS_ESCAPE(c)) {
					PUSH(c);
				} else if(c == 'r') {
					PUSH('\r');
				} else if(c == 'n') {
					PUSH('\n');
				} else if(c == 't') {
					PUSH('\t');
				} else if(c == ' ') {
					PUSH(' ');
				} else if(IS_EOL(c)) {
					/* Do nothing */
				} else {
					ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
							st->file, st->line, st->col, c);
					return -1;
				}
				state = ST_A;
				break;

			case ST_Q:
				if(IS_QUOTE(c)) {
					RETURN(CFG_TOKEN_STRING);
				} else if(IS_ESCAPE(c)) {
					state = ST_QE;
					break;
				} else {
					PUSH(c);
				}
				break;

			case ST_QE:
				if(IS_ESCAPE(c) || IS_QUOTE(c)) {
					PUSH(c);
				} else if(c == 'n') {
					PUSH('\n');
				} else if(c == 'r') {
					PUSH('\r');
				} else if(c == 't') {
					PUSH('\t');
				} else if(IS_EOL(c)) {
					/* Do nothing */
				} else {
					ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
							st->file, st->line, st->col, c);
					return -1;
				}
				state = ST_Q;
				break;

			case ST_C:
				if(IS_ESCAPE(c)) {
					state = ST_CE;
				} else if(IS_EOL(c)) {
					state = ST_S;
					continue; /* Do not read a new char, return EOL */
				} else {
					/* Do nothing */
				}
				break;

			case ST_CE:
				state = ST_C;
				break;

			case ST_E:
				if(IS_COMMENT(c) || IS_QUOTE(c) || IS_ESCAPE(c)) {
					PUSH(c);
					RETURN(c);
				} else if(c == 'r') {
					PUSH('\r');
					RETURN('\r');
				} else if(c == 'n') {
					PUSH('\n');
					RETURN('\n');
				} else if(c == 't') {
					PUSH('\t');
					RETURN('\t');
				} else if(c == ' ') {
					PUSH(' ');
					RETURN(' ');
				} else if(IS_EOL(c)) {
					/* Escped eol means no eol */
					state = ST_S;
				} else {
					ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
							st->file, st->line, st->col, c);
					return -1;
				}
				break;
		}

		READ_CHAR;
	};

	switch(state) {
		case ST_S:
		case ST_C:
		case ST_CE:
			return 1;

		case ST_A:
			RETURN(CFG_TOKEN_ALPHA);

		case ST_Q:
			ERR("%s:%d:%d: Premature end of file, missing closing quote in"
				" string constant\n",
					st->file, st->line, st->col);
			return -1;

		case ST_QE:
		case ST_E:
		case ST_AE:
			ERR("%s:%d:%d: Premature end of file, missing escaped character\n",
					st->file, st->line, st->col);
			return -1;
	}
	BUG("%s:%d:%d: Invalid state %d\n", st->file, st->line, st->col, state);
	return -1;
}


int cfg_parse_section(void *param, cfg_parser_t *st, unsigned int flags)
{
	cfg_token_t t;
	int ret;

	ret = cfg_parse_str(param, st, flags);
	if(ret < 0)
		return ret;
	if(ret > 0) {
		ERR("%s:%d:%d: Section name missing.\n", st->file, st->line, st->col);
		return ret;
	}

	ret = cfg_get_token(&t, st, flags);
	if(ret < 0)
		goto error;
	if(ret > 0) {
		ERR("%s:%d:%d: Closing ']' missing\n", st->file, st->line, st->col);
		goto error;
	}
	if(t.type != ']') {
		ERR("%s:%d:%d: Syntax error, ']' expected\n", st->file, t.start.line,
				t.start.col);
		goto error;
	}

	if(cfg_eat_eol(st, flags))
		goto error;
	return 0;

error:
	if(param && ((str *)param)->s) {
		if(flags & CFG_STR_PKGMEM) {
			pkg_free(((str *)param)->s);
			((str *)param)->s = NULL;
		} else if(flags & CFG_STR_SHMMEM) {
			shm_free(((str *)param)->s);
			((str *)param)->s = NULL;
		} else if(flags & CFG_STR_MALLOC) {
			free(((str *)param)->s);
			((str *)param)->s = NULL;
		}
	}
	return -1;
}


static char *get_base_name(str *filename)
{
	char *tmp1, *tmp2, *res;
	int len;

	res = NULL;
	if((tmp1 = as_asciiz(filename)) == NULL) {
		ERR("cfg_parser: No memory left\n");
		goto error;
	}

	if((tmp2 = basename(tmp1)) == NULL) {
		ERR("cfg_parser: Error in basename\n");
		goto error;
	}
	len = strlen(tmp2);

	if((res = pkg_malloc(len + 1)) == NULL) {
		PKG_MEM_ERROR;
		goto error;
	}
	memcpy(res, tmp2, len + 1);
	pkg_free(tmp1);
	return res;

error:
	if(tmp1)
		pkg_free(tmp1);
	return NULL;
}


/** initialize the config parser.
 * @param basedir - path to the config file name. If 0 the path
 *               (base directory) of the main ser.cfg file will be used, else
 *               basedir will be concatenated to the filename. It will be
 *               used only if filename is not an absolute path.
 * @param filename - config filename (can include path elements).
 * @return 0 on error, !=0 on success.
 */
cfg_parser_t *cfg_parser_init(str *basedir, str *filename)
{
	cfg_parser_t *st;
	char *pathname, *base, *abs_pathname;

	abs_pathname = NULL;
	pathname = filename->s;
	st = NULL;
	base = NULL;

	/* if basedir == 0 or != "" get_abs_pathname */
	if(basedir == 0 || basedir->len != 0) {
		if((abs_pathname = get_abs_pathname(basedir, filename)) == NULL) {
			ERR("cfg_parser: Error while converting %.*s to absolute"
				" pathname\n",
					STR_FMT(filename));
			goto error;
		}
		pathname = abs_pathname;
	}

	if((base = get_base_name(filename)) == NULL)
		goto error;

	if((st = (cfg_parser_t *)pkg_malloc(sizeof(*st))) == NULL) {
		PKG_MEM_ERROR;
		goto error;
	}
	memset(st, '\0', sizeof(*st));

	if((st->f = fopen(pathname, "r")) == NULL) {
		ERR("cfg_parser: Unable to open file '%s'\n", pathname);
		goto error;
	}

	if(abs_pathname)
		pkg_free(abs_pathname);

	st->file = base;
	st->line = 1;
	st->col = 0;
	return st;

error:
	if(st) {
		if(st->f)
			fclose(st->f);
		pkg_free(st);
	}
	if(base)
		pkg_free(base);
	if(abs_pathname)
		pkg_free(abs_pathname);
	return NULL;
}


void cfg_parser_close(cfg_parser_t *st)
{
	if(!st)
		return;
	if(st->f)
		fclose(st->f);
	if(st->file)
		pkg_free(st->file);
	pkg_free(st);
}


void cfg_section_parser(cfg_parser_t *st, cfg_func_f parser, void *param)
{
	if(st == NULL)
		return;
	st->section.parser = parser;
	st->section.param = param;
}


void cfg_set_options(cfg_parser_t *st, cfg_option_t *options)
{
	if(st)
		st->options = options;
}


static int process_option(cfg_parser_t *st, cfg_option_t *opt)
{
	if(opt->f) {
		/* We have a function so store it and pass opt->dst to it */
		if(opt->f(opt->param, st, opt->flags) < 0)
			return -1;
	} else {
		/* We have no function, so if we have a pointer to some
		 * variable in opt->param then store the value of opt->i
		 * there, the variable is assumed to be of type i
		 */
		if(opt->param)
			*(int *)opt->param = opt->val;
	}
	return 0;
}


int sr_cfg_parse(cfg_parser_t *st)
{
	int ret;
	cfg_token_t t;
	cfg_option_t *opt;

	while(1) {
		ret = cfg_get_token(&t, st, 0);
		if(ret < 0)
			return ret;
		if(ret > 0)
			break;

		switch(t.type) {
			case CFG_TOKEN_ALPHA:
				/* Lookup the option name */
				if((opt = cfg_lookup_token(st->options, &t.val)) == NULL) {
					ERR("%s:%d:%d: Unsupported option '%.*s'\n", st->file,
							t.start.line, t.start.col, STR_FMT(&t.val));
					return -1;
				}

				st->cur_opt = &t;
				if(process_option(st, opt) < 0) {
					st->cur_opt = 0;
					return -1;
				}
				st->cur_opt = 0;
				break;

			case '[':
				if(st->section.parser == NULL) {
					ERR("%s:%d:%d: Syntax error\n", st->file, t.start.line,
							t.start.col);
					return -1;
				}
				if(st->section.parser(st->section.param, st, 0) < 0)
					return -1;
				break;

				/* Empty line */
			case '\n':
				continue;

			default:
				ERR("%s:%d:%d: Syntax error\n", st->file, t.start.line,
						t.start.col);
				return -1;
		}
	}
	return 0;
}


cfg_option_t *cfg_lookup_token(cfg_option_t *table, str *token)
{
	int len, i;
	int (*cmp)(const char *s1, const char *s2, size_t n) = NULL;


	if(table == NULL)
		return NULL;

	for(i = 0; table[i].name; i++) {
		len = strlen(table[i].name);

		if(table[i].flags & CFG_PREFIX) {
			if(token->len < len)
				continue;
		} else {
			if(token->len != len)
				continue;
		}

		if(table[i].flags & CFG_CASE_SENSITIVE)
			cmp = strncmp;
		else
			cmp = strncasecmp;

		if(cmp(token->s, table[i].name, len))
			continue;
		return table + i;
	}
	if(table[i].flags & CFG_DEFAULT) {
		return table + i;
	}
	return NULL;
}


int cfg_eat_equal(cfg_parser_t *st, unsigned int flags)
{
	cfg_token_t t;
	int ret;

	ret = cfg_get_token(&t, st, flags);
	if(ret < 0)
		return ret;
	if(ret > 0) {
		ERR("%s:%d:%d: Delimiter '=' missing\n", st->file, st->line, st->col);
		return ret;
	}

	if(t.type != '=') {
		ERR("%s:%d:%d: Syntax error, '=' expected\n", st->file, t.start.line,
				t.start.col);
		return -1;
	}
	return 0;
}


int cfg_eat_eol(cfg_parser_t *st, unsigned int flags)
{
	cfg_token_t t;
	int ret;

	/* Skip EOL */
	ret = cfg_get_token(&t, st, 0);
	if(ret < 0)
		return ret;
	if(ret > 0)
		return 0;
	if(t.type != '\n') {
		ERR("%s:%d:%d: End of line expected\n", st->file, t.start.line,
				t.start.col);
		return -1;
	}
	return 0;
}


int cfg_parse_enum(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret;
	cfg_token_t t;
	cfg_option_t *values, *val;

	values = (cfg_option_t *)param;

	ret = cfg_get_token(&t, st, flags);
	if(ret != 0)
		return ret;

	if(t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
		ERR("%s:%d:%d: Invalid enum value '%.*s'\n", st->file, t.start.line,
				t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if(values) {
		if((val = cfg_lookup_token(values, &t.val)) == NULL) {
			ERR("%s:%d:%d Unsupported enum value '%.*s'\n", st->file,
					t.start.line, t.start.col, STR_FMT(&t.val));
			return -1;
		}
		return process_option(st, val);
	} else {
		return 0;
	}
}


int cfg_parse_enum_opt(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret;

	if(cfg_eat_equal(st, flags))
		return -1;

	ret = cfg_parse_enum(param, st, CFG_EXTENDED_ALPHA | flags);
	if(ret > 0) {
		ERR("%s:%d:%d: Option value missing\n", st->file, st->line, st->col);
		return ret;
	} else if(ret < 0)
		return ret;

	if(cfg_eat_eol(st, flags))
		return -1;
	return 0;
}


int cfg_parse_str(void *param, cfg_parser_t *st, unsigned int flags)
{
	str *val;
	int ret;
	char *buf;
	cfg_token_t t;

	ret = cfg_get_token(&t, st, flags);
	if(ret != 0)
		return ret;

	if(t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
		ERR("%s:%d:%d: Invalid string value '%.*s', a string expected.\n",
				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if(!param)
		return 0;
	val = (str *)param;

	if(flags & CFG_STR_STATIC) {
		if(!val->s || val->len <= t.val.len) {
			ERR("%s:%d:%d: Destination string buffer too small\n", st->file,
					t.start.line, t.start.col);
			return -1;
		}
		buf = val->s;
	} else if(flags & CFG_STR_SHMMEM) {
		if((buf = shm_malloc(t.val.len + 1)) == NULL) {
			SHM_MEM_ERROR;
			return -1;
		}
		if(val->s)
			shm_free(val->s);
	} else if(flags & CFG_STR_MALLOC) {
		if((buf = malloc(t.val.len + 1)) == NULL) {
			SYS_MEM_ERROR;
			return -1;
		}
		if(val->s)
			free(val->s);
	} else if(flags & CFG_STR_PKGMEM) {
		if((buf = pkg_malloc(t.val.len + 1)) == NULL) {
			PKG_MEM_ERROR;
			return -1;
		}
		if(val->s)
			pkg_free(val->s);
	} else {
		*val = t.val;
		return 0;
	}

	memcpy(buf, t.val.s, t.val.len);
	buf[t.val.len] = '\0';
	val->s = buf;
	val->len = t.val.len;
	return 0;
}


int cfg_parse_str_opt(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret;

	if(cfg_eat_equal(st, flags))
		return -1;

	ret = cfg_parse_str(param, st, flags | CFG_EXTENDED_ALPHA);
	if(ret > 0) {
		ERR("%s:%d:%d: Option value missing\n", st->file, st->line, st->col);
	} else if(ret < 0)
		return ret;

	if(cfg_eat_eol(st, flags))
		return -1;
	return 0;
}


int cfg_parse_int(void *param, cfg_parser_t *st, unsigned int flags)
{
	int *val;
	int ret, tmp;
	cfg_token_t t;

	val = (int *)param;

	ret = cfg_get_token(&t, st, flags);
	if(ret != 0)
		return ret;

	if(t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
		ERR("%s:%d:%d: Invalid integer value '%.*s'\n", st->file, t.start.line,
				t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if(str2sint(&t.val, &tmp) < 0) {
		ERR("%s:%d:%d: Invalid integer value '%.*s'\n", st->file, t.start.line,
				t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if(val)
		*val = tmp;
	return 0;
}


int cfg_parse_int_opt(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret;

	if(cfg_eat_equal(st, flags))
		return -1;

	ret = cfg_parse_int(param, st, flags);
	if(ret > 0) {
		ERR("%s:%d:%d: Option value missing\n", st->file, st->line, st->col);
	} else if(ret < 0)
		return ret;

	if(cfg_eat_eol(st, flags))
		return -1;
	return 0;
}


int cfg_parse_bool(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret, *val;
	cfg_token_t t;
	cfg_option_t *map;

	val = (int *)param;

	ret = cfg_get_token(&t, st, flags);
	if(ret != 0)
		return ret;

	if(t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
		ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n",
				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if((map = cfg_lookup_token(cfg_bool_values, &t.val)) == NULL) {
		ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n",
				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
		return -1;
	}

	if(val)
		*val = map->val;
	return 0;
}


int cfg_parse_bool_opt(void *param, cfg_parser_t *st, unsigned int flags)
{
	int ret;
	if(cfg_eat_equal(st, flags))
		return -1;

	ret = cfg_parse_bool(param, st, CFG_EXTENDED_ALPHA | flags);
	if(ret > 0) {
		ERR("%s:%d:%d: Option value missing\n", st->file, st->line, st->col);
	} else if(ret < 0)
		return ret;

	if(cfg_eat_eol(st, flags))
		return -1;
	return 0;
}
