/*
**  Copyright (c) 2006, 2007 Sendmail, Inc. and its suppliers.
**	All rights reserved.
**
**  $Id: config.c,v 1.5 2007/10/22 01:28:31 msk Exp $
*/

#ifndef lint
static char config_c_id[] = "@(#)$Id: config.c,v 1.5 2007/10/22 01:28:31 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/* dkim-filter includes */
#include "config.h"

/*
**  CONFIG_FREE -- release memory associated with a config list
**
**  Parameters:
**  	head -- head of the config list
**
**  Return value:
**  	None.
*/

void
config_free(struct config *head)
{
	struct config *next;
	struct config *cur;

	cur = head;
	while (cur != NULL)
	{
		next = cur->cfg_next;
		free(cur);
		cur = next;
	}
}

/*
**  CONFIG_LOAD -- load configuration from a file
**
**  Parameters:
**  	in -- stream from which to load
**  	cd -- array of (struct configdef) elements containing the
**  	      configuration syntax to assert
**  	line -- line number where an error occurred (updated)
**
**  Return value:
**  	Pointer to a (struct config) which is the head of a list of
**  	loaded configuration items, or NULL on error; if NULL, "line" is
**  	updated to indicate which line number contained the error.
*/

struct config *
config_load(FILE *in, struct configdef *def, unsigned int *line)
{
	int arg;
	int n = -1;
	int err = 0;
	unsigned int myline = 0;
	int value = -1;
	char *p;
	char *str = NULL;
	struct config *new;
	struct config *cur = NULL;
	char buf[BUFRSZ + 1];

	assert(in != NULL);
	assert(def != NULL);

	memset(buf, '\0', sizeof buf);

	while (fgets(buf, sizeof buf - 1, in) != NULL)
	{
		myline++;

		/* read a line; truncate at newline or "#" */
		for (p = buf; *p != '\0'; p++)
		{
			if (*p == '#' || *p == '\n')
			{
				*p = '\0';
				break;
			}
		}

		arg = 0;

		/* break down the arguments */
		/* XXX -- need something better than strtok(), for quoting */
		for (p = strtok(buf, " \t");
		     err == 0 && p != NULL; 
		     p = strtok(NULL, " \t"))
		{
			/* recognize the first? */
			if (arg == 0)
			{
				for (n = 0; ; n++)
				{
					/* nope */
					if (def[n].cd_name == NULL)
					{
						err = 1;
						break;
					}

					if (strcasecmp(def[n].cd_name, p) == 0)
						break;
				}
			}
			else
			{
				char *q;

				switch (def[n].cd_type)
				{
				  case CONFIG_TYPE_STRING:
					str = p;
					break;

				  case CONFIG_TYPE_BOOLEAN:
					if (p[0] == 't' ||
					    p[0] == 'T' ||
					    p[0] == 'y' ||
					    p[0] == 'Y' ||
					    p[0] == '1')
						value = 1;
					else if (p[0] == 'f' ||
					         p[0] == 'F' ||
					         p[0] == 'n' ||
					         p[0] == 'N' ||
					         p[0] == '0')
						value = 0;
					else
						err = 1;

					break;

				  case CONFIG_TYPE_INTEGER:
					value = (int) strtol(p, &q, 0);
					if (*q != '\0')
						err = 1;

					break;

				  default:
					assert(0);
					/* NOTREACHED */
					return NULL;
				}
			}

			arg++;
		}

		/* no arguments */
		if (arg == 0)
			continue;

		/* a parse error, or only one argument, is no good */
		if (arg == 1 || err == 1)
		{
			config_free(cur);

			if (line != NULL)
				*line = myline;

			return NULL;
		}

		new = (struct config *) malloc(sizeof(struct config));
		if (new == NULL)
		{
			config_free(cur);

			if (line != NULL)
				*line = myline;

			return NULL;
		}

		new->cfg_next = cur;
		new->cfg_name = def[n].cd_name;
		new->cfg_type = def[n].cd_type;
		switch (new->cfg_type)
		{
		  case CONFIG_TYPE_STRING:
			new->cfg_string = strdup(str);
			break;

		  case CONFIG_TYPE_BOOLEAN:
			new->cfg_bool = (bool) value;
			break;

		  case CONFIG_TYPE_INTEGER:
			new->cfg_int = value;
			break;

		  default:
			assert(0);
		}

		cur = new;
	}

	return cur;
}

/*
**  CONFIG_CHECK -- verify that stuff marked "required" is present
**
**  Parameters:
**  	head -- head of config list
**  	def -- definitions
**
**  Return value:
**  	Name of the first parameter in "def" that was marked "required"
**  	yet absent from the configuration parsed, or NULL if nothing
**  	required was missing.
*/

char *
config_check(struct config *head, struct configdef *def)
{
	int n;
	struct config *cur;

	assert(head != NULL);
	assert(def != NULL);

	for (n = 0; ; n++)
	{
		if (def[n].cd_name == NULL)
			return NULL;
		if (!def[n].cd_req)
			continue;

		for (cur = head; cur != NULL; cur = cur->cfg_next)
		{
			if (cur->cfg_name == def[n].cd_name)
				break;
		}

		if (cur == NULL)
			return def[n].cd_name;
	}

	/* NOTREACHED */
}

/*
**  CONFIG_GET -- retrieve a parameter's value
**
**  Parameter:
**  	head -- head of config list
**  	name -- name of the parameter of interest
**  	value -- where to write the result (returned)
**  	size -- bytes available at "value"
**
**  Return value:
**  	1 if the data was found, 0 otherwise, -1 if the request was illegal
**
**  Notes:
**  	"value" is a (void *).  It can be used directly, such as:
**
**  		int x;
**
**  		(void) config_get(conflist, "MyInteger", (void *) &x);
*/

int
config_get(struct config *head, const char *name, void *value, size_t size)
{
	struct config *cur;

	assert(head != NULL);
	assert(name != NULL);
	assert(value != NULL);
	assert(size > 0);

	for (cur = head; cur != NULL; cur = cur->cfg_next)
	{
		if (strcasecmp(cur->cfg_name, name) == 0)
		{
			switch (cur->cfg_type)
			{
			  case CONFIG_TYPE_BOOLEAN:
				if (size != sizeof(bool))
					return -1;
				memcpy(value, &cur->cfg_bool, size);
				break;

			  case CONFIG_TYPE_INTEGER:
				if (size != sizeof(int))
					return -1;
				memcpy(value, &cur->cfg_int, size);
				break;

			  default:
				if (size != sizeof(char *))
					return -1;
				memcpy(value, &cur->cfg_string, size);
				break;
			}

			return 1;
		}
	}

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1