/*
 * dsyslog - a dumb syslog (e.g. syslog for people who have a clue)
 * Copyright (c) 2008 William Pitcock <nenolod@sacredspiral.co.uk>
 * Copyright (c) 2005-2008 atheme.org.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "dsyslog.h"

static void dsyslog_config_error(char *format, ...);
static dsyslog_config_file_t *dsyslog_config_parse(char *filename, char *confdata);
static void dsyslog_config_entry_free(dsyslog_config_entry_t *ceptr);

static void dsyslog_config_error(char *format, ...)
{
	va_list ap;
	char buffer[1024];
	char *ptr;

	_ENTER;

	va_start(ap, format);
	vsnprintf(buffer, 1024, format, ap);
	va_end(ap);
	if ((ptr = strchr(buffer, '\n')) != NULL)
		*ptr = '\0';

	if (options.configtest == TRUE)
	{
		printf("%s\n", buffer);
		_LEAVE;
	}
	else
	{
		_ERROR("%s", buffer);
	}

	_LEAVE;
}

static dsyslog_config_file_t *dsyslog_config_parse(char *filename, char *confdata)
{
	char *ptr;
	char *start;
	int linenumber = 1;
	dsyslog_config_entry_t *curce;
	dsyslog_config_entry_t **lastce;
	dsyslog_config_entry_t *cursection;

	dsyslog_config_file_t *curcf;
	dsyslog_config_file_t *lastcf;

	_ENTER;

	lastcf = curcf = (dsyslog_config_file_t *) g_slice_new0(dsyslog_config_file_t);
	curcf->cf_filename = g_strdup(filename);
	lastce = &(curcf->cf_entries);
	curce = NULL;
	cursection = NULL;

	for (ptr = confdata; *ptr; ptr++)
	{
		switch (*ptr)
		{
		  case '#':
			  while (*++ptr && (*ptr != '\n'))
				  ;
			  if (!*ptr)
			  {
				  /* make for(;;) exit from the loop */
				  ptr--;
				  continue;
			  }
			  linenumber++;
			  break;
		  case ';':
			  if (!curce)
			  {
				  dsyslog_config_error("%s:%i Ignoring extra semicolon\n", filename, linenumber);
				  break;
			  }
			  if (!strcmp(curce->ce_varname, "include"))
			  {
				  dsyslog_config_file_t *cfptr;

				  if (!curce->ce_vardata)
				  {
					  dsyslog_config_error("%s:%i Ignoring \"include\": No filename given\n", filename, linenumber);
					  dsyslog_config_entry_free(curce);
					  curce = NULL;
					  continue;
				  }
				  if (strlen(curce->ce_vardata) > 255)
					  curce->ce_vardata[255] = '\0';
				  cfptr = dsyslog_config_load(curce->ce_vardata);
				  if (cfptr)
				  {
					  lastcf->cf_next = cfptr;
					  lastcf = cfptr;
				  }
				  dsyslog_config_entry_free(curce);
				  curce = NULL;
				  continue;
			  }
			  *lastce = curce;
			  lastce = &(curce->ce_next);
			  curce->ce_fileposend = (ptr - confdata);
			  curce = NULL;
			  break;
		  case '{':
			  if (!curce)
			  {
				  dsyslog_config_error("%s:%i: No name for section start\n", filename, linenumber);
				  continue;
			  }
			  else if (curce->ce_entries)
			  {
				  dsyslog_config_error("%s:%i: Ignoring extra section start\n", filename, linenumber);
				  continue;
			  }
			  curce->ce_sectlinenum = linenumber;
			  lastce = &(curce->ce_entries);
			  cursection = curce;
			  curce = NULL;
			  break;
		  case '}':
			  if (curce)
			  {
				  dsyslog_config_error("%s:%i: Missing semicolon before close brace\n", filename, linenumber);
				  dsyslog_config_entry_free(curce);
				  dsyslog_config_free(curcf);
				  _LEAVE NULL;
			  }
			  else if (!cursection)
			  {
				  dsyslog_config_error("%s:%i: Ignoring extra close brace\n", filename, linenumber);
				  continue;
			  }
			  curce = cursection;
			  cursection->ce_fileposend = (ptr - confdata);
			  cursection = cursection->ce_prevlevel;
			  if (!cursection)
				  lastce = &(curcf->cf_entries);
			  else
				  lastce = &(cursection->ce_entries);
			  for (; *lastce; lastce = &((*lastce)->ce_next))
				  continue;
			  break;
		  case '/':
			  if (*(ptr + 1) == '/')
			  {
				  ptr += 2;
				  while (*ptr && (*ptr != '\n'))
					  ptr++;
				  if (!*ptr)
					  break;
				  ptr--;	/* grab the \n on next loop thru */
				  continue;
			  }
			  else if (*(ptr + 1) == '*')
			  {
				  int commentstart = linenumber;

				  for (ptr += 2; *ptr; ptr++)
				  {
					  if ((*ptr == '*') && (*(ptr + 1) == '/'))
					  {
						  ptr++;
						  break;
					  }
					  else if (*ptr == '\n')
						  linenumber++;
				  }
				  if (!*ptr)
				  {
					  dsyslog_config_error("%s:%i Comment on this line does not end\n", filename, commentstart);
					  dsyslog_config_entry_free(curce);
					  dsyslog_config_free(curcf);
					  _LEAVE NULL;
				  }
			  }
			  break;
		  case '\"':
			  start = ++ptr;
			  for (; *ptr; ptr++)
			  {
				  if ((*ptr == '\\') && (*(ptr + 1) == '\"'))
				  {
					  char *tptr = ptr;
					  while ((*tptr = *(tptr + 1)))
						  tptr++;
				  }
				  else if ((*ptr == '\"') || (*ptr == '\n'))
					  break;
			  }
			  if (!*ptr || (*ptr == '\n'))
			  {
				  dsyslog_config_error("%s:%i: Unterminated quote found\n", filename, linenumber);
				  dsyslog_config_entry_free(curce);
				  dsyslog_config_free(curcf);
				  _LEAVE NULL;
			  }
			  if (curce)
			  {
				  if (curce->ce_vardata)
				  {
					  dsyslog_config_error("%s:%i: Ignoring extra data\n", filename, linenumber);
				  }
				  else
				  {
					  char *eptr;

					  curce->ce_vardata = (char *) g_malloc(ptr - start + 1);
					  g_strlcpy(curce->ce_vardata, start, ptr - start + 1);
					  curce->ce_vardatanum = strtol(curce->ce_vardata, &eptr, 0) & 0xffffffff;	/* we only want 32bits and long is 64bit on 64bit compiles */
					  if (eptr != (curce->ce_vardata + (ptr - start)))
					  {
						  curce->ce_vardatanum = 0;
					  }
				  }
			  }
			  else
			  {
				  curce = (dsyslog_config_entry_t *) g_slice_new0(dsyslog_config_entry_t);
				  memset(curce, 0, sizeof(dsyslog_config_entry_t));
				  curce->ce_varname = (char *)g_malloc(ptr - start + 1);
				  g_strlcpy(curce->ce_varname, start, ptr - start + 1);
				  curce->ce_varlinenum = linenumber;
				  curce->ce_fileptr = curcf;
				  curce->ce_prevlevel = cursection;
				  curce->ce_fileposstart = (start - confdata);
			  }
			  break;
		  case '\n':
			  linenumber++;
			  break;
		  case '\t':
		  case ' ':
		  case '=':
		  case '\r':
			  break;
		  default:
			  if ((*ptr == '*') && (*(ptr + 1) == '/'))
			  {
				  dsyslog_config_error("%s:%i Ignoring extra end comment\n", filename, linenumber);
				  ptr++;
				  break;
			  }
			  start = ptr;
			  for (; *ptr; ptr++)
			  {
				  if ((*ptr == ' ') || (*ptr == '\t') || (*ptr == '\n') || (*ptr == ';'))
					  break;
			  }
			  if (!*ptr)
			  {
				  if (curce)
					  dsyslog_config_error("%s: Unexpected EOF for variable starting at %i\n", filename, curce->ce_varlinenum);
				  else if (cursection)
					  dsyslog_config_error("%s: Unexpected EOF for section starting at %i\n", filename, curce->ce_sectlinenum);
				  else
					  dsyslog_config_error("%s: Unexpected EOF.\n", filename);
				  dsyslog_config_entry_free(curce);
				  dsyslog_config_free(curcf);
				  _LEAVE NULL;
			  }
			  if (curce)
			  {
				  if (curce->ce_vardata)
				  {
					  dsyslog_config_error("%s:%i: Ignoring extra data\n", filename, linenumber);
				  }
				  else
				  {
					  char *eptr;

					  curce->ce_vardata = (char *)g_malloc(ptr - start + 1);
					  g_strlcpy(curce->ce_vardata, start, ptr - start + 1);
					  curce->ce_vardatanum = strtol(curce->ce_vardata, &eptr, 0) & 0xffffffff;	/* we only want 32bits and long is 64bit on 64bit compiles */
					  if (eptr != (curce->ce_vardata + (ptr - start)))
					  {
						  curce->ce_vardatanum = 0;
					  }
				  }
			  }
			  else
			  {
				  curce = (dsyslog_config_entry_t *)g_slice_new(dsyslog_config_entry_t);
				  memset(curce, 0, sizeof(dsyslog_config_entry_t));
				  curce->ce_varname = (char *)g_malloc(ptr - start + 1);
				  g_strlcpy(curce->ce_varname, start, ptr - start + 1);
				  curce->ce_varlinenum = linenumber;
				  curce->ce_fileptr = curcf;
				  curce->ce_prevlevel = cursection;
				  curce->ce_fileposstart = (start - confdata);
			  }
			  if ((*ptr == ';') || (*ptr == '\n'))
				  ptr--;
			  break;
		}		/* switch */
	}			/* for */
	if (curce)
	{
		dsyslog_config_error("%s: Unexpected EOF for variable starting on line %i\n", filename, curce->ce_varlinenum);
		dsyslog_config_entry_free(curce);
		dsyslog_config_free(curcf);
		_LEAVE NULL;
	}
	else if (cursection)
	{
		dsyslog_config_error("%s: Unexpected EOF for section starting on line %i\n", filename, cursection->ce_sectlinenum);
		dsyslog_config_free(curcf);
		_LEAVE NULL;
	}
	_LEAVE curcf;
}

static void dsyslog_config_entry_free(dsyslog_config_entry_t *ceptr)
{
	dsyslog_config_entry_t *nptr;

	_ENTER;

	for (; ceptr; ceptr = nptr)
	{
		nptr = ceptr->ce_next;
		if (ceptr->ce_entries)
			dsyslog_config_entry_free(ceptr->ce_entries);
		if (ceptr->ce_varname)
			g_free(ceptr->ce_varname);
		if (ceptr->ce_vardata)
			g_free(ceptr->ce_vardata);
		g_slice_free(dsyslog_config_entry_t, ceptr);
	}

	_LEAVE;
}

void dsyslog_config_free(dsyslog_config_file_t *cfptr)
{
	dsyslog_config_file_t *nptr;

	_ENTER;

	for (; cfptr; cfptr = nptr)
	{
		nptr = cfptr->cf_next;
		if (cfptr->cf_entries)
			dsyslog_config_entry_free(cfptr->cf_entries);
		if (cfptr->cf_filename)
			g_free(cfptr->cf_filename);
		g_slice_free(dsyslog_config_file_t, cfptr);
	}

	_LEAVE;
}

dsyslog_config_file_t *dsyslog_config_load(char *filename)
{
	struct stat sb;
	FILE *fd;
	int ret;
	char *buf = NULL;
	dsyslog_config_file_t *cfptr;

	_ENTER;

	fd = fopen(filename, "rb");
	if (!fd)
	{
		dsyslog_config_error("Couldn't open \"%s\": %s\n", filename, strerror(errno));
		_LEAVE NULL;
	}
	if (stat(filename, &sb) == -1)
	{
		dsyslog_config_error("Couldn't fstat \"%s\": %s\n", filename, strerror(errno));
		fclose(fd);
		_LEAVE NULL;
	}
	if (!sb.st_size)
	{
		fclose(fd);
		_LEAVE NULL;
	}
	buf = (char *)g_malloc(sb.st_size + 1);
	if (buf == NULL)
	{
		dsyslog_config_error("Out of memory trying to load \"%s\"\n", filename);
		fclose(fd);
		_LEAVE NULL;
	}
	ret = fread(buf, 1, sb.st_size, fd);
	if (ret != sb.st_size)
	{
		dsyslog_config_error("Error reading \"%s\": %s\n", filename, ret == -1 ? strerror(errno) : strerror(EFAULT));
		free(buf);
		fclose(fd);
		_LEAVE NULL;
	}
	buf[ret] = '\0';
	fclose(fd);
	cfptr = dsyslog_config_parse(filename, buf);
	g_free(buf);
	_LEAVE cfptr;
}

dsyslog_config_entry_t *dsyslog_config_find(dsyslog_config_entry_t *ceptr, char *name)
{
	_ENTER;

	for (; ceptr; ceptr = ceptr->ce_next)
		if (!strcmp(ceptr->ce_varname, name))
			break;
	_LEAVE ceptr;
}

/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
 * vim:ts=8
 * vim:sw=8
 * vim:noexpandtab
 */
