/*
 * This code is part of Screws project.
 * Copylefted by pancake@phreaker.net at 2003
 */


#include "Config.h"
#include "Ssl.h"
#include "Args.h"

/* Inside Bitwise Hack */
//#define INSIDE 0x00001000
char *conf_keys[]=
{ "listen",  "env",      "maxthr",    "maxcpu",  "maxmem", "prio", "chroot",
  "setuid",  "seteuid",  "setgid",    "setegid", "whitespace",   "exec",
  "timeout", "chdir",    "strict",    "daemon",  "logfile",      "verbose",
  "console", "backlog",  "noargs",    "falsetimeout", "modopt",
  "maxhdr",  "httplen",  "notimeout", "usleep",  "addssl",       "errfile",
  "nolock",  "io",       "keep",      "logstr",  "nologs",  "enc",
  "addmod",  "noresolv", "docore",    "exectimeout"  };


void
do_whitespace(buf)
	char *buf;
{
	loop_t i;
	for(i=strlen(buf);i;i--)
	{
	if (buf[i]==Svr.whitespace)
		buf[i]=' ';
	}
}

/* Ascii to Integer conversion with some nice stuff */
enum {
	INT_NONE,
	INT_UID,
	INT_GID
};
int
get_int(str,line,u,id)
	char *str;
	int line;
	bool u; /* unsigned? */
	int id; /* 0=NONE,1=UID,2=GID */
{
	struct passwd *pwd;
	struct group *grp;
	register int n=strlen(str),value=atoi(str);

	/* uid */
	if (id==INT_UID)
	{
		pwd=getpwnam(str);
		if (pwd)
		{
			return pwd->pw_uid;
		}
	}

	/* gid */
	if (id==INT_GID)
	{
		grp=getgrnam(str);
		if (grp)
		{
			return grp->gr_gid;
		}
	}

	/* numeric value */
	while(n--) if((str[n]<'0' || str[n]>'9') && str[n]!='-')
	{
		switch(id)
		{
		case INT_UID:
			printf("++ Invalid username '%s'. ",str);
			break;
		case INT_GID:
			printf("++ Invalid groupname '%s'. ",str);
			break;
		case INT_NONE:
			printf("++ Invalid numeric value. ");
			break;
		}
		printf("(wrapped to %d)",value);
			
		if (line) printf(" at line %d.\n", line);
		else printf("\n");
		break;
	}

	/* negative? */
	if (u) if (value<0)
	{	
		printf( "++ Value must be a positive value"
			" (wrapped to 0) (at line %d)\n", line);
		value=0; 
	}
	return value;
}

/* Set a value for a Listen struct */
int
set_listen_val(var,ptr,val,id)
	int var,ptr,val,id;
{
	bool inside=false;
	if (ptr==INSIDE||(ptr|INSIDE))
	{
		ptr&=!INSIDE;
		inside=true;
	}
	switch(ptr)
	{
	case PTRNULL:
		if (Svr.strict) return ERROR_PREVIOUS;
		break;
	case PTRALL:
		for(ptr=0;ptr<Svr.l;ptr++)
		if (Svr.Lis[ptr].name)
		{
			if (inside) 
			{Svr.Lis[ptr].V2[var]=val;
			}
			else {
				Svr.Lis[ptr].V[var]=val;
			}
		}
		break;
	default:
		if (inside) 
		{
			Svr.Lis[ptr].V2[var]=val;
		}
		else 
		{
			Svr.Lis[ptr].V[var]=val;
		}
		break;
	}
	return 0;
}

/* Set a string */
int
set_listen_str(var,ptr,str)
	int var,ptr;
	char *str;
{
	bool inside=false;
	int i;

	if (ptr!=PTRALL&&ptr&INSIDE)
	{
		ptr&=!INSIDE;
		inside=true;
	}
	if (inside && var==_EXEC)
	{
		// XXX Print something here? exec can't be nested
		inside=false;
	}
	
	if (!str) return 0; /* Never must happen */
	do_whitespace(str);
	
	switch(ptr)
	{
	case PTRNULL:
		if (Svr.strict) return ERROR_PREVIOUS;
		break;
	case PTRALL:
		for(ptr=0;ptr<Svr.l;ptr++)
		if (Svr.Lis[ptr].name)
		{
			if (inside) freedup(&Svr.Lis[ptr].S2[var],str);
			else freedup(&Svr.Lis[ptr].S[var],str);
		}
		break;
	default:
		if (inside) freedup(&Svr.Lis[ptr].S2[var],str); 
		else freedup(&Svr.Lis[ptr].S[var],str);
		break;
	}
	return 0;
}

/* Set Default Values for Svr struct */
void
conf_set_defaults()
{
	int v[]={	getuid(), geteuid(), /* uid / euid */
			getgid(), getegid(), /* gid / egid */
			10, 20, -1, -1,      /* prio/maxthr/maxcpu/maxmem */
			3, 0, 666 }; /* timeout / exectimeout / maxheads */
	Svr.strict=Args.strict;
	Svr.whitespace='\\';
	Svr.S[_CHROOT]=strdup("/");
	Svr.S[_CHDIR]=strdup(".");
	Svr.S[_EXEC]=strdup(EXEC);
	Svr.S[_LOGFILE]=strdup(LOGFILE);
	Svr.S[_LOGSTR]=strdup(LOGSTR);
	Svr.S[_ERRFILE]=strdup(ERRFILE);
	memcpy(Svr.V,v,sizeof(Svr.V));
	Svr.l=0;
	Svr.Lis=(struct Listen*)malloc(1);
	Svr.v=0;
	Svr.backlog=10;
	Svr.noargs=false;
	Svr.usleep=100;
	Svr.keep=DEFAULT_KEEP;
	Svr.falsetimeout=false;
	Svr.nolock=false;
#if POLL
	Svr.io=IO_POLL; // USE IO_SELECT||IO_POLL AS DEFAULT ONEs
#else
	Svr.io=IO_SELECT; 
#endif
	Svr.nologs=false;
	Svr.enc=ENC_TLS;
	Svr.resolv=true;
	Svr.signal=SIGTERM;
	Svr.httplen=-1; /* no limit */
	Svr.nenv=0;
	Svr.envval=(char **)malloc(1);
	Svr.envkey=(char **)malloc(1);
}

/* Return the number of listening threads */
int
count_listens()
{
	register int i, ret=0;
	for(i=0;i<Svr.l;i++)
		if (Svr.Lis[i].name) ret++;
	return ret;
}

/* Returns a pointer value by related by a 'string' */
int
get_ptr_by_name(name,line)
	char *name;
	int line;
{
	int i,offs=(name[0]=='@')?1:0,
	    ptr=(offs)?INSIDE:0;
	
	if (!strcmp(name,"@*"))
	{
		return (PTRALL|ptr|INSIDE);
	}
	if (!strcmp(name+offs,"*")) 
	{
		return (PTRALL|ptr);
	}
	for(i=0;i<Svr.l;i++)
	{
		if (Svr.Lis[i].name)
		if (!strcmp(Svr.Lis[i].name,name+offs)) 
		{
			return (i|ptr);
		}
	}
	printf("++ Invalid name identifier at line %d (%s).\n",line,name);
	return PTRNULL;
}

/* Check if directive have enought arguments */
int
conf_check_args(b,n)
	char b[5][BUFSIZE];
	register int n;
{
	register int i;
	for (i=0;i<n;i++) 
		switch(b[i][0])
		{
			case '#':
					b[i][0]=0;
			case 0:
					printf("++ Missing word at arg %d\n",i);
					return 1;
					break;
		}
	return 0;
}

/* Set a value into the Lis struct */
int
set_value(buf,Narg,Kons,Line,u)
	char *buf; /* Buffer with args */
	int Narg,Kons,Line; /* nArgs, Konstan, line */
	bool u; /* unsigned? */
{
	char b[5][BUFSIZE];
	register int ptr=0;
	int id;

	for(ptr=0;ptr<3;ptr++)b[ptr][0]=0;
	sscanf(buf,"%*s %s %s",b[0],b[1]);
	if ( conf_check_args(b,Narg) ) return ERROR_PREVIOUS;
	if (b[1][0]=='#') b[1][0]=0; /* ignore comments */
	if (u==IS_STRING)
	{
		if (!b[1][0]) freedup(&Svr.S[Kons],b[0]);  else
		if (set_listen_str(Kons,
			   	get_ptr_by_name(b[0],Line),b[1] ))
			return ERROR_PREVIOUS;
	} else {
		if (!b[1][0]) Svr.V[Kons]=get_int(b[0],Line,u,Narg); else
		if (set_listen_val(Kons,
       				get_ptr_by_name(b[0],Line),
       				get_int(b[1],Line,u,Narg)) )
	    	return ERROR_PREVIOUS;
	}
	return 0;
}

/* == */
/* parse every K_??? key in a switch while */
/* == */

int
conf_parse_key(line,buf,key)
	int line;
	char *buf;
	unsigned int key;
{
	char b[5][BUFSIZE];
	register int i=0;

	for(i=0;i<5;i++) b[i][0]=0;

	switch(key)
	{
	case K_LISTEN:
		sscanf(buf,"%*s %s %s %s %s",b[0],b[1],b[2],b[3]);
		if ( conf_check_args(b,4) ) return ERROR_PREVIOUS;
		{
		int family=0, ptr=count_listens();
		if (!strcmp(b[0],"inet")) family=AF_INET; else
		if (!strcmp(b[0],"inet6")) 
		{
#if !IPV6
			printf("++ SWS compiled without IPv6 support.\n");
			return ERROR_PREVIOUS;
#endif
			family=AF_INET6;
		} else {
		     printf("++ Invalid protocol type (supported are "
			    "\"inet\" and \"inet6\")\n");
		     return ERROR_PREVIOUS;
		     }
		ptr=count_listens();
		if (ptr>=Svr.l)
		{
			Svr.l++;
			Svr.Lis=(struct Listen*)realloc
			( Svr.Lis, sizeof(struct Listen)*Svr.l );
		} else { /* find a hole */
			for(ptr=0;ptr<Svr.l;ptr++)
			if (!Svr.Lis[ptr].name) break;
		}
		Svr.Lis[ptr].family=family;
		Svr.Lis[ptr].port=get_int(b[2],line,true,0);
		Svr.Lis[ptr].ip=(char *)malloc(strlen(b[1])+1);
			strcpy(Svr.Lis[ptr].ip,b[1]);
		Svr.Lis[ptr].name=(char *)malloc(strlen(b[3])+1);
			strcpy(Svr.Lis[ptr].name,b[3]);
		memcpy(Svr.Lis[ptr].V, Svr.V,sizeof(Svr.V));
		memcpy(Svr.Lis[ptr].V2,Svr.V,sizeof(Svr.V));
		/* All using set_listen_str() */
		Svr.Lis[ptr].S[_CHDIR]=strdup(EXEC_PATH); //Svr.S[_CHDIR])
		Svr.Lis[ptr].S2[_CHDIR]=strdup(Svr.S[_CHDIR]);
		Svr.Lis[ptr].S[_CHROOT]=strdup(Svr.S[_CHROOT]);
		Svr.Lis[ptr].S2[_CHROOT]=strdup(Svr.S[_CHROOT]);
		Svr.Lis[ptr].S[_EXEC]=strdup(Svr.S[_EXEC]);
		Svr.Lis[ptr].S2[_EXEC]=strdup(Svr.S[_EXEC]);
		set_listen_str(_LOGFILE, ptr,        Svr.S[_LOGFILE]);
		set_listen_str(_LOGFILE, ptr|INSIDE, Svr.S[_LOGFILE]);
		set_listen_str(_ERRFILE, ptr,        Svr.S[_ERRFILE]);
		set_listen_str(_ERRFILE, ptr|INSIDE, Svr.S[_ERRFILE]);
		set_listen_str(_LOGSTR,  ptr,        Svr.S[_LOGSTR]);
		set_listen_str(_LOGSTR,  ptr|INSIDE, Svr.S[_LOGSTR]);
#if USESSL
		/* Do not inside SSL */
		set_listen_str(_SSLCRT,  ptr,        "");
		set_listen_str(_SSLKEY,  ptr,        "");
		Svr.Lis[ptr].usessl=false;
#endif
		Svr.Lis[ptr].nenv=Svr.nenv;
		Svr.Lis[ptr].envkey=(char **)malloc(sizeof(void*)*Svr.nenv);
		Svr.Lis[ptr].envval=(char **)malloc(sizeof(void*)*Svr.nenv);
		for(i=0;i<Svr.nenv;i++)
			{
			/* Key */
			int len=strlen(Svr.envkey[i]);
			Svr.Lis[ptr].envkey[i]=(char *)malloc(len+1);
			strncpy(Svr.Lis[ptr].envkey[i],Svr.envkey[i],len);
			/* Value */
			len=strlen(Svr.envval[i]);
			Svr.Lis[ptr].envval[i]=(char *)malloc(len+1); 
        		strncpy(Svr.Lis[ptr].envval[i],Svr.envval[i],len);
			}
		}
		break;
	case K_ENV:
		sscanf(buf,"%*s %s %s %s",b[0],b[1],b[2]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		{
			/* unsetenv */
			if (b[1][0]==0)
			{
				unsetenv(b[0]);
				break;
			}
			do_whitespace(b[1]);
			do_whitespace(b[2]);

			/* global variable */
			if (b[2][0]==0)
			{
				setenv(b[0],b[1],1);
				if (Svr.v>=2)
				printf("ENV: %s -> %s\n",b[0],b[1]);
			} else {
			int ptr=get_ptr_by_name(b[0],line);
			bool inside=false;
               	if (ptr!=PTRALL&&ptr&INSIDE)
               	{
               		ptr&=!INSIDE;
               		inside=true;
               	}
               	switch(ptr)
               	{
               	case PTRNULL:
               		if (Svr.strict) return ERROR_PREVIOUS;
               		break;
               	case PTRALL:
					for(ptr=0;ptr<Svr.l;ptr++)
					{
					Svr.Lis[ptr].nenv++;
					Svr.Lis[ptr].envkey=(char **)realloc(
						Svr.Lis[ptr].envkey, Svr.Lis[ptr].nenv*sizeof(void*));
					Svr.Lis[ptr].envkey[Svr.Lis[ptr].nenv-1]=strdup(b[1]);
					Svr.Lis[ptr].envval=(char **)realloc(
						Svr.Lis[ptr].envval, Svr.Lis[ptr].nenv*sizeof(void*));
					Svr.Lis[ptr].envval[Svr.Lis[ptr].nenv-1]=strdup(b[2]);
					}
					break;
				default:
					if (inside) {
						  fprintf(stderr,
						  "ENV NOT YET SUPPORTED AS GLOBAL INSIDE @%d\n",line);
						} else {
						  Svr.nenv++;
						  Svr.envkey=(char **)realloc(
							Svr.envkey, Svr.nenv*sizeof(void*));
						  Svr.envkey[Svr.nenv-1]=strdup(b[1]);
						  Svr.envval=(char **)realloc(
							Svr.envval, Svr.nenv*sizeof(void*));
						  Svr.envval[Svr.nenv-1]=strdup(b[2]);
						}
						break;
					} /* Switch */
			} /* if/else Global */
		}
		break;
	case K_MAXTHR:
		if (set_value(buf,INT_NONE,_MAXTHR,line,true))	
			return ERROR_PREVIOUS;
		break;
	case K_MAXCPU:
		if (set_value(buf,INT_NONE,_MAXCPU,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_MAXMEM:
		if (set_value(buf,INT_NONE,_MAXMEM,line,true))
			return ERROR_PREVIOUS;
                break;
	case K_PRIO:
		if (set_value(buf,INT_NONE,_PRIO,line,false))
			return ERROR_PREVIOUS;
		break;
	case K_CHROOT:
		if (set_value(buf,INT_NONE,_CHROOT,line,IS_STRING))
			return ERROR_PREVIOUS;
		break;
	case K_SETUID:
		if (set_value(buf,INT_UID,_UID,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_SETEUID:
		if (set_value(buf,INT_UID,_EUID,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_SETGID:
		if (set_value(buf,INT_GID,_GID,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_SETEGID:
		if (set_value(buf,INT_GID,_EGID,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_WHITESPACE:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		if ( strlen(b[0])!=1 )
			printf("++ Invalid strlen in whitespace, getting"
				" only the first char at line %d.\n",line);
		Svr.whitespace=b[0][0];
		break;
	case K_EXEC:
		if (set_value(buf,INT_NONE,_EXEC,line,IS_STRING))
			return ERROR_PREVIOUS;
		break;
	case K_TIMEOUT:
		if (set_value(buf,INT_NONE,_TIMEOUT,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_EXECTIMEOUT:
		if (set_value(buf,INT_NONE,_EXECTIMEOUT,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_CHDIR:
		if (set_value(buf,INT_NONE,_CHDIR,line,IS_STRING))
			return ERROR_PREVIOUS;
		break;
	case K_STRICT:
		if (Svr.strict) printf("++ Strict directive redefined"
				       " at line %d.\n",line);
		else Svr.strict=true;
		break;
	case K_DAEMON:
		Args.daemon=true;
		break;
	case K_LOGFILE:
		if (set_value(buf,INT_NONE,_LOGFILE,line,IS_STRING))
			return ERROR_PREVIOUS;
		break;
	case K_ERRFILE:
		if (set_value(buf,INT_NONE,_ERRFILE,line,IS_STRING))
			return ERROR_PREVIOUS;
		break;
	case K_VERBOSE:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		Svr.v=get_int(b[0],line,true,0);
		break;
	case K_CONSOLE:
		Error("CONSOLE",ERROR_NOTYET);
		break;
	case K_BACKLOG:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		Svr.backlog=get_int(b[0],line,true,0);
		break;
	case K_NOARGS:
		if (Svr.noargs)
			printf(" ++ Noargs directive redefined"
				" at line %d.\n",line);
		Svr.noargs=true;
		break;
	case K_FALSETIMEOUT:
		if (Svr.falsetimeout) 
			printf(" ++FalseTimeout directive redefined"
				" at line %d.\n",line);
		Svr.falsetimeout=true;
		break;
	case K_MAXHDR:
		if (set_value(buf,INT_NONE,_HEADS,line,true))
			return ERROR_PREVIOUS;
		break;
	case K_HTTPLEN:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		Svr.httplen=get_int(b[0],line,false,0);
		break;
	case K_NOTIMEOUT:
		if (Svr.notimeout)
			printf("++ notimeout directive redefined"
				" at line %d.\n",line);
		Svr.notimeout=true;
		break;
	case K_USLEEP:
		Svr.usleep=get_int(b[0],line,true,0);
		break;
#if USESSL
	case K_ADDSSL:
		sscanf(buf,"%*s %s %s %s",b[0],b[1],b[2]);
		if ( conf_check_args(b,2) ) return ERROR_PREVIOUS;
		{
		int ptr=get_ptr_by_name(b[0],line);
		if (ptr==PTRNULL)
		{
			return ERROR_PREVIOUS;
		}
		set_listen_str(_SSLCRT,ptr,b[1]); /* Public Certificate */
		if (b[2][0])
		  set_listen_str(_SSLKEY,ptr,b[2]); /* Private Key */
		else
		  set_listen_str(_SSLKEY,ptr,b[1]); /* same file */
		Svr.Lis[ptr].usessl=true;
		}
		break;
#else
	case K_ADDSSL:
		printf("[E] Screws is not compiled with SSL support. (line:%d)\n",line);
		break;
#endif
	case K_NOLOCK:
		if (Svr.nolock)
			printf("++ nolock directive redefined"
				" at line %d.\n",line);
		Svr.nolock=true;
		break;
	case K_IO:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		if (!strcmp(b[0],"direct")) {
			printf(" [w] direct IO doesn't permits SSL or nested setUID.\n");
			Svr.io=IO_DIRECT;
		} else if (!strcmp(b[0],"poll")) {
			Svr.io=IO_POLL;
		} else if (!strcmp(b[0],"buffer")) {
			Svr.io=IO_BUFFER;
		} else if (!strcmp(b[0],"select")) {
			Svr.io=IO_SELECT;
		}
		break;
	case K_KEEP:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		Svr.keep=get_int(b[0],line,true,0);
		break;
	case K_LOGSTR:
		if (set_value(buf,INT_NONE,_LOGSTR,line,IS_STRING))
               		return ERROR_PREVIOUS;
		break;
	case K_NOLOGS:
		if (Svr.nologs)
			printf("++ nologs directive redefined"
				" at line %d.\n",line);
		Svr.nologs=true;
		break;
	case K_ENC:
		sscanf(buf,"%*s %s",b[0]);
		if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		if (!strcmp(b[0],"ssl2"))
			Svr.enc=ENC_SSL2;
		else
		if (!strcmp(b[0],"ssl3"))
			Svr.enc=ENC_SSL3;
		else
		if (!strcmp(b[0],"ssl23"))
			Svr.enc=ENC_SSL23;
		else
		if (!strcmp(b[0],"tls"))
			Svr.enc=ENC_TLS;
		else {
			printf("[E] Invalid encryptation '%s' at line %d.\n",
					b[0],line);
			return ERROR_PREVIOUS;
		}
		break;
#if DLOPEN
	case K_ADDMOD:
		sscanf(buf,"%*s %s",b[0]);
               	if ( conf_check_args(b,1) ) return ERROR_PREVIOUS;
		if (Svr.v>=V_LOAD)
		{
		 printf("[m] Loading module '%s'\n",b[0]);
		}
		if (Svr.strict & ! screws_module_add(b[0]) )
		{
			return ERROR_PREVIOUS;
		}
		break;
	case K_MODOPT:
		{
		int i=0;
		b[0][0]=0;
		b[1][0]=0;
		sscanf(buf,"%*s %s %s",b[0],b[1]);
		if ( conf_check_args(b,2) ) return ERROR_PREVIOUS;
		for(i=0;b[1][i];i++)
			if (b[1][i]==Svr.whitespace) b[1][i]=' ';
		if (! screws_module_option(b[0],b[1]) )
			{
			printf("[E] Invalid module name \"%s\" at line %d.\n",
					b[0],line);
			}
		}
		break;
#endif
	case K_NORESOLV:
		Svr.resolv=false;
		break;
	case K_DOCORE:
		Svr.signal=SIGSEGV;
		break;
	default:
		printf("++ Unrecognized directive \"%s\" at line %d.\n",
		conf_keys[key],line);
		if (Svr.strict) exit(1);
		break;
	}
	return 0;
}



/*
 *
 * Main Readconf loop
 * 
 */


int
ReadConfig(file,reset)
	const char *file;
	int reset; // for external console (runtime reload)
{
	FILE *f=NULL;
	int  i=0,
	     err=ERROR_VOID,
	     line=0;
	char *p,buf[2][BUFSIZE];

	if (!strcmp(file,"-"))
		f=stdin;
	else 
		f=fopen(file,"r");
	if (!f) 
	{
		return (ERROR_CONFIGNOTFOUND);
	}

	if (reset) conf_set_defaults();

	// Just while a feof
	while(!feof(f)&&!err)
	{
		buf[1][0]=0;
		fgets(buf[0],BUFSIZE,f);
		if (p=strchr(buf[0],'#'))
				p[0]=0;
		sscanf(buf[0],"%s",buf[1]);
		line++;
		if (buf[1][0]=='#'||!buf[1][0]) continue;
		i=K_LASTONE;
		while(i--)
		{
			if (!strcmp(conf_keys[i],buf[1]))
			{
				err=conf_parse_key(line,buf,i);
				break;
			}
		}
		if (i<0||err)
		{
			printf("** Error ocurred on directive '%s' at line %d.\n",
					buf[1], line);
			err=ERROR_PREVIOUS;
		}
	}
	
	fclose(f);
	if (Svr.strict) return err;
	else 			return ERROR_VOID;
}
