/******************************************************************************
*									      *
*	(C) 1993, 1994 by K. Ballueder					      *
*									      *
*	See README and COPYING for details.				      *
*									      *
*		kballued@charon.physik.uni-osnabrueck.de		      *
*		kballued@jupiter.rz.uni-osnabrueck.de			      *
*									      *
**** Changelog: ***************************************************************
Wed Dec  1 00:22:39  1993	initial version
Fri Dec  3 18:22:05  1993	seems to work perfectly
Sat Jan  1 19:13:32  1994	added permission changes to global file
*/

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>

#include "vconfig.h"

#define VCONF_MAX_LINE	PATH_MAX+20
#define VCONF_DELIM	" =\t"

/* this variable determines where to look for the global configuration file */
char vconf_global_prefix[PATH_MAX] = "/usr/local/lib/";

#ifdef HAS_NO_STRDUP
static char *strdup(char *s)
{
	char *s2 = malloc(strlen(s)+1);
	if(s2)
		strcpy(s,s2);
	return s2;
}
#endif

static int error(int type, void *ptr, VCONF_ERRFNCT errfnct)
{
	static int	lasterrtype = 0;
	char		message[160]="";
	int		errorcode = 0;
	vconf_parameter *vcp = (vconf_parameter *) ptr;
	char		*cptr = (char *) ptr;


	switch(type&(VCONF_LOCAL|VCONF_GLOBAL|VCONF_CLINE))
	{	case VCONF_GLOBAL:	if(lasterrtype != VCONF_GLOBAL)
						strcat(message,"Error in global config file:\n");
					strcat(message,"\tparameter ");
					strcat(message,vcp->string);

					lasterrtype = VCONF_GLOBAL;
					errorcode	|= VCONF_GLOBAL;
					break;
		case VCONF_LOCAL:	if(lasterrtype != VCONF_LOCAL)
						strcat(message,"Error in local config file:\n");
					strcat(message,"\tparameter ");
					strcat(message,vcp->string);
					strcat(message," not allowed here.");
					lasterrtype = VCONF_LOCAL;
					errorcode |= VCONF_LOCAL;
					break;
		case VCONF_CLINE:	if(lasterrtype != VCONF_CLINE)
						strcat(message,"Error on commandline:\n");
					strcat(message,"\tparameter ");
					strcat(message,vcp->clstring);
					strcat(message," not allowed here.");
					lasterrtype = VCONF_CLINE;
					errorcode |= VCONF_CLINE;
					break;
		case VCONF_PERMIT1:	if(lasterrtype != VCONF_GLOBAL)
						strcat(message,"Error in global config file:\n");
					lasterrtype = VCONF_GLOBAL;
					strcat(message,"\tsyntax error in permission, local. or cline. expected:\n");
					strcat(message,cptr);
					errorcode |= VCONF_PERMIT1;
					break;
		case VCONF_PERMIT2:	if(lasterrtype != VCONF_GLOBAL)
						strcat(message,"Error in global config file:\n");
					lasterrtype = VCONF_GLOBAL;
					strcat(message,"\tpermission requested for unknown option:\n");
					strcat(message,cptr);
					errorcode |= VCONF_PERMIT2;
					break;
		default:		strcpy(message,"Missing mandatory option:");
					strcat(message,vcp->string);
					errorcode |= VCONF_MANDATORY;
					return errorcode;
	}
	if(errfnct)
		errfnct(message);
	return errorcode;
}

static void set_vcparm(vconf_parameter *vcp, char line[], vconf_type type)
{
	char	*cptr = line+strlen(vcp->string);

	for( ; (*cptr == ' ' || *cptr == '\t' || *cptr == '=')
		&& *cptr != '\0'; cptr++)
	;	/* eat whitespace and = */

	vcp->value = strdup(cptr);
	cptr = vcp->value + strlen(vcp->value) - 1;
	if(*cptr == '\n')
		*cptr = '\0';
	if(*--cptr == '\"')
		*cptr = '\0';
	if(*vcp->value == '\"')
	{	cptr = strdup(vcp->value+1);
		free(vcp->value);
		vcp->value = cptr;
	}
	vcp->set = type;
}

/* checks file for lines of the form:  permit.[local|cline].option and allows option to 
 be used in local config-file and on commandline */
static int changepermissions(const char *pathname, vconf_parameter *vc_parm,
			VCONF_ERRFNCT errfnct)
{
	FILE 	*file = fopen(pathname, "rt");
	char	line[VCONF_MAX_LINE];
	int	i, found, type;
	int 	errorcode = 0;
	enum	{PERMIT, FORBID} operation;
	
	if(file != NULL)
		while(! feof(file))
		if(fgets(line, VCONF_MAX_LINE, file))
		{	if(strlen(line) <= 13)
				continue;
			if( strncmp("permit.",line,7)==0)
				operation = PERMIT;
			else if( strncmp("forbid.",line,7)==0)
				operation = FORBID;
			else
				continue;
			if(strncmp("local.",line+7,6)==0)
				type = VCONF_LOCAL;
			else if(strncmp("cline.",line+7,6)==0)
				type = VCONF_CLINE;
			else
			{	errorcode |= error(VCONF_PERMIT1, line, errfnct);
				continue;
			}	
			found = 0;
			for(i=0; vc_parm[i].string != NULL; i++)
				if(strncmp(vc_parm[i].string,line+13,strlen(vc_parm[i].string))==0)
				{	if(operation == PERMIT)
						vc_parm[i].type |= type;
					else
						vc_parm[i].type ^= type;
					found = 1;
				}
			if(! found)
				errorcode |= error(VCONF_PERMIT2, line, errfnct);
		}
	fclose(file);
	return errorcode;
}
static int parsefile(const char *pathname, vconf_parameter *vc_parm,
			vconf_type type, VCONF_ERRFNCT errfnct)
{
	FILE 	*file = fopen(pathname, "rt");
	char	line[VCONF_MAX_LINE];
	int 	i, errorcode = 0;

	if(file != NULL)
		while(! feof(file))
		if(fgets(line, VCONF_MAX_LINE, file))
		{	if(type == VCONF_GLOBAL && 
				(strncmp("permit.",line,7)==0 ||
				strncmp("forbid.",line,7)==0))
					continue;
				for(i=0; vc_parm[i].string != NULL; i++)
				if(strncmp(vc_parm[i].string,line,strlen(vc_parm[i].string))==0)
					if(type & vc_parm[i].type)
						set_vcparm(&vc_parm[i], line, type);
					else
						errorcode |= error(type, &vc_parm[i], errfnct);
		}
	fclose(file);
	return errorcode;
}

static int optcmp(char *opt, char *optstr)
{
	while(*opt && *optstr && (*opt == *optstr))
		opt++,optstr++;
	if(*opt == '\0')
		return 0;	/* equal */
	else
		return 1;

}

static void remove_arg(int n, int *argc, char ***argv)
{
	int i;

	for(i=n; i < *argc; i++)
		*(*argv+i) = *(*argv+i+1);
	(*argc) --;
}

static int parsecmdline(vconf_parameter vc_parm[], int *argc, char ***argv,
			VCONF_ERRFNCT errfnct)
{	int	i,j, found;
	int 	errorcode = 0;
	char 	*cptr, *cptr2;
	
	for(i=1;i <= *argc; )
	{	found = 0;
		for(j=0;vc_parm[j].string != NULL;j++)
		{	if(strcmp(*((*argv)+i),"--")==0)	/* finish! */
			{	remove_arg(i, argc, argv);
				return errorcode;
			}
			if(optcmp(vc_parm[j].clstring,*((*argv)+i))==0)
				if(vc_parm[j].type & VCONF_CLINE) 
				{	vc_parm[j].set = VCONF_CLINE;
					cptr2 = cptr = strdup(*((*argv)+i));
					cptr2 += strlen(vc_parm[j].clstring);
					if(*cptr2 == '\0') 	/* sep argv */
					{	free(cptr);
						vc_parm[j].value = strdup(*((*argv)+i+1));
						remove_arg(i+1, argc, argv);
					}
					else
						vc_parm[j].value = cptr;
					remove_arg(i, argc, argv);
					found = 1;
				}
				else
				{	errorcode |= error(VCONF_CLINE,&vc_parm[j],errfnct);
					/* does not really make sense, but anyway...*/
					/* why should someone define a parameter that is
					   not allowed ? */
					remove_arg(i, argc, argv);
				}
		}
		i += 1-found;
	}
	return errorcode;
}

/*
**	This routine parses the files and the commandline
**	and returns 0 on success or an error code.
*/

int vconfig(char		*filename,	/* name of conffile without . */
		vconf_parameter *vc_parm,	/* ptr to array of defs	*/
		int 		*argc,		/* as usual */
		char ***argv,
		void * ptr)		/* function to print errormsg */
{
	char		pathname[PATH_MAX];
	int	 	errorcode = 0, i;
	VCONF_ERRFNCT	*errfnct;

	errfnct = (VCONF_ERRFNCT *)ptr;

	strcpy(pathname, vconf_global_prefix);
	strcat(pathname, filename);
	errorcode |= changepermissions(pathname, vc_parm, errfnct);
	errorcode |= parsefile(pathname, vc_parm, VCONF_GLOBAL, errfnct);

	strcpy(pathname, getenv("HOME"));
	strcat(pathname, "/.");
	strcat(pathname, filename);
	errorcode |= parsefile(pathname, vc_parm, VCONF_LOCAL, errfnct);

	errorcode |= parsecmdline(vc_parm, argc, argv, errfnct);
	for(i = 0; vc_parm[i].string != NULL; i++)
		if((vc_parm[i].type & VCONF_MANDATORY) 
			&& (vc_parm[i].set == 0))
			errorcode |= VCONF_MANDATORY;
	return errorcode;
}

