/*
 * Copyright (c) 2000-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * OpenFWTK project legacy API functions - configuration
 *
 * (C) Copyright 2000-2001 ArkanoiD <ark@eltex.ru>
 * (C) Copyright 2000 Eberhard Mattes <em-gw@windhager.de>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/param.h>
#include "firewall.h"

static char* moduleId ATTR_UNUSED = "$Id: cfg.c,v 1.18 2007/09/10 02:49:13 arkenoi Exp $";

static	char	pfile[MAXPATHLEN] = PERMFILE;

extern void strtrm(char *);
extern char* xstrdup();
extern void* xmalloc();
extern void* xrealloc();
extern size_t strlcat(char*,const char*,size_t);
extern size_t strlcpy(char*,const char*,size_t);
extern int   enargv(char *,char **,int,char *,int);

struct config_file
{
  FILE *f;
  const char *name;
  long lineno;
};

struct config_var
{
  char *name;
  char *value;
  struct config_var *next;
};

static struct config_var *config_vars = NULL;


/* Return value: 0=ok, 1=EOF */
int read_config_line (struct config_file *cf, char *dst, size_t dst_size)
{
  int first = 1;
  size_t i = 0;

  for (;;)
    {
      int c = getc (cf->f);
      if (c == EOF)
        {
          if (ferror (cf->f)) {
            syslog(LLEV,"%s: %s", cf->name, strerror(errno));
	    exit(1);
	  }
          if (first) {
	    errno = 0;
	    /* EOF */
            return 1; 
	  }
          syslog (LLEV,"%s: last line not terminated", cf->name);
	  exit(1);
        }

      if (first)
        {
          cf->lineno += 1;
          first = 0;
        }

      if (c == '\n')
        {
          while (i > 0 && isspace ((unsigned char)dst[i-1]))
            --i;
          /* Note: comment lines are not to be continued. Really? */
          if (i == 0 || dst[i-1] != '\\' || dst[0] == '#')
            {
              dst[i] = 0;
              return 0;
            }
          /* Read a continuation line */
          --i;                  /* Overwrite the backslash */
          cf->lineno += 1;
        }
      else if (i + 1 >= dst_size) {
        syslog (LLEV,"%s:%ld: line too long", cf->name, cf->lineno);
	exit(1);
      } else
        dst[i++] = (char)c;
    }
}

int
cfg_setpfile(char *s)
{
	if (strlen(s) >= sizeof(pfile)) {
		syslog(LLEV,"fwtksyserr: netperm-table %.100s does not fit path storage",s);
		return(1);
	}
	strlcpy(pfile,s,sizeof(pfile));
	return(0);
}

static int 
process_var(char *line)
{
	char	*nam, *val;
	struct config_var *cvp, *entry;
	char	tokbuf[MAX_STR];
	char	*av[MAX_ARG];
	int	ac;

	nam = xstrdup(line);

	if (!(val = strchr(nam,'=')))
		return(1);
	*(val++) = '\0';
	strtrm(nam);

	/* We use the first arg only; the purpose of whole ugly thing
	 * is just to make sure quotes and stuff processing is exactly 
	 * similar to one we have elsewhere.
	 */

	if ((ac = enargv(val,av, sizeof(av) / sizeof(char *), tokbuf,sizeof(tokbuf))) != 1 ) 
		return(1);
	
	entry = (struct config_var*) xmalloc(sizeof(struct config_var));
	entry->name = xstrdup(nam);
	entry->value = xstrdup(av[0]);
	entry->next = NULL;

	if (config_vars) {
		for (cvp = config_vars; cvp->next; cvp = cvp->next)
			;
		cvp->next = entry;
	} else
		config_vars = entry;
	free(nam);
	return(0);

}

static char*
get_var(const char* var)
{
	struct config_var *cvp;

	if (*var != '$') {
		syslog(LLEV,"fwtksyserr: internal logic error in macro processing");
		exit(1);
	}
	if (config_vars) {
		for (cvp = config_vars; cvp->next; cvp = cvp->next) {
			if (!strcmp(var+1, cvp->name)) 
				return(cvp->value);
		}
	} 
	syslog(LLEV,"fwtkcfgerr: macro %.128s not found", var);
	exit(1);
}

static int
expand_vars(int ac, char **av, int avsiz, char *tobuf, int bsiz)
{
	char	*newbuf = xmalloc(bsiz);
	char	*op;
	char	*oav[MAX_ARG]; 
	int	len, icnt, ocnt = 0;
	int	recursion_needed = 0;
	
	op = newbuf;
	bzero(newbuf, bsiz);
	bzero(oav, sizeof(oav));
	for (icnt = 0; icnt < ac; icnt++) {
		if(av[icnt][0] != '$') {
			if (op + (len = strlen(av[icnt])) >= newbuf + bsiz) 
				return(-1);
			memcpy(op, av[icnt], len);
			oav[ocnt] = op;
			*(op += len) = '\0'; op++;
			ocnt++;
			if (ocnt >= MAX_ARG) {
				syslog(LLEV,"fwtksyserr: macro reference too complicated");
				exit(1);
			}
		} else {
			char	ttokbuf[MAX_STR];
			char	*tav[MAX_ARG];
			int	tac, ticnt;
			if ((tac = enargv(get_var(av[icnt]),tav,
				 	  sizeof(tav) / sizeof(char *),
					  ttokbuf,sizeof(ttokbuf))) < 0) {
			}
			for (ticnt = 0; ticnt < tac; ticnt++) {
				recursion_needed |= (tav[ticnt][0] == '$'); 
				if (op + (len = strlen(tav[ticnt])) >= newbuf + bsiz) 
					return(-1);
				memcpy(op, tav[ticnt], len);
				oav[ocnt] = op;
				*(op += len) = '\0'; op++;
				ocnt++;
				if (ocnt >= MAX_ARG) {
					syslog(LLEV,"fwtksyserr: macro reference too complicated");
					exit(1);
				}
			}
		}
	}
	memcpy(tobuf, newbuf, bsiz);
	memcpy(av, oav, sizeof(oav));
	if (recursion_needed)
		return(expand_vars(ocnt,av,sizeof(oav) / sizeof(char *),
		       tobuf, bsiz));
	else
		return(ocnt);

	
}

Cfg*
cfg_append(char *cfile,Cfg *chainstart,char *app)
{
	Cfg	*newcfg;
	Cfg	*entry;
	char	buf[MAX_CFG_ENTRY];
	struct	config_file	cfs;

	cfs.lineno = 0;
	cfs.name = strdup(cfile);
	if (!(cfs.f=fopen(cfile,"r"))) {
		syslog(LLEV,"fwtksyserr: cannot open netperm-table file %.512s: %.64s",
		       cfile, strerror(errno));
		exit(1);
	}

	newcfg = chainstart;

	if ((chainstart != (Cfg*) -1) && chainstart) {
		while (newcfg->next)
			newcfg = newcfg->next;
	} else 
		chainstart = NULL;

	while (!read_config_line(&cfs,buf,sizeof(buf))) {

		char	*p;
	 	char	*parms;

		if (buf[0] == '#')
			continue;

		for (p = buf; isspace(*p); p++) ;

		if (!*p)
			continue;

		if (!strncmp(p,"include",7)) {
			/*
			 * Check if include statement is empty
			 */
			if (!*(p+=7)) {
				syslog(LLEV,"fwtkcfgerr: %.200s line %ld malformed",cfile,cfs.lineno);
				continue;
			}
			/*
			 * Check it is not "include<wsp>" but something else 
			 * fall through, it may be a valid tag
			 */
			if (isspace(*p++)) {
				while (isspace(*p))
					p++;
				/*
			 	 * Do include
			 	 */
				chainstart = cfg_append(p, chainstart, app); 
				continue;
			}
		}

		if (!(p = strchr(buf,':')) ) {
			if (process_var(buf))
				syslog(LLEV,"fwtkcfgerr: %.200s line %ld malformed",cfile,cfs.lineno);
			continue;
		}

		*p++ = '\0';
		parms = p;

		/* who cares what tags are there for empty record? 	*/

		if (!*parms) {
			syslog(LLEV,"fwtkcfgerr: %.200s line %ld has no parameters",cfile,cfs.lineno);
			continue;
		}

		p = strtok(buf,",");
		do {
		        char tok[sizeof(buf)];
		        strncpy(tok, p, sizeof(tok));
			strtrm(tok);
			if (!strcasecmp(tok,app) || !strcmp(tok,"*"))
				break;
		} while ((p = strtok(NULL,",")));
		
		strtrm(p);

		if (!p)
			continue;

		if (!process_var(parms))
			continue;

		entry = (Cfg *)xmalloc(sizeof(Cfg)); 

		p = strtok(parms," \t\n");

		entry->flags = 0;
		if (!strncmp(p,"permit-",7)) {
			entry->flags |= PERM_ALLOW;
			entry->op = xstrdup(p+7);
		} else if (!strncmp(p,"deny-",5)) {
			entry->flags |= PERM_DENY;
			entry->op = xstrdup(p+5);
		} else 
			entry->op = xstrdup(p);
		entry->argc = 0;
		entry->argv = (char **) xmalloc(sizeof(char *));
		entry->argv[0] = NULL ;

		if ((p=strtok(NULL,"\n"))) {
			char	tokbuf[MAX_CFG_ENTRY];
			char	*av[MAX_ARG];
			int	ac;
			if ((ac = enargv(p,av,sizeof(av) / sizeof(char *),tokbuf,sizeof(tokbuf))) < 0) {
				syslog(LLEV,"fwtkcfgerr: %.512s line %d too long or misquoted",cfile,(int)cfs.lineno);
				exit(1);
			}
			if ((ac = expand_vars(ac,av,sizeof(av) / sizeof(char *),tokbuf,sizeof(tokbuf))) < 0) {
				syslog(LLEV,"fwtkcfgerr: %.512s line %d buffer overrun on macro processing",cfile,(int)cfs.lineno);
				exit(1);
			}
			for (entry->argc = 0; entry->argc <ac ; entry->argc++) {
				entry->argv[(entry->argc)] = xstrdup(av[entry->argc]);
				entry->argv = (char **) xrealloc(entry->argv,(entry->argc+2)*sizeof(char *));
				entry->argv[entry->argc+1] = NULL;
			}
		}
		entry->ln = cfs.lineno;
		entry->next = NULL;	

		if (!chainstart) 
			chainstart = entry;
		else
			newcfg->next = entry;

		newcfg = entry;
		errno = 0;
	}
	if (errno) {
		syslog(LLEV,"fwtksyserr: error reading configuration file %.100s (%.50s)",cfile, strerror(errno));
		exit(1);
	}
	return(chainstart ? chainstart : (Cfg*)-1 );
}

Cfg*
cfg_read(char *app)
{
	return(cfg_append(pfile,(Cfg*)-1,app));
}

Cfg*
cfg_get(char *fac,Cfg *cp)
{
	static	Cfg*	nextcf;

	if (!cp && !(cp = nextcf))
		return(NULL);
	while (cp && strcasecmp(fac,cp->op))
		cp = cp->next;
	nextcf = cp ? cp->next : NULL;
	return(cp);
}

void
cfg_free(Cfg *cp)
{
	Cfg	*next;
	while (cp) {
		next = cp->next;
		while (*cp->argv)
			free(*((cp->argv)++));
		free(cp->op);
		free(cp);
		cp = next;
	}
}


