/* 1740, Fri 8 Dec 00 (PST)

   NMC_PARS.C:  Scanner/parser for nmc config and rules files

   Copyright (C) 1992-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: nmc_pars.c,v $
 * Revision 1.1.1.2.2.14  2002/02/23 01:57:21  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.12  2001/05/24 02:19:44  nevil
 * LfapMet implemented by Remco Poortinga.
 * MinPDUs implemented by Nevil.
 *
 * Revision 1.1.1.2.2.9  2000/11/29 21:40:10  nevil
 * Bug fixes for stream / packet queue handling
 *
 * Revision 1.1.1.2.2.8  2000/08/21 01:09:14  nevil
 * Don't let getarg() result contain semicolons
 *
 * Revision 1.1.1.2.2.7  2000/08/08 19:44:45  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.5  2000/06/06 03:38:12  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.2  2000/01/12 02:57:04  nevil
 * Implement 'packet pair matched' turnaroundtime distribution attributes.
 * Fix ASN-related bugs in NeTraMet, distribution-related bugs in fd_filter.
 *
 * Revision 1.1.1.2.2.1  1999/11/29 00:17:21  nevil
 * Make changes to support NetBSD on an Alpha (see version.history for details)
 *
 * Revision 1.1.1.2  1999/10/03 21:06:18  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.8  1999/09/22 05:38:36  nevil
 * Improve code to work properly on 64-bit machines
 * - Add OS=ALPHA handling to configure.in
 * - Clean up the Alpha compiler warnings
 * - Change all the snmp-related code to use Bit32 instead of unsigned long
 *
 * Revision 1.1.1.1.2.7  1999/09/14 00:46:47  nevil
 * 4.3 Release ..
 *  - Implement -D option to run NeMaC as a daemon
 *  - Tidy up the on-line help displays
 *  - Make IPv4 the default PeerType for sfmt_attribute()
 *
 * Revision 1.1.1.1.2.6  1999/05/18 03:36:26  nevil
 * Implement IPv6 in NeTraMet, and its manager/collectors.
 * - This is controlled by the V6 #define
 * - NeTraMet recognises v6 packets and fishes through their extension
 *     headers until it finds the actual payload.
 * - NeMaC et al display v6 addresses in the fom specified by RFC 2373
 * - fd_util and fd_extract allow colons in addresses (for defining tags)
 *
 * Revision 1.1.1.1.2.5  1999/02/03 04:41:43  nevil
 * Implementation of TCP attributes, part 5
 *
 * Revision 1.1.1.1.2.4  1999/01/27 04:26:14  nevil
 * Minor corrections to fix compiler warnings
 *
 * Revision 1.1.1.1.2.3  1999/01/20 04:01:34  nevil
 * Implementation of TCP attributes, part 4
 *
 * Revision 1.1.1.1.2.2  1999/01/08 01:38:32  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1.2.1  1998/11/25 03:33:23  nevil
 * Implement tcpdata (part 2)
 *
 * Revision 1.1.1.1  1998/11/16 03:57:27  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1.2.2  1998/11/13 01:43:24  nevil
 * Test implementation of distribution-valued attributes used a special
 * parse routine (getdistparams) for their value and mask.  This has
 * been deleted now that SRL makes it easy to write rules using them.
 *
 * Revision 1.1.1.1.2.1  1998/11/11 23:14:39  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:25  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.1  1998/10/23 03:56:05  nevil
 * Change log_msg() to write to stderr if no log file is available
 *
 * Revision 1.1.3.2  1998/10/18 23:44:10  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:24  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:28  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.11  1998/07/21 00:43:57  rtfm
 * Change attrib numbers for 'New Attribs' I-D
 * First release version of SRL
 *
 * Revision 1.4  1998/04/23 03:17:52  rtfm
 * Added final add_rule(*, NULL) call to flush rule download buffer
 *
 * Revision 1.3  1998/04/22 22:23:19  kawai
 * Added checks to prevent buffer over runs in get_args
 * All errors and debugging output now go to stderr
 *
 * Revision 1.2  1998/03/13 03:54:37  kawai
 * -Replace check_log by log_msg which does the complete processing
 *  of log messages
 * -Added some more diagnostics for failed fopens
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#define RULETEST_DEBUG  0

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>

#ifdef _AIX
#define _XOPEN_EXTENDED_SOURCE  /* Needed to get syslog defined properly */
#endif
#include <syslog.h>
#ifdef _AIX
#undef _XOPEN_EXTENDED_SOURCE
#endif

#include <string.h>
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <time.h>

#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "ausnmp.h"

#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"
#include "snmpclnt.h"
#include "mib.h"

#include "nmc.h"
#include "nmc_c64.h"


void log_msg(int priority, int die, char *fmt, ...)
{
   va_list ap;
   time_t t;

   if (testing) {
      va_start(ap, fmt);
      fprintf(stderr, "<><><> ");
      vfprintf(stderr, fmt, ap);
      }
#ifdef LOG_LOCAL
   va_start(ap, fmt);
   if (syslog_name[0]) {
      vsyslog(priority, fmt, ap);
      }
#endif
   if (logfile[0] != '\0') {
      if (append_log && (logfl = fopen(logfile,"a")) == 0) {
         if (syslog_name[0])
            syslog(LOG_ERR, "Failed to open logfile '%s':%s\n",
	    logfile, strerror(errno));
	 else fprintf(stderr,"Failed to open logfile '%s':%s\n",
	    logfile, strerror(errno));
         }
      if (logfl == NULL)  /* Couldn't open log file */
         fprintf(stderr, "failed to open %s %s\n",logfile, strerror(errno));
      }
   if (logfl == NULL) {  /* No log file available */
      logfl = stderr;
      append_log = 0;
      }

   time(&t);
   fprintf(logfl, "%s -- ", fmt_time(&t));
   va_start(ap, fmt);
   vfprintf(logfl, fmt, ap);  /* Was fprintf!  Nevil, 10 Sep 98 */
   va_end(ap);

   if (append_log) fclose(logfl);
   else fflush(logfl);

   if (die) exit(die);
   }

void mswait(unsigned int ms)  /* Wait for ms milliseconds */
{
   struct timeval timeout;
   timeout.tv_sec = ms/1000;  timeout.tv_usec = (long)(ms%1000)*1000L;
   select(FD_SETSIZE, 0, 0, 0, &timeout);
   }

FILE *wfopen(char *lfn, char *fn)  /* Open next file in sequence for write */
{
   int n;
   FILE *f;
   for (n = 1; ; ++n) {
      sprintf(lfn,"%s.%03d",fn,n);
      if ((f = fopen(lfn,"r")) == NULL) {
         if ((f = fopen(lfn,"w")) != NULL) return f;

         if (syslog_name[0])
            syslog(LOG_ERR, "Failed to open logfile '%s':%s\n",
       	    lfn, strerror(errno));
	 else fprintf(stderr,"Failed to open logfile '%s':%s\n",
            lfn, strerror(errno));
         exit(0);
         }
      fclose(f);
      }
   }

char *strmov(char *d, char *s)
{
   while (*s != '\0') *d++ = *s++;
   return d;
   }

char *gnbr(unsigned int *n, char *s)
   /* Get nbr from 's', return updated 's' */
{
   unsigned int v = 0, b = 10, d;
   while (*s == ' ') ++s;
   if (*s == 'x' || *s == 'X') {
      ++s;  b = 16;
      }
   else if (*s == '0') {
      ++s;  b = 8;
      }
   for (;;) {
      d = *s;
      if (b == 16) {
         if (!isxdigit(d)) break;
         }
      else {
         if (!isdigit(d)) break;
         if (b == 8 && d > '7') break;
         }
      if (d <= '9') d -= '0';
      else if (d <= 'F') d -= ('A'-10);
      else d -= ('a'-10);
      v = v*b + d;  ++s;
      }
   *n = v;  return s;
   }

char *gcstring(char *s, int *len)  /* Get string from s */
{
   static char escin[] = {
      'b', 'f', 'n', 'r', 't', 'v','\\','\'','\"','\?', 0 };
   static char escout[] ={
      '\b','\f','\n','\r','\t','\v','\\','\'','\"','\?' };
   unsigned int c,j;
   char sbuf[80], *t = sbuf, *rp;
   while (*s) {
      if (*s == '\\') {
	 ++s;  c = *s;
	 if (c == '0' || c == 'x' || c == 'X') {  /* Octal or hex nbr */
	    s = gnbr(&c,s);  *t++ = c;
	    }
	 else if (isdigit(c)) {  /* Octal number */
	    --s;  *s = '0';  /* Make gnbr use base 8 */
	    s = gnbr(&c,s);  *t++ = c;
	    }
	 else {
	    j = 0;  do {
	       if (c == escin[j]) break;
	       } while (escin[++j] != 0);
	    if (escin[j] != 0) {
	       *t++ = escout[j];  ++s;
	       }
	    }
	 }
      else *t++ = *s++;
      }
   *t = '\0';  *len = t-sbuf;
    rp = (char *)malloc(*len+1);  strcpy(rp,sbuf);
   return rp;
   }

static char ftb[32];

char *fmt_time(time_t *ti)
{
   if (!utc_time) {
      char *ts = ctime(ti);
      strncpy(&ftb[0],  &ts[11], 9);  /* 17:31:42_ */
      strncpy(&ftb[9],  &ts[0],  4);  /* Thu_ */
      strncpy(&ftb[13], &ts[8],  3);  /* 23_  */
      strncpy(&ftb[16], &ts[4],  4);  /* Sep_ */
      strncpy(&ftb[20], &ts[20], 4);  /* 1997 */
      ftb[24] = '\0';
      }
   else {
      struct tm *t = gmtime(ti);
      char *dow[] = {
         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
      char *moy[] = {
         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
      sprintf(ftb, "%02d:%02d:%02d %s %2d %s %4d",
         t->tm_hour,t->tm_min,t->tm_sec, dow[t->tm_wday],
         t->tm_mday, moy[t->tm_mon], t->tm_year+1900);
      }
   return ftb;
   }   

char *uptime_string(unsigned long timeticks, char *buf)
{
   int	seconds, minutes, hours, days;

   timeticks /= 100;
   days = timeticks / (60 * 60 * 24);
   timeticks %= (60 * 60 * 24);

   hours = timeticks / (60 * 60);
   timeticks %= (60 * 60);

   minutes = timeticks / 60;
   seconds = timeticks % 60;

   if (days == 0){
      sprintf(buf, "%d:%02d:%02d", hours, minutes, seconds);
      }
   else if (days == 1) {
      sprintf(buf, "%d day, %d:%02d:%02d",
	 days, hours, minutes, seconds);
      }
   else {
      sprintf(buf, "%d days, %d:%02d:%02d",
	 days, hours, minutes, seconds);
      }
   return buf;
   }

#if V6
char *sfmt_v6addr(char *d, unsigned char *a)
{
   char buf[50];
   int j, k, st,len, stx,lenx;
   unsigned int v, a2[IP6_ADDR_LEN/2], av;
   st = len = lenx = 0;
   for (k = j = 0; j != IP6_ADDR_LEN; j += 2) {
      v = (a[j] << 8) | a[j+1];
      a2[k++] = v;          /* Build array of two-byte pairs */
      if (v == 0) ++len;
      else {
         if (len > lenx) {  /* Find longest run of zero pairs */
            stx = st;  lenx = len;
	    }
         st = k;  len = 0;
         }
      }
   if (len > lenx) {
      stx = st;  lenx = len;
      }
   if (lenx != 0 && stx == 0) {  /* Longest run at left */
      d = strmov(d, ":");  j = lenx;
      }
   else {
      sprintf(buf, "%04X", a2[0]);
      d = strmov(d,buf);  j = 1;
      }
   for (; j < IP6_ADDR_LEN/2; ) {
      if (lenx != 0 && j == stx) {
         d = strmov(d,":");  j += lenx;
         }
      else {
         sprintf(buf, ":%04X", a2[j]);
         d = strmov(d, buf);  ++j;
         }
      }
   if (j == stx+lenx) d = strmov(d, ":");  /* Longest run at right */
   *d = '\0';
   return d;
   }
#endif

char *sfmt_address(char *d, unsigned char *a,
   unsigned char addrtype, unsigned char addrsz)
{
   int j;  char buf[50];
   switch (addrtype) {
   case ADJ_ADDR:
#if defined(NETFLOW)
      switch (addrsz) {
      case IP4_ADDR_LEN:
         sprintf(buf,"%u", a[0]);  d = strmov(d,buf);
         for (j = 1; j != addrsz; ++j) {
            sprintf(buf,".%u", a[j]);  d = strmov(d,buf);
	    }
         break;
      default:
         sprintf(buf,"%02X", a[0]);  d = strmov(d,buf);
         for (j = 1; j != addrsz; ++j) {
            sprintf(buf,"-%02X", a[j]);  d = strmov(d,buf);
            }
         break;
         }
      break;
#else
      sprintf(buf,"%02X", a[0]);  d = strmov(d,buf);
      for (j = 1; j != addrsz; ++j) {
         sprintf(buf,"-%02X", a[j]);  d = strmov(d,buf);
         }
      break;
#endif
   case PEER_ADDR:
      switch (addrsz) {
      case MAC_ADDR_LEN:
      case NSAP_ADDR_LEN:
      case TRANS_ADDR_LEN:
         sprintf(buf,"%02X", a[0]);  d = strmov(d,buf);
         for (j = 1; j != addrsz; ++j) {
            sprintf(buf,"-%02X", a[j]);  d = strmov(d,buf);
	    }
         break;
#if V6
      case IP6_ADDR_LEN:
         sfmt_v6addr(buf, a);
         d = strmov(d, buf);
         break;
#endif
      default:
         sprintf(buf,"%u", a[0]);  d = strmov(d,buf);
         for (j = 1; j != addrsz; ++j) {
            sprintf(buf,".%u", a[j]);  d = strmov(d,buf);
	    }
         break;
         }
      break;
   case TRANS_ADDR:
      sprintf(buf,"%u", a[0] << 8 | a[1]);  d = strmov(d,buf);
      break;
      }
   return d;
   }

#ifdef NEW_ATR
char *sfmt_distrib(char *data, struct distribution *dp)
{
   char buf[1200];
   int nb,j;
   if (dp->Transform == DS_NULL)
      return strmov(data, "0");
   sprintf(buf,"%d %d %d %d  %d %d %d %d ",
      dp->Transform, dp->ScaleFactor, dp->LowerLimit, dp->UpperLimit,
      dp->Buckets, dp->Parameter1, dp->Parameter2, dp->Parameter3);
   data = strmov(data, buf);
   nb = dp->Transform == DS_DYN_PTS ? dp->Buckets : dp->Buckets+1;
   for (j = 0; j != nb; ++j) {
      sprintf(buf," %d", dp->counts[j]);
      data = strmov(data, buf);
      }
   return data;
   }

char *sfmt_tcp_data(char *data, struct subflow_data *sfd)
{
   char buf[500];
   int j;

   if (sfd == NULL || !sfd->valid)
      return strmov(data, "0");
   sprintf(buf,"%lu %lu ", sfd->n_subflows, sfd->mx_active_subflows);
   data = strmov(data, buf);

   data = putc64(data, &sfd->ToTCPLenOctets);
   data = strmov(data, " ");
   data = putc64(data, &sfd->FromTCPLenOctets);
   data = strmov(data, " ");

   data = putc64(data, &sfd->ToTCPSeqOctets);
   data = strmov(data, " ");
   data = putc64(data, &sfd->FromTCPSeqOctets);
   data = strmov(data, " ");

   data = putc64(data, &sfd->ToTCPAckOctets);
   data = strmov(data, " ");
   data = putc64(data, &sfd->FromTCPAckOctets);

   sprintf(buf," %lu %lu", sfd->ToTCPDecrSeq, sfd->FromTCPDecrSeq);
   data = strmov(data, buf);

   return data;
   }
#endif  /* NEW_ATR */

void printruleaddress(FILE *f, unsigned char *a)
{
   int j, k;
   fprintf(f,"%u", a[0]);
   for (k = RULE_ADDR_LEN-1; k != 0; --k) if (a[k] != 0) break;
   for (j = 1; j <= k; ++j) fprintf(f,".%u", a[j]);
   }


int parse_open(char *fn)  /* Open file and initialise parser */
{
   if ((rfp = fopen(fn, "r")) == NULL) {
      rferrors = 1;  return 0;
      }
   rfwarnings = rferrors = rule_line = 0;  ic = '\n';  lic = ' ';
   strcpy(scan_rfname,fn);  /* Remember rule file's name */
   return 1;
   }

void push_include(char *ifp)
{
   struct incl_ent *iep;
   if (incl_depth == MXINCL) {
      p_error("Includes nested too deep");
      return;
      }
   iep = &incl_stack[incl_depth];
   iep->fp = rfp;
   if ((rfp = fopen(ifp, "r")) == NULL) {
      p_error("Couldn't open include file");  
      rfp = iep->fp;  return;
      }
   ++incl_depth;
   strcpy(iep->buf,inbuf);  iep->bp = ibp;
   iep->line_nbr = rule_line;  iep->iblisted = iblisted;
   iep->lic = lic;  iep->ic = ic;
   strcpy(iep->rfname,scan_rfname);
   strcpy(scan_rfname,ifp);  /* Remember rule file's name */
   rule_line = 0;  ic = '\n';
   }

int end_of_file(void)
{
   struct incl_ent *iep;
   if (ic != EOF) return 0;
   if (incl_depth == 0) return 1;
   iep = &incl_stack[--incl_depth];
   rfp = iep->fp;
   strcpy(inbuf,iep->buf);  ibp = iep->bp;
   rule_line = iep->line_nbr;  iblisted = iep->iblisted;
   lic = iep->lic;  ic = iep->ic;
   strcpy(scan_rfname,iep->rfname);
   return 0;
   }

int trailing_arg()
{
   for (;;) {
      if (end_of_file() || lic == '\n')
         return 0;  /* No trailing argument */
      if (!isspace(ic)) break;
      nextchar();
      }
   return 1;  /* We have a trailing argument */
   }

void getarg(char *arg)
{
   int count = 1;  /* Space for trailing null */
   for (;;) {
      if (end_of_file()) {
         *arg = '\0';  return;
         }
      if (!isspace(ic)) break;
      nextchar();
      }
   do {
      *arg++ = ic;
      nextchar();
      } while (!isspace(ic) && ic != ';' &&
         ++count < NAME_LN && !end_of_file() );
   *arg = '\0';  return;
   }

int nextchar()
{
   lic = ic;
   for (;;) {
      if (lic == '\n') {
	 if (fgets(inbuf, sizeof(inbuf), rfp) == NULL) {
            ic = EOF;  if (end_of_file()) return EOF;
            }
         else {
            iblisted = 0;  ++rule_line;
	    ibp = inbuf;
            if (listrules && pass == 1) p_error(NULL);
	    }
	 }
      ic = *ibp++;
      if (ic == '#') lic = '\n';  /* Ignore comments */
      else return ic;
      }
   }

int wordis(p,w)
char *p, *w;
{

   if (strlen(p) != strlen(w)) return 0;  /* Lengths differ */
   return strcmp(p,w) == 0;
   }

void p_error(char *msg)
{
   if (!iblisted) {
      printf("%s %4d: %s", scan_rfname,rule_line,inbuf);
      iblisted = 1;
      }
   if (msg) {
      printf("%s !!!\n", msg);
      ++rferrors;
      }
   }

void p_warn(char *msg)
{
   if (!iblisted) {
      printf("%s %4d: %s", scan_rfname,rule_line,inbuf);
      iblisted = 1;
      }
   if (msg) {
      printf("%s <<< warning\n", msg);
      ++rfwarnings;
      }
   }

int lookup(char *id)  /* Return 1 if id is already in table */
{
   int id_len, j;
   unsigned int hash;
   struct symbol *lsp, *sp;

   id_len = strlen(id);
   if (id_len > SYMBOLSZ) {
      sprintf(err_msg, "Id %s has more than %d chars", id,id_len);
      p_warn(err_msg);
      id_len = SYMBOLSZ;
      }
   hash = id_len;
   for (j = 0; j != id_len; ++j) hash += id[j];
   st_index = hash & STHASHMASK;
   sp = &symbol_table[st_index];  lsp = NULL;
   if (sp->id[0] != 0) {  /* At least one entry for this hash */
      if (strcmp(sp->id,id) == 0) {
         toktype = sp->type;  toklen = sp->size;
         return 1;
	 }
      else {
         for (;;) {
            lsp = sp;
            if (lsp->next == 0) break;  /* End of chain */
            sp = &symbol_table[st_index = lsp->next]; 
            if (strcmp(sp->id,id) == 0) {
               toktype = sp->type;  toklen = sp->size;
               return 1;
	       }
	    }
         } 
      }
   /* Not there, put it in */
   if (lsp != NULL) {  /* Add to hash chain */
      if (st_top == STSIZE) {
         sprintf(err_msg, "More than %d symbols",STSIZE);
         p_error(err_msg);
         }
      else {  /* Add new symbol */
         st_index = st_top++;
         lsp->next = st_index;
         sp = &symbol_table[st_index];
	 }
      }
   memset(sp, 0, sizeof(struct symbol));
   strncpy(sp->id, id, id_len);
   sp->id[id_len] = '\0';
   return 0;  /* Not already in ST, now added */
   }

void add_symbol(char *id, int type, int value)
{
   struct symbol *sp;
   lookup(id);
   sp = &symbol_table[st_index];
   sp->type = type;  sp->value = value;
   }

void dump_symbol_table()
{
   int j;
   struct symbol *sp;
   for (j = 0; j != st_top; ++j) {
      sp = &symbol_table[j];
      if (sp->id[0] != 0) {
         printf("%5d, %5d:  %s  type=%d, value=%d, size=%d\n",
            j, sp->next, sp->id, sp->type,sp->value,sp->size);
	 }
      }
   }

void init_symbol_table()
{
   int j;

   st_top = STHASHBASE;
   for (j = 0; j != STHASHBASE; ++j)
      symbol_table[j].id[0] = '\0';

   if (MIBver == 4) {
      add_symbol("ip", TOK_WORD, AT_IP4);
      add_symbol("ipx", TOK_WORD, AT_NOVELL);
      add_symbol("novell", TOK_WORD, AT_NOVELL);
      add_symbol("decnet", TOK_WORD, AT_DECNET);
      add_symbol("ethertalk", TOK_WORD, AT_ETHERTALK);
      add_symbol("clns", TOK_WORD, AT_CLNS);
      add_symbol("other", TOK_WORD, AT_OTHER);
      add_symbol("dummy", TOK_WORD, AT_DUMMY);
      }
   else {
      add_symbol("ip", TOK_WORD, old_addr_type[AT_IP4]);
      add_symbol("ipx", TOK_WORD, old_addr_type[AT_NOVELL]);
      add_symbol("novell", TOK_WORD, old_addr_type[AT_NOVELL]);
      add_symbol("decnet", TOK_WORD, old_addr_type[AT_DECNET]);
      add_symbol("ethertalk", TOK_WORD, old_addr_type[AT_ETHERTALK]);
      add_symbol("clns", TOK_WORD, old_addr_type[AT_CLNS]);
      add_symbol("other", TOK_WORD, old_addr_type[AT_OTHER]);
      add_symbol("dummy", TOK_WORD, old_addr_type[AT_DUMMY]);
      }

   add_symbol("countpkt", TOK_ACTION, RA_COUNTPKT);
   add_symbol("count", TOK_ACTION, RA_COUNT);
   add_symbol("succeed", TOK_ACTION, RA_SUCCEED);
   add_symbol("nomatch", TOK_ACTION, RA_FAIL);
   add_symbol("retry", TOK_ACTION, RA_FAIL);
   add_symbol("fail", TOK_ACTION, RA_FAIL);
   add_symbol("pushtoact", TOK_ACTION, RA_PUSHTOACT);
   add_symbol("pushto", TOK_ACTION, RA_PUSHTO);
   add_symbol("gotoact", TOK_ACTION, RA_GOTOACT);
   add_symbol("goto", TOK_ACTION, RA_GOTO);
   add_symbol("gosubact", TOK_ACTION, RA_GOSUBACT);
   add_symbol("gosub", TOK_ACTION, RA_GOSUB);
   add_symbol("return", TOK_ACTION, RA_RETURN);
   add_symbol("assignact", TOK_ACTION, RA_ASSIGNACT);
   add_symbol("assign", TOK_ACTION, RA_ASSIGN);
   add_symbol("ignore", TOK_ACTION, RA_IGNORE);
   add_symbol("pushruletoact", TOK_ACTION, RA_PUSHTOACT);
   add_symbol("pushruleto", TOK_ACTION, RA_PUSHTO);
   add_symbol("pushpkttoact", TOK_ACTION, RA_PUSHPKTTOACT);
   add_symbol("pushpktto", TOK_ACTION, RA_PUSHPKTTO);
   add_symbol("poptoact", TOK_ACTION, RA_POPTOACT);
   add_symbol("popto", TOK_ACTION, RA_POPTO);

   add_symbol("set", TOK_WORD, RF_SET);
   add_symbol("rules", TOK_WORD, RF_RULES);
   add_symbol("format", TOK_WORD, RF_FORMAT);
   add_symbol("statistics", TOK_WORD, RF_STATS);
   add_symbol("next", TOK_WORD, RF_NEXT);
   add_symbol("include", TOK_WORD, RF_INCLUDE);

   add_symbol("icmp", TOK_WORD, PT_ICMP);
   add_symbol("tcp", TOK_WORD, PT_TCP);
   add_symbol("udp", TOK_WORD, PT_UDP);
   add_symbol("ospf", TOK_WORD, PT_OSPF);

   add_symbol("ftpdata", TOK_WORD, WNP_FTPDATA);
   add_symbol("ftp", TOK_WORD, WNP_FTP);
   add_symbol("http", TOK_WORD, WNP_WWW);
   add_symbol("telnet", TOK_WORD, WNP_TELNET);
   add_symbol("smtp", TOK_WORD, WNP_SMTP);
   add_symbol("domain", TOK_WORD, WNP_DOMAIN);
   add_symbol("nntp", TOK_WORD, WNP_NNTP);
   add_symbol("ntp", TOK_WORD, WNP_NTP);
   add_symbol("snmp", TOK_WORD, WNP_SNMP);
   add_symbol("gopher", TOK_WORD, WNP_GOPHER);
   add_symbol("www", TOK_WORD, WNP_WWW);

   for (j = 0; j != SZ_ATTRIBS; ++j) {
      add_symbol(attribs[j].name, TOK_ATTRIB, attribs[j].index);
      symbol_table[st_index].size = attribs[j].len;
      }
   }

int getword()
{
   char wbuf[30], *wp = wbuf;
   struct symbol *sp;
   int r;

   for (;;) {
      *wp++ = tolower(ic);
      nextchar();
      if (end_of_file()) return EOF;
      if (!isalnum(ic) && ic != '_') break;
      }
   *wp = '\0';

   r = lookup(wbuf);
   sp = &symbol_table[st_index];
   if (r) {  /* Found in ST */
      if (sp->type == TOK_SYMBOL)
         return RF_SYMBOL;  /* Label with unknown address */
      else return sp->value;
      }

   sp->type = toktype = TOK_SYMBOL;  /* New symbol */
      /* sp->value is initially 0 */
   return RF_SYMBOL;
   }

int getnbr(int maxnbr)  /* NB, 22 Oct 93 */
{
   int v = 0;
   toktype = TOK_OTHER;
   for (;;) {
      if (end_of_file()) return EOF;
      if (isdigit(ic)) break;
      else if (isalpha(ic)) return getword();
      else nextchar();
      }
   toktype = TOK_INT;
   for (;;) {
      v = v*10 + ic-'0';
      nextchar();
      if (end_of_file()) EOF;
      if (!isdigit(ic)) break;
      }
   if (v > maxnbr) {  /* NB, 22 Oct 93 */
      sprintf(err_msg, "Number > %d",maxnbr);  /* NB, 22 Oct 93 */
      p_error(err_msg);
      }
   return v;
   }

unsigned int getint(unsigned int *base)
{
   char wbuf[30], *wp;
   unsigned long v;
   unsigned char c, word;
   int sic;  char *sibp;
   for (;;) {
      if (end_of_file()) return EOF;
      if (isalnum(ic)) break;
      if (ic == '\'') break;
      else nextchar();
      }
   if (ic == '\'') {
      nextchar();  v = ic;
      nextchar();  if (ic != '\'') p_error("Missing ' in char constant");
      nextchar();
      if (*base == 0) *base = 10;
      return v;      
      }
   sic = ic;  sibp = ibp;  /* Save scanner state */
   for (word = 0, wp = wbuf; ; ) {
      if (isalpha(ic) && !isxdigit(ic)) word = 1;
      *wp++ = tolower(ic);
      nextchar();
      if (end_of_file()) break;
      if (!isalnum(ic)) break;
      }
   if (word) {
      if (*base == 0) *base = 10;
      ic = sic;  ibp = sibp;  /* Back up scanner */
      return getword();
      }
   *wp = '\0';
   if (*base == 0)
      *base = ic == '-' ? 16 : 10;  /* Hex address bytes separated by - */
   for (wp = wbuf, v = 0; *wp != '\0'; ) {
      c = *wp++;
      if (isdigit(c)) c -= '0';
      else c = c-'a' + 10;
      v = v*(*base) + c;
      }
   return v;
   }

void getaddress(unsigned char *a,unsigned char len, unsigned char addrsz)
{
   unsigned int j, base, v;
   for (base = j = 0; j != addrsz; ++j) {
      v = getint(&base);
      if (len != 1 &&  /* Two or more bytes in address */
            ic != '.' && ic != '-' &&  /* No byte separator character */
            j == 0) {  /* Allow 16-bit decimal in first two bytes */
         a[j++] = (v>>8) & 0xFF;  v &= 0x00FF;
         }
      a[j] = v;
      if (ic != '.' && ic != '-') {  /* End of token; pad with zeroes */
         for (++j; j != addrsz; ++j) a[j] = 0;
         return;
         }
      }
   while (ic == '.' || isdigit(ic))
      nextchar();  /* Skip to end of v6 addresses in srl+v6 rulesets */
   }

int getattribute(unsigned char col, struct flow_info *fp)
{
   switch(col) {
   case FTLOWINTERFACE:
      fp->LowInterface = getnbr(255);
      break;
   case FTLOWADJACENTTYPE:
      fp->LowAdjType = getnbr(255);
      break;
   case FTLOWADJACENTADDRESS:
      getaddress(fp->LowAdjAddress,MAC_ADDR_LEN, MAC_ADDR_LEN);
      break;
   case FTLOWADJACENTMASK:
      getaddress(fp->LowAdjMask,MAC_ADDR_LEN, MAC_ADDR_LEN);
      break;
   case FTLOWPEERTYPE:
      fp->LowPeerType = getnbr(255);
      break;
   case FTLOWPEERADDRESS:
      getaddress(fp->LowPeerAddress,PEER_ADDR_LEN, PEER_ADDR_LEN);
      break;
   case FTLOWPEERMASK:
      getaddress(fp->LowPeerMask,PEER_ADDR_LEN, PEER_ADDR_LEN);
      break;
   case FTLOWTRANSTYPE:
      fp->LowTransType = getnbr(255);
      break;
   case FTLOWTRANSADDRESS:
      getaddress(fp->LowTransAddress,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTLOWTRANSMASK:
      getaddress(fp->LowTransMask,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTHIINTERFACE:
      fp->HighInterface = getnbr(255);
      break;
   case FTHIADJACENTTYPE:
      fp->HighAdjType = getnbr(255);
      break;
   case FTHIADJACENTADDRESS:
      getaddress(fp->HighAdjAddress,MAC_ADDR_LEN, MAC_ADDR_LEN);
      break;
   case FTHIADJACENTMASK:
      getaddress(fp->HighAdjMask,MAC_ADDR_LEN, MAC_ADDR_LEN);
      break;
   case FTHIPEERTYPE:
      fp->HighPeerType = getnbr(255);
      break;
   case FTHIPEERADDRESS:
      getaddress(fp->HighPeerAddress,PEER_ADDR_LEN, PEER_ADDR_LEN);
      break;
   case FTHIPEERMASK:
      getaddress(fp->HighPeerMask,PEER_ADDR_LEN, PEER_ADDR_LEN);
      break;
   case FTHITRANSTYPE:
      fp->HighTransType = getnbr(255);
      break;
   case FTHITRANSADDRESS:
      getaddress(fp->HighTransAddress,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTHITRANSMASK:
      getaddress(fp->HighTransMask,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTSOURCECLASS:
      fp->SourceClass = getnbr(255);
      break;
   case FTDESTCLASS:
      fp->DestClass = getnbr(255);
      break;
   case FTFLOWCLASS:
      fp->FlowClass = getnbr(255);
      break;
   case FTSOURCEKIND:
      fp->SourceKind = getnbr(255);
      break;
   case FTDESTKIND:
      fp->DestKind = getnbr(255);
      break;
   case FTFLOWKIND:
      fp->FlowKind = getnbr(255);
      break;
   case FTDSCODEPOINT:
      fp->DSCodePoint = getnbr(0x3F);
      break;

#if defined(NETFLOW)
   case FTMETERID:
      fp->MeterId = getnbr(255);
      break;
   case FTLOWROUTEASN:
      getaddress(fp->LowRouteASN,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTLOWROUTEPREFIX:
      fp->LowRoutePrefix = getnbr(255);
      break;
   case FTHIROUTEASN:
      getaddress(fp->HighRouteASN,TRANS_ADDR_LEN, TRANS_ADDR_LEN);
      break;
   case FTHIROUTEPREFIX:
      fp->HighRoutePrefix = getnbr(255);
      break;
#endif

   default:
      sprintf(err_msg,"Attribute %d not allowed",col);
      p_error(err_msg);
      }
   }

char *sfmt_attrib(char *d, struct flow_info *fp, unsigned char col)
{
   char buf[50];
   char pal = fp->LowPeerType > MX_PROTOCOLS ? MAC_ADDR_LEN 
      : addr_len[fp->LowPeerType];
#ifdef NEW_ATR
   struct distribution *dist;
#endif
   switch(col) {
   case FTFLOWINDEX:  /* Used to synchronise column blobs */
      sprintf(buf,"%u",fp->FlowIndex);
      break;
   case FTFLOWSTATUS:
      sprintf(buf,"%u",fp->FlowStatus);
      break;
   case FTLOWINTERFACE:
      sprintf(buf,"%u",fp->LowInterface);
      break;
   case FTLOWADJACENTTYPE:
      sprintf(buf,"%u",fp->LowAdjType);
      break;
   case FTLOWADJACENTADDRESS:
#if defined(NETFLOW)
      return sfmt_address(d,fp->LowAdjAddress,ADJ_ADDR,
         fp->LowAdjType == AT_IP4 ? IP4_ADDR_LEN : MAC_ADDR_LEN);
#else
      return sfmt_address(d,fp->LowAdjAddress,ADJ_ADDR,MAC_ADDR_LEN);
#endif
   case FTLOWADJACENTMASK:
      return sfmt_address(d,fp->LowAdjMask,ADJ_ADDR,MAC_ADDR_LEN);
   case FTLOWPEERTYPE:
      sprintf(buf,"%u",fp->LowPeerType);
      break;
   case FTLOWPEERADDRESS:
      return sfmt_address(d,fp->LowPeerAddress,PEER_ADDR,pal);
   case FTLOWPEERMASK:
      return sfmt_address(d,fp->LowPeerMask,PEER_ADDR,pal);
   case FTLOWTRANSTYPE:
      sprintf(buf,"%u",fp->LowTransType);
      break;
   case FTLOWTRANSADDRESS:
      return sfmt_address(d,fp->LowTransAddress,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTLOWTRANSMASK:
      return sfmt_address(d,fp->LowTransMask,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTHIINTERFACE:
      sprintf(buf,"%u",fp->HighInterface);
      break;
   case FTHIADJACENTTYPE:
      sprintf(buf,"%u",fp->HighAdjType);
      break;
   case FTHIADJACENTADDRESS:
#if defined(NETFLOW)
      return sfmt_address(d,fp->HighAdjAddress,ADJ_ADDR,
         fp->HighAdjType == AT_IP4 ? IP4_ADDR_LEN : MAC_ADDR_LEN);
#else
      return sfmt_address(d,fp->HighAdjAddress,ADJ_ADDR,MAC_ADDR_LEN);
#endif
   case FTHIADJACENTMASK:
      return sfmt_address(d,fp->HighAdjMask,ADJ_ADDR,MAC_ADDR_LEN);
   case FTHIPEERTYPE:
      sprintf(buf,"%u",fp->HighPeerType);
      break;
   case FTHIPEERADDRESS:
      return sfmt_address(d,fp->HighPeerAddress,PEER_ADDR,pal);
   case FTHIPEERMASK:
      return sfmt_address(d,fp->HighPeerMask,PEER_ADDR,pal);
   case FTHITRANSTYPE:
      sprintf(buf,"%u",fp->HighTransType);
      break;
   case FTHITRANSADDRESS:
      return sfmt_address(d,fp->HighTransAddress,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTHITRANSMASK:
      return sfmt_address(d,fp->HighTransMask,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTRULESET:
      sprintf(buf,"%u",fp->FlowRuleSet);
      break;
   case FTUPOCTETS:
      putc64(buf, &fp->FwdBytes);
      break;
   case FTUPPDUS:
      putc64(buf, &fp->FwdPackets);
      break;
   case FTDOWNOCTETS:
      putc64(buf, &fp->BackBytes);
      break;
   case FTDOWNPDUS:
      putc64(buf, &fp->BackPackets);
      break;
   case FTFIRSTTIME:
      sprintf(buf,"%lu",fp->FirstTime);
      break;
   case FTLASTTIME:
      sprintf(buf,"%lu",fp->LastTime);
      break;
   case FTSOURCECLASS:
      sprintf(buf,"%u",fp->SourceClass);
      break;
   case FTDESTCLASS:
      sprintf(buf,"%u",fp->DestClass);
      break;
   case FTFLOWCLASS:
      sprintf(buf,"%u",fp->FlowClass);
      break;
   case FTSOURCEKIND:
      sprintf(buf,"%u",fp->SourceKind);
      break;
   case FTDESTKIND:
      sprintf(buf,"%u",fp->DestKind);
      break;
   case FTFLOWKIND:
      sprintf(buf,"%u",fp->FlowKind);
      break;
   case FTDSCODEPOINT:
      sprintf(buf,"%u",fp->DSCodePoint);
      break;

#if defined(NETFLOW)
   case FTMETERID:
      sprintf(buf,"%u",fp->MeterId);
      break;
   case FTLOWROUTEASN:
      return sfmt_address(d,fp->LowRouteASN,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTLOWROUTEPREFIX:
      sprintf(buf,"%u",fp->LowRoutePrefix);
      break;
   case FTHIROUTEASN:
      return sfmt_address(d,fp->HighRouteASN,TRANS_ADDR,TRANS_ADDR_LEN);
   case FTHIROUTEPREFIX:
      sprintf(buf,"%u",fp->HighRoutePrefix);
      break;
#endif

#ifdef NEW_ATR
   case FTDISTRIBUTIONS:
      sprintf(buf,"%0x",fp->distrib_bits);
      break;
   case FTTOPACKETSIZE:
   case FTFROMPACKETSIZE:
   case FTTOINTERARRIVALTIME:
   case FTFROMINTERARRIVALTIME:
   case FTTOTURNAROUNDTIME:
   case FTFROMTURNAROUNDTIME:
   case FTTOBITRATE:
   case FTFROMBITRATE:
   case FTTOPDURATE:
   case FTFROMPDURATE:

   case FTTOTCPSIZE:
   case FTFROMTCPSIZE:
   case FTTOTCPTIME:
   case FTFROMTCPTIME:
   case FTTOTCPRATE1:
   case FTFROMTCPRATE1:
   case FTTOTCPRATE2:
   case FTFROMTCPRATE2:

/*   case FTTOTURNAROUNDTIME1:
     case FTFROMTURNAROUNDTIME1: */
   case FTTOTURNAROUNDTIME2:
   case FTFROMTURNAROUNDTIME2:
   case FTTOTURNAROUNDTIME3:
   case FTFROMTURNAROUNDTIME3:
   case FTTOTURNAROUNDTIME4:
   case FTFROMTURNAROUNDTIME4:

   case FTTOFLOWOCTETS:
   case FTFROMFLOWOCTETS:
   case FTTOFLOWPDUS:
   case FTFROMFLOWPDUS:
   case FTFLOWTIME:
      for (dist = fp->distrib_list; dist != NULL; dist = dist->next) {
         if (dist->selector == col) break;
         }
      if (dist == NULL) {
         printf("Couldn't find distribution <<");
         return d;
         }
      return sfmt_distrib(d, dist);

   case FTTOLOSTPDUS:
      sprintf(buf,"%u",fp->ToLostPDUs);
      break;
   case FTFROMLOSTPDUS:
      sprintf(buf,"%u",fp->FromLostPDUs);
      break;
   case FTTOPQOVERFLOWS:
      sprintf(buf,"%u",fp->ToPQOverflows);
      break;
   case FTFROMPQOVERFLOWS:
      sprintf(buf,"%u",fp->FromPQOverflows);
      break;

   case FTTCPDATA:
      return sfmt_tcp_data(d, fp->sfd);
#endif  /* NEW_ATR */

   default:
      sprintf(buf,"*");  /* Unknown attribute */
      }
   return strmov(d,buf);
   }

#define TR_FIN   1
#define TR_TO    2
#define TR_ACT   3

int action_type[] = { 0,
   TR_FIN,  /* RA_IGNORE         1 */
   TR_FIN,  /* RA_RETRY          2 */
   TR_FIN,  /* RA_COUNT          3 */
   TR_FIN,  /* RA_COUNTPKT       4 */
   TR_FIN,  /* RA_RETURN         5 */
   TR_TO,   /* RA_GOSUB          6 */
   TR_ACT,  /* RA_GOSUBACT       7 */
   TR_TO,   /* RA_ASSIGN         8 */
   TR_ACT,  /* RA_ASSIGNACT      9 */
   TR_TO,   /* RA_GOTO          10 */ 
   TR_ACT,  /* RA_GOTOACT       11 */
   TR_TO,   /* RA_PUSHTO        12 */
   TR_ACT,  /* RA_PUSHTOACT     13 */
   TR_TO,   /* RA_PUSHPKTTO     14 */
   TR_ACT   /* RA_PUSHPKTTOACT  15 */
   };

int scan_rulefile(struct meter_status *ms, int doset,
   int list, int standby)
{
   struct meter_rule_info *mri;
   struct rule_info ri, last_ri;
   int only_gotoAct,last_only_gotoAct, gosub_rule,last_gosub_rule,
      cf,cfa;
   int rule_set, nrules, ncounts, n, len, kind, 
      labels, seltype;
   struct flow_info ai;
   unsigned char a, b;
   char *rfname;
   char sbuf[100], *sp, inc_fn[NAME_LN];
   char abuf[50];

   if (testing)
      fprintf(stderr,
	      "scan_rf(): standby=%d, pass=%d, standby rf %s, current rf %s\n",
	      standby, pass, ms->s_ruleset.filename, ms->c_ruleset.filename);
   if (standby) {
      if (!parse_open(rfname = ms->s_ruleset.filename)) {
         printf("   Couldn't open standby rule file %s !!!\n",
            ms->s_ruleset.filename);
         
         return 0;  /* Fail */
         }
      mri = &ms->s_ruleset;
      }
   else {
      if (!parse_open(rfname = ms->c_ruleset.filename)) {
         printf("   Couldn't open rule file %s !!!\n", ms->c_ruleset.filename);
         return 0;  /* Fail */
	 }
      mri = &ms->c_ruleset;
      }
   ri.RuleSet = mri->ri_Index;  /* Meter ruleinfo row index */
#if LINERAR_SEARCH
   MIBver = ms->MIBver;
#endif
   rule_set = 0;
   nrules = ncounts = 0;  kind = RF_RULES;
   last_only_gotoAct = last_gosub_rule = 0;
   memset(&last_ri, 0, sizeof(last_ri));
   for (;;) {
      cf = cfa = 0;
      do {  /* First char of a line */
	 nextchar();
	 if (end_of_file()) break;
	 } while (lic != '\n');
      if (end_of_file()) break;
      n = getnbr(255);  /* What kind of line is it? */
      if (end_of_file()) break;
      if (n == RF_INCLUDE) {
         getarg(inc_fn);
         push_include(inc_fn);
         continue;
         }
      else if (n == RF_SET) {
         rule_set = getnbr(255);
         if (rule_set == RF_SYMBOL) {
            strcpy(mri->ri_Name, symbol_table[st_index].id);
            symbol_table[st_index].value = 1;  /* Suppress 'undefined' error msg */
            }
         else sprintf(mri->ri_Name, "%d", rule_set);
         kind = RF_RULES;  continue;
         }
      else if (n == RF_RULES) {
         kind = RF_RULES;  continue;
         }
      else if (n == RF_FORMAT) {
         for (a = 0; a != LASTATTRIB+1; ++a)
            ms->format[a] = ms->required[a] = 0;
         for (a = 0; ; ) {
            n = getnbr(255);
            if (n < 0 || n > LASTATTRIB) {
               sprintf(err_msg,"Attribute %d not allowed in format",n);
               p_error(err_msg);
	       }
            else {
               ms->format[a] = n;  ms->required[n] = 1;
               ms->separator[a] = " ";  /* Default */
               }
            if (ic == ';' || end_of_file()) break;

            for (;;) {
               if (end_of_file() || !isspace(ic)) break;
               nextchar();
               }
            if (ic == ';' || end_of_file()) break;

            if (!isalnum(ic)) {  /* User-specified separator */
               if (ic == '\"') {
                  nextchar();  /* Opening quote */
                  for (sp = sbuf; ; ) {
                     if (end_of_file()) {
                        p_error("Missing \"");
                        break;
   	                }
                     else if (ic == '\"' && ibp[-2] != '\\') break;
                     else *sp++ = ic;
                     if (*ibp == '#') {  /* Allow # in string */
                        lic = ic;  ic = *ibp++;
                        }
                     else nextchar();
                     }
                  nextchar();
                  }
               else {
                  sp = sbuf;  *sp++ = ' ';
                  for (;;) {
                     if (isspace(ic)) break;
                     *sp++ = ic;
	             nextchar();
                     }
                  *sp++ = ' ';
	          }
               *sp = '\0';
               ms->separator[a] = gcstring(sbuf, &len);
	       }
            ++a;
            if (ic == ';' || end_of_file()) break;
            }
         if (list) {
	    printf("Format: ");
            for (n = ms->format[a = 0]; ; ) {
               for (b = 1; b <= SZ_ATTRIBS && attribs[b].index != n; ++b) ;
               if (b <= SZ_ATTRIBS) fprintf(stdout,attribs[b].name);
               else fprintf(stdout,"attr*%u", n);  /* Unknown attribute */
               if ((n = ms->format[a+1]) == 0) {  /* Last attribute */
                  if (strcmp(ms->separator[a]," ") != 0)
                     printf(ms->separator[a]);  /* No trailing blank */
                  break;
                  }
               printf(ms->separator[a++]);
               }
            printf("\n");
            }
         continue;
         }
      else if (n == RF_STATS) {
         ms->statsreqd = 1;  /* Collect statistics */
         continue;
         }

      labels = -1;  /* Make chain of labels for this rule */
      while (n == RF_SYMBOL) {  /* Allow multiple labels */
         if (pass == 1) {
            if (symbol_table[st_index].value == 0) {
               symbol_table[st_index].value = kind;
               symbol_table[st_index].target = nrules+1;
                  /* (kind == RF_RULES ? nrules : nactions) + 1; !! */
               if (verbose) printf("line %d: %s is %s %d\n", rule_line,
                  symbol_table[st_index].id,
                  kind == RF_RULES ? "rule" : "?????",
                  symbol_table[st_index].target);
               }
            else {
               sprintf(err_msg,"Duplicate label %s", 
                  symbol_table[st_index].id);
               p_error(err_msg);
	       }
            }
         symbol_table[st_index].action = labels;  /* Remember the labels */
         labels = st_index;
         nextchar();
         n = getnbr(255);
         }

      if (kind == RF_RULES) {
         ri.RuleSelector = n;  /* Attribute nbr */
         seltype = toktype;
         len = toktype == TOK_ATTRIB ? toklen : 1;
         getaddress(ri.RuleMask,len, RULE_ADDR_LEN);
         if (end_of_file()) break;
         getaddress(ri.RuleMatchedValue,len, RULE_ADDR_LEN);
         if (end_of_file()) break;
         ri.RuleAction = getnbr(255);
         if (toktype != TOK_ACTION &&
            !(toktype == TOK_INT && 
               ri.RuleAction >= RA_IGNORE && ri.RuleAction <= RA_POPTOACT ))
            p_error("Rule action expected");
         if (end_of_file()) break;
         ri.RuleJumpIndex = getnbr(32767);  /* NB, 22 Oct 93 */
         if ((toktype != TOK_WORD || ri.RuleJumpIndex != RF_NEXT) &&
               toktype != TOK_INT && toktype != TOK_SYMBOL)
            p_error("Label or rule number expected");
         if (ri.RuleJumpIndex == RF_NEXT)
            ri.RuleJumpIndex = nrules+2;  /* nrules not incremented yet! */
         if (ri.RuleAction == RA_SUCCEED) {
            ri.RuleAction = ri.RuleJumpIndex == 0 ? RA_IGNORE : RA_GOTOACT;
	    }
         cf = cfa = 0;
         while (labels >= 0) {  /* Set action in rule's label entries */
            n = symbol_table[labels].action;
            symbol_table[labels].action = ri.RuleAction;
            if (pass == 2) {
               if (symbol_table[labels].comefrom) ++cf;
               else if (symbol_table[labels].comefromAct) ++cfa;
               if (testing)
                  fprintf(stderr, "LABEL: %s, line=%d   cf=%d, cfa=%d\n", 
                     symbol_table[labels].id, rule_line, cf, cfa);
               }
            labels = n;
	    }
         if (ri.RuleAction == RA_AGGREGATE) p_error(
            "Aggregate action no longer available: use Count instead");
         else if (ri.RuleAction == RA_TALLY) p_error(

            "Tally action no longer available: use Count instead");
         else if (ri.RuleAction == RA_ASSIGN || 
               ri.RuleAction == RA_ASSIGNACT) {
            if (ri.RuleSelector < FTV1 || ri.RuleSelector > FTV5)
               p_error("Assign target (first field of rule) must be v1..v5");
            if (ri.RuleMatchedValue[1] != 0 ||
                  ri.RuleMatchedValue[0] < 1 || 
                  ri.RuleMatchedValue[0] > LASTATTRIB) {
               sprintf(err_msg,"Assign value %d not an attribute number",
                  ri.RuleMatchedValue[0]);
               p_error(err_msg);
	       }
	    }
         else if (seltype != TOK_ATTRIB &&
               (ri.RuleSelector < 0 || ri.RuleSelector > LASTATTRIB))
            p_error("Selector value should be an attribute");
         if (toktype == TOK_SYMBOL) {
            ri.RuleJumpIndex = symbol_table[st_index].target;
            if (pass == 1) {
               if (action_type[ri.RuleAction] == TR_TO)
                  ++symbol_table[st_index].comefrom;
               else if (action_type[ri.RuleAction] == TR_ACT)
                  ++symbol_table[st_index].comefromAct;
               else p_error("Symbol must be target of Goto or GotoAct");
               if (testing)
                  fprintf(stderr, "SYMBOL: %s, line=%d   cf=%d, cfa=%d\n", 
                     symbol_table[st_index].id, rule_line,
                     symbol_table[st_index].comefrom,
                     symbol_table[st_index].comefromAct);
               }
	    }
         if (pass == 2) {
            if (ri.RuleAction == RA_GOSUB || ri.RuleAction == RA_GOSUBACT)
               gosub_rule = 1;
            else if (ri.RuleSelector == 0)  /* Null selector */
               gosub_rule = last_gosub_rule;
            else gosub_rule = 0;
            only_gotoAct = 0;
#if RULETEST_DEBUG
            printf("-- rule_line=%d, nrules=%d, cf=%d, cfa=%d, last_gosub_rule=%d",
	       rule_line, nrules, cf,cfa, last_gosub_rule);
#endif
            if (!cf) {  /* There are no Gotos to this rule */
               if (last_ri.RuleSelector == 0) {  /* Null selector */
                  switch (action_type[last_ri.RuleAction]) {
                  case TR_FIN:
                     if (!cfa && nrules != 0 && !last_gosub_rule)
                        p_warn("Rule can't be executed (null+fin)");
                     only_gotoAct = 1;
                     break;
                  case TR_ACT:
                     if (last_ri.RuleJumpIndex != nrules+1  /* Next */
                           && !cfa && nrules != 0 && !last_gosub_rule)
                        p_warn("Rule can't be executed (null+Act)");
                     only_gotoAct = 1;
                     break;
                  case TR_TO:
                     if (last_ri.RuleJumpIndex == nrules+1)  /* Next */
                        only_gotoAct = 0;
                     else if (!cfa && nrules != 0 && !last_gosub_rule) {
                        p_warn("Rule can't be executed (null+to)");
                        only_gotoAct = 1;
                        }
                     else only_gotoAct = 1;
                     break;
		     }
		  }
               else {  /* Non-null selector */
                  switch (action_type[last_ri.RuleAction]) {
                  case TR_FIN:
                     if (!cfa && nrules == 0 && !last_gosub_rule)
                        p_warn("Rule can't be executed (sel+fin)");
                     only_gotoAct = 1;
                  case TR_ACT:
                     only_gotoAct = last_only_gotoAct;
                     break;
                  case TR_TO:
		     if (last_only_gotoAct) only_gotoAct = 
			last_ri.RuleJumpIndex != nrules+1;  /* Next */
                     else only_gotoAct = 0;
                     break;
		     }
		  }
	       }
#if RULETEST_DEBUG
            printf(", only_gotoAct=%d, last_only_gotoAct=%d\n",
               only_gotoAct, last_only_gotoAct);
#endif
            if (nrules != 0) {  /* Check flow-in to non-test actions */
               switch (ri.RuleAction) {
               case RA_COUNTPKT:
               case RA_ASSIGN:
               case RA_ASSIGNACT:
               case RA_PUSHPKTTO:
               case RA_PUSHPKTTOACT:
                  if (!only_gotoAct) {
                     if (!cf) p_error("Possible flow into rule: must GotoAct");
                     else p_error("Possible Goto to this rule: must GotoAct");
                     }
	          break;
		  }
               }
#ifdef NEW_ATR
            if (DISTRIB_ATTRIB(ri.RuleSelector)) {
               switch (ri.RuleAction) {
               case RA_COUNT:
               case RA_PUSHTO:
               case RA_PUSHTOACT:
                  p_error("Must pushPkt distribution-valued attributes");
                  break;
		  }
               }
#endif
            if (ri.RuleJumpIndex != 0) {
               switch (ri.RuleAction) {  /* Check target of goto */
               case RA_PUSHTO:
               case RA_PUSHPKTTO:
               case RA_GOSUB:
               case RA_GOTO:
                  if (toktype == TOK_SYMBOL) {
                     n = symbol_table[st_index].action;
                     if (n == RA_ASSIGN || n == RA_ASSIGNACT)
                        p_error("Must goto Action of an Assign rule");
                     if (n == RA_PUSHPKTTO || n ==RA_PUSHPKTTOACT)
                        p_error("Must goto Action of a PushPkt rule");
		     }
                  break;
                  }
               switch (ri.RuleAction) {
               case RA_COUNT:
               case RA_COUNTPKT:
                  p_error("Index no longer used by count: zero required");
                  break;
               case RA_FAIL:
               case RA_IGNORE:
                  p_error("Zero index required");
                  break;
               case RA_PUSHTO:
               case RA_PUSHTOACT:
               case RA_PUSHPKTTO:
               case RA_PUSHPKTTOACT:
               case RA_GOTO:
               case RA_GOTOACT:
               case RA_GOSUB:
               case RA_GOSUBACT:
                  if (toktype == TOK_SYMBOL) {
                     if (symbol_table[st_index].value != RF_RULES)
                        p_error("Rule index required");
                     if (ri.RuleJumpIndex == 0) {
                        sprintf(err_msg,"Undeclared label %s", 
                           symbol_table[st_index].id);
                        p_error(err_msg);
		        }
		     }
                  else if (toktype == TOK_INT &&
			(ri.RuleJumpIndex < 1 || ri.RuleJumpIndex > ms->nrules))
                     p_error("Rule index required");
                  break;
               case RA_RETURN:
                  if (toktype != TOK_INT ||
		        ri.RuleJumpIndex < 1 || ri.RuleJumpIndex > 200)
                     p_error("Gosub offset must be > 0");
                  break;
                  }
	       }
            else {  /* JumpIndex == 0 */
               switch (ri.RuleAction) {
               case RA_PUSHTO:
               case RA_PUSHTOACT:
               case RA_PUSHPKTTO:
               case RA_PUSHPKTTOACT:
               case RA_GOTO:
               case RA_GOTOACT:
               case RA_GOSUB:
               case RA_GOSUBACT:
                  p_error("Target of jump can't be zero");
                  break;
               case RA_RETURN:
                  p_error("Gosub offset must be > 0");
                  break;
		  }
	       }
            }
         if (ri.RuleAction == RA_COUNT || ri.RuleAction == RA_COUNTPKT) {
            ri.RuleJumpIndex = ++ncounts;
	    }
         if (end_of_file()) break;
         ri.RuleNbr = ++nrules;
         if (list && verbose) {
	    printf("Rule %d,%d:  %d & ", rule_set,nrules,ri.RuleSelector);
	    printruleaddress(stdout, ri.RuleMask);
            printf(" = ");
	    printruleaddress(stdout, ri.RuleMatchedValue);
	    printf(": %d, %d\n", ri.RuleAction,ri.RuleJumpIndex);
	    }
         if (doset) add_rule(ms,&ri);  /* Add rule to meter's rule table */
         memcpy(&last_ri, &ri, sizeof(ri));
         last_only_gotoAct = only_gotoAct;
         last_gosub_rule = gosub_rule;
         }
      }
   if (doset) add_rule(ms, NULL);  /* Flush download buffer */

   fclose(rfp);
   for (n = 0; n != st_top; ++n) {
      if (symbol_table[n].type == TOK_SYMBOL && symbol_table[n].value == 0) {
         printf(">>> Symbol %s is undefined\n", symbol_table[n].id);
         ++rferrors;
         }
      }
   if (rule_set == 0) {
      printf(">>> No SET statement in rule file %s\n", rfname);
      ++rferrors;
      return 0;
      }
   if (rferrors == 0) {
      ms->ruleset = rule_set;  ms->nrules = nrules;
      ms->ncounts = ncounts;
      return 1;  /* Succeed */
      }
   return 0;  /* Fail */
   }

int download_ruleset(struct meter_status *ms, int standby)
{
   struct meter_rule_info *mri;

   mri = standby ? &ms->s_ruleset : &ms->c_ruleset;
   if (ms->MIBver == 4) {  /* 'new' proposed standard MIB */
      if (ruleset_util(ms,RU_INIT, mri)) {  /* Create the table row */
         scan_rulefile(ms,1,0,standby);  /* 1 => download rules */
	 log_msg(LOG_INFO, FALSE, "loaded %d rules from %s to meter %s\n",
		 mri->ri_Size, mri->filename, ms->name);
         ruleset_util(ms,RU_READY, mri);  /* Set RulesReady */
         }
      else {  /* Don't leave a 'skeleton' row! */
         ruleset_util(ms, RU_DESTROY, mri);
         return 0;
         }
      }
   else {  /* 'old' NeTraMet v3 MIB */
      if (set_rule_info(ms,0)) {  /* Set rule table size */
         scan_rulefile(ms,1,0,standby);  /* 1 => download rules */
         }
      }
   return 1;
   }

int parse_rulefile(struct meter_status *ms,
   int list, int syntax, int standby, int restart)
{
   int using_standby;

   MIBver = ms->MIBver;
   init_symbol_table();  /* Must re-initialise for each meter */
#if LINEAR_SEARCH
   st_top = 0;  
#endif
   pass = 1;
   if (testing)
      fprintf(stderr, "parse_rf(): standby=%d, standby rf %s, current rf %s\n",
         standby, ms->s_ruleset.filename, ms->c_ruleset.filename);
   if (scan_rulefile(ms,0,0,standby)) {  /* No errors so far */
      pass = 2;
      if (scan_rulefile(ms,0,list,standby)) {  /* Still no errors */
         pass = 3;
         if (ms->MIBver == 3) {
            if (standby) {  /* Specifying standby rule set */
               if (ms->ruleset > 1 &&  /* It's a user-specified set */
                     ms->nrules != 0) {  /* Rules were specified */
                  ms->s_ruleset.ri_Index = ms->ruleset;
                  if (syntax) return 1;
                  if (ms->ruleset == ms->CurrentRuleSet)  {
                     /* Ask meter to use default rule set */
                     ms->c_ruleset.ri_Index = 1;
                     set_rule_info(ms,1);
                     ms->CurrentRuleSet = 1;  /* Now using default rule set */
                     if (ms->download_level == 0 ||
                           (ms->download_level == 1 && restart)) {
                        download_ruleset(ms, standby);
   		     }
                     if (ms->c_ruleset.filename[0] == '\0') {
                           /* No rulefile specified */
                        ms->c_ruleset.ri_Index = ms->ruleset;
                        set_rule_info(ms,1);  /* Use new standby rule set */
                        ms->CurrentRuleSet = ms->ruleset;
                        }
		     }
                  else {  /* Target rule set not in use */
                     if (ms->download_level == 0 ||
                           (ms->download_level == 1 && restart)) {
                        download_ruleset(ms, standby);
		        }
	             }
	          }
               set_rule_info(ms,2);  /* Set the standby rule set */
               ms->StandbyRuleSet = ms->s_ruleset.ri_Index;
               }
            else {  /* Specifying current rule set */
               if (testing) fprintf(stderr,
                  "Pass 3: ruleset=%d, nrules=%d, CurrentRuleSet=%d, "
                  "download_level=%d, restart=%d\n",
                  ms->ruleset,ms->nrules, ms->CurrentRuleSet,
                  ms->download_level, restart);
               if (ms->nrules != 0) {  /* Rules were specified */
                  ms->c_ruleset.ri_Index = ms->ruleset;
                  if (syntax) return 1;
                  if (ms->ruleset == ms->CurrentRuleSet)  {
                     ms->c_ruleset.ri_Index = 1;
                        /* Ask meter to use default rule set */
                     set_rule_info(ms,1);
                     ms->CurrentRuleSet = 1;  /* Now using default rule set */
                     }
                  ms->c_ruleset.ri_Index = ms->ruleset;
                  if (ms->download_level == 0 ||
                        (ms->download_level == 1 && restart)) {
                     download_ruleset(ms, 0);  /* 0 => current */
		     }
	          }
               ms->c_ruleset.ri_Index = ms->ruleset;
               if (ms->c_ruleset.ri_Index != ms->CurrentRuleSet) {
                  set_rule_info(ms,1);  /* Start using specified rule set */
                  ms->CurrentRuleSet = ms->c_ruleset.ri_Index;
		  }
	       }
            }
         else {  /* MIBver == 4 */
            if (testing) fprintf(stderr,
               "Pass 3: standby=%d, ruleset=%d, nrules=%d,"
               "download_level=%d, restart=%d\n",
               standby, ms->ruleset,ms->nrules,
               ms->download_level, restart);
            if (syntax) return 1;
            if (!ms->use_meter_rows) {
               if (standby) {  /* Specifying standby rule set */
                  if (ms->nrules != 0) {  /* Rules were specified */
                     if (download_ruleset(ms, 1)) {  /* 1 => standby */
                        if (ms->mi_Index == 0) task_util(ms, TU_INIT);
                        task_util(ms, TU_STANDBY);
                        }
                     else return 0;
   		     }
	          }
               else {  /* Specifying current rule set */
                  if (ms->nrules != 0) {  /* Rules were specified */
                     if (download_ruleset(ms, 0)) {  /* 0 => current */
                        if (ms->mi_Index == 0) task_util(ms, TU_INIT);
                        task_util(ms, TU_CURRENT);
                        }
                     else return 0;
		     }
		  }
	       }
	    }
         }
      }
   return 1;
   }
