/*
**
** Copyright (C) 1994 Swedish University Network (SUNET)
**
**
** This program is developed by UDAC, Uppsala University by commission
** of the Swedish University Network (SUNET). 
**
** 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 of the License, 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 FITTNESS 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.
**
**
**                                        Martin.Wendel@udac.uu.se
**                                        Torbjorn.Wictorin@udac.uu.se
**
**                                        UDAC	
**                                        P.O. Box 174
**                                        S-751 04 Uppsala
**                                        Sweden
**
*/


%{
	/* getoption.c */

#pragma	alloca	/* For AIX */

#include	<assert.h>
#include	"emil.h"

#ifndef	NO_FNMATCH
#include	<fnmatch.h>
#ifndef	FNM_NOMATCH
#define	FNM_NOMATCH	1	/* This MAY work!  941223/TW */
#endif
#else
static	int	ematch(char * p, char * s)
{
	/* Returns 0 if match, 1 else. '*' is a wildcard */
	/* Suggest that you install fnmatch instead...   */
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "-   ematch (no fnmatch).\n");
#endif
  for (;*p;p++) 
    if (*p == '*') 
      {
	p++; 
	if (*p=='\0') 
	  return 0;
	while (*s != '\0') 
	  {
	    if (ematch(p, s) == 0) 
	      return 0; 
	    else 
	      s++;
	  }
	return	1;
      } 
    else 
      if (*p != *(s++)) 
	return 1;
  return *s;
}
#endif

static	int	line_count;
static	int	error_count;	/* used in lex */
static	int	should_load_config_file	= 1;
extern	FILE *	conf_fd;

void	* Yalloc(int siz)
{
	void	* Y;
	static	off_t	len	= 0,	/* Of allocated segment */
			offset	= 0,	/* Amount used thereof	*/
			pz;		/* Pagesize		*/
	static	char	* where;
        char ebuf[256];

	assert(siz > 0); siz     = ((siz + 3) / 4) * 4;	/* Word align */
	if ((len - offset) < siz) {

#ifdef	HAVE_GETPAGESIZE
		if (len==0)	len	= pz = getpagesize();
#elif	hpux
		if (len==0)     len	= pz	= 4096;
#else
		if (len==0)	len	= pz = sysconf(_SC_PAGESIZE);
#endif
		else		len	+= pz;
		if (len < siz)	len	= siz;
		/*
		 sprintf(ebuf,"Yalloc: len=%d offset=%d l-d=%d siz=%d",
			len, offset, len-offset, siz);
		 logger(LOG_DEBUG,ebuf);
		*/
		where	= malloc(len);
		if (where == NULL) {
			sprintf(ebuf,"getoption cannot get memory (%d) %m",len);
			logger(LOG_ERR,ebuf);
			exit(1);
		}
		Y	= where;
		offset	= siz;
	}
	else {
		Y	= &where[offset];
		offset	+= siz;
	}
	bzero(Y,siz);
	return	Y;
}

#define	N_CONTEXT_TYPES	3	/* Mime, UUencode, Applefile */

struct	member_struct	{
	struct	member_struct	* mm;	/* Points to next      */
	char	*	name;	/* Of the config group	       */
	char	*	r;	/* Receiver selection criteria */
	char	*	s;	/* Sender   selection criteria */
	char	*	rmx;	/* Receiver MX        criteria */
};

struct	lookup_struct {
	struct	lookup_struct	* ll;
	char	* matchstring;
	char	* out;
};

struct	lookup_struct	* table_start[N_CONTEXT_TYPES], 
			* table_end[N_CONTEXT_TYPES];

static	struct	config_struct	* g_start, /* Points to first group structure  */
				* g_end,   /* Points to the last group structure */	
				* gtmp;

static	struct	member_struct	* m_start, /* first member in chain */
				* m_end,   /* Last member in chain  */
				* mtmp;

static	char	* group_name;

static	int	Ycmp(char * pattern, char * string)
{
	char	p[255], s[255]; char	* c, *d; int i;
	int	cc;

	/* Copy / fold */

	for (c=pattern,d=p,i=(sizeof(p)-2); *c && i > 0; c++,d++,i--) *d = * c; *d = 0;
	for (c=string, d=s,i=(sizeof(s)-2); *c && i > 0; c++,d++,i--) *d = * c; *d = 0;

	/* Compare */

#ifndef	NO_FNMATCH
	cc = fnmatch(pattern,string,0) != FNM_NOMATCH; /* 941223/TW */
#else
	cc = ematch(pattern,string) == 0;
#endif
#ifdef DEBUG
	if (edebug)
	  {
	    if (cc)
	      fprintf(stderr, "+   Ycmp: %s matches %s.\n", string, pattern);
	    else
	      fprintf(stderr, "+   Ycmp: %s does not match %s.\n", string, pattern);
	  }
#endif
	return	cc;
}

extern 	char	* yytext;	/* defined by flex */

/* static	int	yywrap()
	{
		return	1;
	}
*/

#define	yywrap()	1

static	void	yyerror(char * msg)
{
	char	ebuf[2048];
	sprintf(ebuf,"Config file: %s at line %d around '%s'\n",msg,line_count+1, yytext);
	logger(LOG_ERR,ebuf);
	error_count++;
}
%}

%union {
	char * string;
	int	val;
}

%token	COLON COMMA EQUALS CHARSET TEXTENC HENC FORMAT BIN GROUP MEMBER EOR MATCH
%token	<string> STRING QSTRING

%start expressions

%%

expressions	: /* Empty */
		| expressions expression
		;

expression	: GROUP STRING	COLON {
			/* this backward method is becase we want to look at first hit */
			gtmp	= g_end;
			g_end  	= Yalloc(sizeof(struct config_struct));
			if (g_start == NULL) 	g_start	= g_end;
			else			gtmp->gg= g_end;
			strcpy((g_end->name=Yalloc(strlen($2)+1)),$2);
		  }
		  varvals EOR

		| MEMBER STRING COLON {
			strcpy((group_name=Yalloc(strlen($2)+1)),$2);
		  }
		  users	EOR

		| MATCH STRING QSTRING STRING EOR {
			/* Add a entry to match table */
			int	ctyp;
			struct	lookup_struct	* ttmp, ** ts, ** te;
			if	(strcasecmp($2,"MIME")==0)	ctyp	= 0;
			else if	(strcasecmp($2,"UUENCODE")==0)	ctyp	= 1;
			else if	(strcasecmp($2,"APPLEFILE")==0)	ctyp	= 2;
			else	{
				yyerror("Wrong context type for match");
				YYERROR;
			}
			ts = &table_start[ctyp]; te = &table_end[ctyp];
			ttmp		= *te;
			*te		= Yalloc(sizeof(struct lookup_struct));
			if (*ts == NULL)  *ts	= *te;
			else		ttmp->ll= *te;
			strcpy(((*te)->matchstring=Yalloc(strlen($3)+1)),$3);
			strcpy(((*te)->out=Yalloc(strlen($4)+1)),$4);
		}
		;

varvals		: varval
		| varval COMMA varvals
		;

varval		: CHARSET EQUALS STRING	{
			strcpy((g_end->charset	= Yalloc(strlen($3)+1)),$3);
		  }
		| FORMAT  EQUALS STRING {
			strcpy((g_end->format	= Yalloc(strlen($3)+1)),$3);
		  }
		| BIN     EQUALS STRING {
			strcpy((g_end->bin	= Yalloc(strlen($3)+1)),$3);
		  }
		| TEXTENC EQUALS STRING {
			strcpy((g_end->text	= Yalloc(strlen($3)+1)),$3);
		  }
		| HENC EQUALS STRING {
			strcpy((g_end->header	= Yalloc(strlen($3)+1)),$3);
		  }
		;

users		: user_definition
		| users COMMA user_definition
		;

		  /* R      S     RMX  */
user_definition	: STRING STRING STRING 
		  {
			mtmp	= m_end;
			m_end	= Yalloc(sizeof(struct	member_struct));
			if (m_start == NULL)	m_start	= m_end;
			else			mtmp->mm= m_end;
			m_end->name		= group_name;
			strcpy((m_end->r	= Yalloc(strlen($1)+1)),$1);
			strcpy((m_end->s	= Yalloc(strlen($2)+1)),$2);
			strcpy((m_end->rmx	= Yalloc(strlen($3)+1)),$3);
		  }
		;

%%

#include	"lex.yy.c"

struct	config_struct *	getoption(char * r, char * s, char * rmx)
{
	struct	member_struct	* mm;
	struct	config_struct	* gg;

#ifdef	YYDEBUG
	extern	int	yydebug;
	yydebug	= 1;
#endif

	if (should_load_config_file) {
		should_load_config_file = 0;
		if ((yyin = conf_fd)          == NULL &&
		    (yyin = fopen(MAINCF,"r"))==NULL) {
			char	ebuf[512];
			strcpy(ebuf,"Cannot open '");
			strcat(ebuf,MAINCF);
			strcat(ebuf,"' ");
			logger(LOG_ALERT,ebuf);
			return NULL;
		}
		if (yyparse())	return	NULL;
		fclose(yyin);
	}

	/* Look for member */

	for (mm = m_start; mm; mm = mm->mm)		/* Lookup member */
		if (Ycmp(mm->r,r) && Ycmp(mm->s,s) && Ycmp(mm->rmx,rmx))
			for (gg = g_start; gg; gg = gg->gg) /* Lookup group */
				if (strcasecmp(gg->name,mm->name)==0) 
					return gg;
	return	NULL;	/* group or member not found */
}

static	void	confextr_error(char * txt, char * context, char * match, char * output)
{
	char	buf[255];
	strcpy(buf,txt);
	strcat(buf," confextr(\"");
	if (context) strcat(buf,context); else strcat(buf,"<NULL>");
	strcat(buf,"\", \"");
	if (match) strcat(buf,match); else strcat(buf,"<NULL>");
	strcat(buf,"\", \"");
	if (output) strcat(buf,output); else strcat(buf,"<NULL>");
	strcat(buf,"\")");
	yyerror(buf);
}

char	* confextr(char * context, char * match, char * output)
{
	int	context_type;
	struct	lookup_struct	* l;

	if (should_load_config_file) {
		should_load_config_file = 0;
		if ((yyin = conf_fd)          == NULL &&
		    (yyin = fopen(MAINCF,"r"))==NULL) {
			perror("cannot open config file");
			return NULL;
		}
		if (yyparse())	return	NULL;
		fclose(yyin);
	}

	if 	(context == NULL || 
		(match == NULL && output == NULL) ||
		(match != NULL && output != NULL))	{
		confextr_error("Confextr: Invalid parameter combination",
			context,match,output);
		return NULL;
	}

	if	(strcasecmp(context,"MIME")==0)		context_type	= 0;
	else if	(strcasecmp(context,"UUENCODE")==0)	context_type	= 1;
	else if	(strcasecmp(context,"APPLEFILE")==0)	context_type	= 2;
	else	{
		confextr_error("Confextr: invalid context", context, match, output);
		return NULL;
	}

	if (match == NULL) {
		if (output == NULL)	return NULL;
		for (l = table_start[context_type]; l; l = l->ll)
			if (strcasecmp(l->out,output)==0) return	l->matchstring;
		confextr_error("Confextr: invalid match", context, match, output);
		return	NULL;
	}
	else if (output == NULL) {
		if (match == NULL)	return NULL;
		for (l = table_start[context_type]; l; l = l->ll)
			if (strcasecmp(l->matchstring,match)==0) return	l->out;
		confextr_error("Confextr: invalid output", context, match, output);
		return	NULL;
	}
	else	{
		confextr_error("Confextr: both match & output set", context, match, output);
		return	NULL;
	}
}
