
static char rcsid[] ="@(#)$Id: parse.c,v 4.1 90/04/28 22:41:58 syd Exp $";

/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 4.1 $   $State: Exp $
 *
 * 			Copyright (c) 1986, 1987 Dave Taylor
 * 			Copyright (c) 1988, 1989, 1990 USENET Community Trust
 *******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 *
 *	Syd Weinstein - elm@DSI.COM
 *			dsinc!elm
 *
 *******************************************************************************
 * $Log:	parse.c,v $
 * Revision 4.1  90/04/28  22:41:58  syd
 * checkin of Elm 2.3 as of Release PL0
 * 
 *
 ******************************************************************************/


/** This is the parser for the filter program.  It accepts a wide variety of
    constructs, building the ruleset table as it goes along.  Check the 
    data structure in filter.h for more information on how the rules are
    stored.  The parser is a cunning state-table based program.

**/

#include <stdio.h>
#include <ctype.h>

#include "defs.h"
#include "filter.h"

#define NONE			0
#define AND			10

#define NEXT_CONDITION		0
#define GETTING_OP		1
#define READING_ARGUMENT	2
#define READING_ACTION		3
#define ACTION_ARGUMENT		4

char *strtok(), *whatname(), *actionname();

int
get_filter_rules()
{
	/** Given the users home directory, open and parse their rules table,
	    building the data structure as we go along.
	    returns -1 if we hit an error of any sort...
	**/

	FILE *fd;				/* the file descriptor     */
	char  buffer[SLEN], 			/* fd reading buffer       */
	      *str, 				/* ptr to read string      */
	      *word,				/* ptr to 'token'          */
	      filename[SLEN], 			/* the name of the ruleset */
	      action_argument[SLEN], 		/* action arg, per rule    */
	      cond_argument[SLEN];		/* cond arg, per condition */
	int   not_condition = FALSE, 		/* are we in a "not" ??    */
	      type=NONE, 			/* what TYPE of condition? */
	      lasttype, 			/* and the previous TYPE?  */
	      state = NEXT_CONDITION,		/* the current state       */
	      in_single, in_double, 		/* for handling spaces.    */
	      i, 				/* misc integer for loops  */
	      relop = NONE,			/* relational operator     */
	      action, 				/* the current action type */
	      buflen,				/* the length of buffer    */
	      line = 0;				/* line number we're on    */

	struct condition_rec	*cond, *newcond;

	sprintf(filename,"%s/%s", home, filterfile);

	if ((fd = fopen(filename,"r")) == NULL) {
	  if (outfd != NULL)
	   fprintf(outfd,"filter (%s): Couldn't read user filter rules file!\n",
		  username);
	  return(-1);
	}

	cond_argument[0] = action_argument[0] = '\0';

	/* Now, for each line... **/

	if ((cond = (struct condition_rec *) 
		     malloc(sizeof(struct condition_rec))) == NULL) {
	  if (outfd != NULL)
	    fprintf(outfd,"filter (%s): couldn't malloc first condition rec!\n",
		    username);
	  return(-1);
	}
	
	rules[total_rules].condition = cond;	/* hooked in! */

	while (fgets(buffer, SLEN, fd) != NULL) {
	  line++;

	  if (buffer[0] == '#' || (buflen = strlen(buffer)) < 2)
	    continue;		/* nothing to look at! */

	  in_single = in_double = 0;

	  for (i=0; i < buflen; i++) {
	    if (buffer[i] == '"') 
	      in_double = ! in_double;
	    else if (buffer[i] == '\'')
	      in_single = ! in_single;
	    if ((in_double || in_single) && buffer[i] == ' ')
	      buffer[i] = '_';
	  }

	  lasttype = type;
	  type = NONE;
	  str = (char *) buffer;

	  /** Three pieces to this loop - get the `field', the 'relop' (if
	      there) then, if needed, get the argument to check against (not 
	      needed for errors or the AND, of course)
	  **/

	  while ((word = strtok(str, " ()[]:\t\n")) != NULL) {

	    str = (char *) NULL;		/* we can start stomping! */
	  
	    lowercase(word);

	    if (strcmp(word, "if") == 0) {	/* only ONE 'if' allowed */
	      if ((word = strtok(str, " ()[]:\t\n")) == NULL)	/* NEXT! */
	        continue;
	      lowercase(word);
	    }
	
	    if (state == NEXT_CONDITION) {
	      lasttype = type;
	      type = NONE;

	      if (the_same(word, "not") || the_same(word, "!")) {
	        not_condition = TRUE;
	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	          continue;
	      }

	           if (the_same(word, "from")) 	    type = FROM;
	      else if (the_same(word, "to")) 	    type = TO;
	      else if (the_same(word, "subject"))   type = SUBJECT;
	      else if (the_same(word, "lines"))     type = LINES;
	      else if (the_same(word, "contains"))  type = CONTAINS;
	      else if (the_same(word, "and") || 
	               the_same(word, "&&")) 	    type = AND;

	      else if (the_same(word,"?") || the_same(word, "then") || 
		       the_same(word, "always")) {

		/** shove THIS puppy into the structure and let's continue! **/

	        if (lasttype == AND) {
		  if (outfd != NULL)
	            fprintf(outfd,
         "filter (%s): Error reading line %d of rules - badly placed \"and\"\n",
		    username, line);
		  return(-1);
	        }

	        if (the_same(word, "always"))
		  cond->matchwhat = ALWAYS;	/* so it's a hack... */
		else
		  cond->matchwhat = lasttype;

	        if (relop == NONE) relop = EQ;	/* otherwise can't do -relop */
	        cond->relation  = (not_condition? - (relop) : relop);

		for (i=strlen(cond_argument); --i >= 0;)
	          if (cond_argument[i] == '_') cond_argument[i] = ' ';

		strcpy(cond->argument1, cond_argument);
	        if ((newcond = (struct condition_rec *)
		     malloc(sizeof(struct condition_rec))) == NULL) {
		  if (outfd != NULL)
	            fprintf(outfd,
		     	    "filter (%s): Couldn't malloc new cond rec!!\n",
		            username);
		  return(-1);
	        }
	        cond->next = NULL;

	        relop = EQ;	/* default relational condition */

	        state = READING_ACTION;
	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	          continue;
	        goto get_outta_loop;
	      }

	      if (type == NONE) {
		if (outfd != NULL)
	          fprintf(outfd,
      "filter (%s): Error reading line %d of rules - field \"%s\" unknown!\n",
		     username, line, word);
		return(-1);
	      }

	      if (type == AND) {

		/** shove THIS puppy into the structure and let's continue! **/

		cond->matchwhat = lasttype;
	        cond->relation  = (not_condition? - (relop) : relop);
		strcpy(cond->argument1, cond_argument);
	        if ((newcond = (struct condition_rec *)
	             malloc(sizeof(struct condition_rec))) == NULL) {
		  if (outfd != NULL)
	            fprintf(outfd,
			"filter (%s): Couldn't malloc new cond rec!!\n",
			username);
		  return(-1);
	        }
	        cond->next = newcond;
		cond = newcond;
		cond->next = NULL;

	        not_condition = FALSE;
	        state = NEXT_CONDITION;
	      }
	      else {
	        state = GETTING_OP;
	      }
	    }

get_outta_loop: 	/* jump out when we change state, if needed */

	    if (state == GETTING_OP) {

	       if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	         continue;

	       lowercase(word);

	       relop = NONE;

	       if (the_same(word, "=") || the_same(word, "in") || 
                   the_same(word, "contains")) {
                 state = READING_ARGUMENT;
	         relop = EQ;
	       }
	       else {
	         if (the_same(word, "<=")) 	relop = LE;
	         else if (the_same(word, ">="))	relop = GE;
	         else if (the_same(word, ">"))	relop = GT;
	         else if (the_same(word, "<>")||
		          the_same(word, "!="))	relop = NE;
	         else if (the_same(word, "<"))	relop = LT;

		 /* maybe there isn't a relop at all!! */

		 state=READING_ARGUMENT;

	       }
	    }
		 
	    if (state == READING_ARGUMENT) {
	      if (relop != NONE) {
	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	          continue;
	      }
	      for (i=strlen(word); --i>=0;)
	        if (word[i] == '_') word[i] = ' ';

	      strcpy(cond_argument, word);
	      state = NEXT_CONDITION;
	    }

	    if (state == READING_ACTION) {
	      action = NONE;

	      not_condition = FALSE;

	      if (the_same(word, "delete"))       action = DELETE_MSG;
	      else if (the_same(word, "savec"))   action = SAVECC;
	      else if (the_same(word, "save"))    action = SAVE;
	      else if (the_same(word, "forward")) action = FORWARD;
	      else if (the_same(word, "exec"))    action = EXEC;
	      else if (the_same(word, "leave"))   action = LEAVE;
	      else {
		if (outfd != NULL)
	          fprintf(outfd,
	"filter (%s): Error on line %d of rules - action \"%s\" unknown\n",
			username, line, word);
	      }

	      if (action == DELETE_MSG || action == LEAVE) {
	        /** add this to the rules section and alloc next... **/

	        rules[total_rules].action = action;
		rules[total_rules].argument2[0] = '\0';	/* nothing! */
	        total_rules++;
	         
	        if ((cond = (struct condition_rec *)
		     malloc(sizeof(struct condition_rec))) == NULL) {
		  if (outfd != NULL)
	            fprintf(outfd,
			"filter (%s): couldn't malloc first condition rec!\n",
			username);
	          return(-1);
	        }
	
	        rules[total_rules].condition = cond;	/* hooked in! */
	        state = NEXT_CONDITION;	
	      }
	      else {
	        state = ACTION_ARGUMENT;
	      }

	      if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	        continue;

	    }
	
	    if (state == ACTION_ARGUMENT) {
	      strcpy(action_argument, word);

	      /** add this to the rules section and alloc next... **/

	      rules[total_rules].action = action;
	      expand_macros(action_argument, rules[total_rules].argument2,line,
			    printing_rules);
	      total_rules++;
	         
	      if ((cond = (struct condition_rec *)
		     malloc(sizeof(struct condition_rec))) == NULL) {
		if (outfd != NULL)
	          fprintf(outfd,
			  "filter (%s): couldn't malloc first condition rec!\n",
			  username);
	        return(-1);
	      }
	
	      rules[total_rules].condition = cond;	/* hooked in! */

	      state = NEXT_CONDITION;
	      if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
	        continue;
	    }
	  }
	}

	return(0);
}
