/* config.c --- gereric config file loader
   Copyright (C) 1999 Beau Kuiper

   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, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"
char *getconfigdata_r(CONFIGFILECACHE *cache, int section, char *cmd, 
		      int *occur, int depth, int *error);

/* this, rather messyly, splits an input line into the command and data
   where the command is the first word on a line, and the data is the
   second.
*/

char *strsplit(char *in, char *cmd, char *data)
{
	int pos = 0;
	int pos2 = 0;
	
	if (strlen(in) >= 1)
		if (in[strlen(in) - 1] == 10)
			in[strlen(in) - 1] = 0;
	
	while(in[pos] != 0)	/* scan entire string resolving \ and
				   remove comments */
	{
		if ((in[pos] == '\\') && (in[pos+1] != 0))
			pos++;
		else if (in[pos] == '#')
		{
			in[pos2] = 0;
			break;
		}
		in[pos2] = in[pos];
		pos++;
		pos2++;
	}	
	in[pos2] = 0;
	
	pos = 0;
	pos2 = 0;
	while((in[pos] <= 32) && (in[pos] != 0))
		pos++;
	if (in[pos] == 0)
		return(NULL);
	
	while(((unsigned char)(in[pos]) > 32) && (in[pos] != 0))
	{
		cmd[pos2] = in[pos];
		pos2++;
		pos++;
	}
	
	if (in[pos] == 0)
		return(NULL);
	
	cmd[pos2] = 0;
	
	while((in[pos] <= 32) && (in[pos] != 0))
		pos++;
	if (in[pos] == 0)
		return(NULL);

	pos2 = 0;
	strcpy(data, in + pos);
	
	pos = strlen(data);
	
	/* now trace back spaces and tabes at end of line */
	while(data[--pos] <= 32);

	data[pos + 1] = 0;
	return(data);
}

/* This will open and load each line in the specified config file (filename)
   and then split it up into constitute parts before sending them off to
   a handler you specify with a void pointer (to be used as the structure
   that the config file will load each item into) */

int loadconfigfile(char *filename, int (* confighandler)(char *, char *, int, void *), 
                   void *configdata, int besecure, int *handlererror, int *linenum)
{ 
	NEWFILE *configfile;
	char *inputline;
	char *cmd = NULL;
	char *data = NULL;
	int result;
	
	*linenum = 0;
	configfile = nfopen(filename);
	if (configfile == NULL)
		return(CONFIG_FILE_ERROR);
	
	if (besecure)
		if (!isfilesafe(configfile->fd))
		{
			nfclose(configfile);
			return(CONFIG_FILE_UNSAFE);
		}

	result = CONFIG_OK;
	while((inputline = nfgetcs(configfile, '\n')) != NULL)
	{
		int a = strlen(inputline) + 1;
		reallocwrapper(a, (void **)&cmd);
		reallocwrapper(a, (void **)&data);
		(*linenum)++;
		
		/* This is a fix for config files written with DOS
		   based software */
		 
		a = strlen(inputline);
 		if (a >= 2)
	 		if (inputline[a-2] == '\r')
				inputline[a-2] = 0;
		
		if (strsplit(inputline, cmd, data) != NULL)
		{
			*handlererror = confighandler(cmd, data, *linenum, configdata);
			if (*handlererror)
			{
				result = CONFIG_HANDLER_ERROR;
				freewrapper(inputline);
				break;
			}
		}
		freewrapper(inputline);
	}
	
	freeifnotnull(cmd);
	freeifnotnull(data);
	nfclose(configfile);
	return(result);
}

/* this code is quite tricky, but it saves much memory */

int cachemaker(char *cmd, char *data, int linenum, void *c)
{
	char newcmd[32];
	CONFIGFILECACHE *cache = (CONFIGFILECACHE *)c;
	int count;
	
	if (strcasecmp(cmd, "[section]") == 0)
	{
		/* add new section */

		if (strcasecmp(data, "none") == 0)
			return(CFC_SECTION_NONE);
			
		/* check to see if section already is there */
		for (count = 0; count < cache->sectioncount; count++)
			if (strcasecmp(data, cache->sectionlist + cache->sectionindex[count]) == 0)
				return(CFC_SECTION_EXISTS);
		
		/* nupe, add it, may be a little slow, but is efficient enough */
		cache->sectioncount++;
		reallocwrapper(sizeof(int) * cache->sectioncount, (void *)&cache->sectionindex);
		reallocwrapper(sizeof(int) * cache->sectioncount, (void *)&cache->sectionlinenum);
		cache->sectionindex[cache->sectioncount - 1] = cache->sectionlen;
		cache->sectionlinenum[cache->sectioncount - 1] = linenum;
		
		cache->sectionlen += strlen(data) + 1;
		reallocwrapper(cache->sectionlen, (void *)&(cache->sectionlist));
		strcpy(cache->sectionlist + cache->sectionindex[cache->sectioncount - 1], data);
		
		reallocwrapper(sizeof(int) * cache->sectioncount, (void **)&(cache->index));
		cache->index[cache->sectioncount - 1] = cache->datacount;
	}
	else
	{
		if (cache->sectioncount == 0)
			return(CFC_NO_SECTION);
			
		/* change an include to a special character for quick 
		   recognision. Puts this special character in a new
		   string that is long enough for the machine type
		   muddleftpd is running on */
		   
		if (strcasecmp(cmd, "include") == 0)
		{
			memset(newcmd, 1, sizeof(int) + 2);
			newcmd[sizeof(int)+1] = 0;
			cmd = newcmd;
		}
		/* add it to the data list */
			
		cache->datacount++; 
		reallocwrapper(sizeof(char *) * cache->datacount, (void *)&(cache->dataindex));
		reallocwrapper(sizeof(char *) * cache->datacount, (void *)&(cache->cmdindex));
		reallocwrapper(sizeof(int) * cache->datacount, (void *)&cache->datalinenum);
	
		cache->dataindex[cache->datacount - 1] = cache->datalen;
		cache->cmdindex[cache->datacount - 1] = cache->cmdlen;
		cache->datalinenum[cache->datacount - 1] = linenum;
	
		cache->datalen += strlen(data) + 1;
		cache->cmdlen += strlen(cmd) + 1;
		
		reallocwrapper(cache->datalen, (void *)&(cache->datalist));
		reallocwrapper(cache->cmdlen, (void *)&(cache->cmdlist));
		
		strcpy(cache->datalist + cache->dataindex[cache->datacount - 1], data);
		strcpy(cache->cmdlist + cache->cmdindex[cache->datacount - 1], cmd);
	}
	return(FALSE);
}

CONFIGFILECACHE *loadconfigcache(char *filename, int *linenum, int *error)
{
	CONFIGFILECACHE *newcache = NULL;
	int result, count;
	
	newcache = mallocwrapper(sizeof(CONFIGFILECACHE));
	newcache->sectioncount = 0;
	newcache->datacount = 0;
	newcache->sectionlen = 0;
	newcache->datalen = 0;
	newcache->cmdlen = 0;
	newcache->sectionindex = NULL;
	newcache->dataindex = NULL;
	newcache->cmdindex = NULL;
	newcache->sectionlist = NULL;
	newcache->datalist = NULL;
	newcache->cmdlist = NULL;
	newcache->index = NULL;
	newcache->sectionlinenum = NULL;
	newcache->datalinenum = NULL;
	
	result = loadconfigfile(filename, cachemaker, newcache, TRUE, error, linenum);

	if (result != CONFIG_OK)
	{
		if (result != CONFIG_HANDLER_ERROR)
			*error = result;
		freeconfigcache(newcache);
		return(NULL);
	}

	/* resolve all include references! */
	
	for (count = 0; count < newcache->datacount; count++)
	{
		if (*(newcache->cmdlist + newcache->cmdindex[count]) == 1)
		{
			/* found an include item, resolve the include reference */
			int section = getsectionid(newcache, newcache->datalist + newcache->dataindex[count]);
			if (section == -1)
			{
				*error = CFC_INCLUDE_ERROR;
				*linenum = newcache->datalinenum[count];
				freeconfigcache(newcache);
				return(NULL);
			}
			memcpy((char *)(newcache->cmdlist + newcache->cmdindex[count] + 1), &section, sizeof(section));
		}
	}

	/* check for recursive includes! */
	for (count = 0; count < newcache->sectioncount; count++)
	{
		int deptherror = FALSE;
		int occur = 1;
		char *data;
		
		/* search for something to check for looping */
		data = getconfigdata_r(newcache, count, "[section]", &occur, 1, &deptherror);
		if (deptherror)
		{
			*error = CFC_INCLUDE_LOOP;
			*linenum = newcache->sectionlinenum[count];
			freeconfigcache(newcache);
			return(NULL);
		}
	}
	freeifnotnull(newcache->sectionlinenum);
	newcache->sectionlinenum = NULL;
	freeifnotnull(newcache->datalinenum);
	newcache->datalinenum = NULL;
	return(newcache);
}

char *config_errorstr(int result)
{
	static char longerror[256];
	
	switch(result)
	{
		case CONFIG_OK:
			return("No errors.");
		case CONFIG_HANDLER_ERROR:
			return("Config handler returned error result.");
		case CONFIG_FILE_UNSAFE:
			return("File was not deemed safe. Must not be group/world writable and must be owned by process owner.");
		case CONFIG_FILE_ERROR:
			snprintf(longerror, 256, "Error opening file (%s).", strerror(errno));
			return(longerror);
		case CFC_INCLUDE_ERROR:
			return("Included section could not be found.");
		case CFC_SECTION_NONE:
			return("Section cannot be named 'none'.");
		case CFC_SECTION_EXISTS:
			return("Section already exists.");
		case CFC_NO_SECTION:
			return("No section has been decleared for config data.");
		case CFC_INCLUDE_LOOP:
			return("A possible recursive loop was found in section.");
		default:
			return("Unknown error!");
	}
}

int getsectionid(CONFIGFILECACHE *cache, char *section)
{
	int count;
	
	for(count = 0; count < cache->sectioncount; count++)
		if (strcasecmp(section, cache->sectionlist + cache->sectionindex[count]) == 0)
			return(count);
	
	return(-1);
}

char *getconfigdata_r(CONFIGFILECACHE *cache, int section, char *cmd, int *occur, int depth, int *error)
{
	int count, last;
	int first;
	int num;
	char *res;

	assert(section < cache->sectioncount);
	assert(section >= 0);
	assert(*occur > 0);
	
	if (depth > MAXINCLUDEDEPTH)
	{
		*error = TRUE;
		return(NULL);
	}
	
	first = cache->index[section];
	
	/* find the last datalist element to look at */
	if (section == cache->sectioncount - 1)
		last = cache->datacount;
	else
		last = cache->index[section + 1];

	/* find the occurance wanted */
	for(count = first; count < last; count++)
	{
		if (*(cache->cmdlist + cache->cmdindex[count]) == 1)
		{
			memcpy(&num, (char *)(cache->cmdlist + cache->cmdindex[count] + 1), sizeof(num));
			res = getconfigdata_r(cache, num, cmd, 
					      occur, depth + 1, error);
			if (res)
				return(res);
		}	
			
		if (strcasecmp(cmd, cache->cmdlist + cache->cmdindex[count]) == 0)
			(*occur)--;
		
		if (*occur == 0)
			return(cache->datalist + cache->dataindex[count]);
	}
	
	/* if not found, return NULL */
	return(NULL);
}

char *getconfigdata(CONFIGFILECACHE *cache, int section, char *cmd, int occur)
{
	int error;
	return(getconfigdata_r(cache, section, cmd, &occur, 1, &error));
}

char **makeconfiglist(CONFIGFILECACHE *cache, char *section, char *label)
{
	int sectnum;
	char **out;
	char *setting;
	int occur;
	
	sectnum = getsectionid(cache, section);
	if (sectnum == -1)
		return(NULL);
	
	out = mallocwrapper(sizeof(char *));
	occur = 1;
	
	while ((setting = getconfigdata(cache, sectnum, label, occur)))
	{
		out[occur - 1] = setting;
		occur++;
		reallocwrapper(sizeof(char *) * occur, (void *)&out);
	}
	out[occur - 1] = NULL;
	return(out);
}

void loadintfromconfig(CONFIGFILECACHE *cache, int section, char *setting, 
		       int *to, int def)
{
	char *set = getconfigdata(cache, section, setting, 1);
	
	if (set)
		sscanf(set, "%d", to);
	else
		*to = def;
}

void loadstrfromconfig(CONFIGFILECACHE *cache, int section, char *setting, 
		       char **to, char *def)
{
	char *set = getconfigdata(cache, section, setting, 1);
	
	if (set)
		*to = set;
	else
		*to = def;
}
 
void freeconfigcache(CONFIGFILECACHE *cache)
{
	freeifnotnull(cache->sectionindex);
	freeifnotnull(cache->dataindex);
	freeifnotnull(cache->cmdindex);
	
	freeifnotnull(cache->sectionlist);
	freeifnotnull(cache->index);
	freeifnotnull(cache->datalist);
	freeifnotnull(cache->cmdlist);
	freeifnotnull(cache->sectionlinenum);
	freeifnotnull(cache->datalinenum);
	freewrapper(cache);
}
