%{
/*
  config.y - functions parsing the configuration file
 
  (c) 1999-2002 Robert Cheramy <robert@cheramy.net>
  (c) 2000      Samuel Hocevar <sam@via.ecp.fr>
  (c) 2001      Loïc Tortay & IN2P3 Computing Center <tortay@cc.in2p3.fr>
 
  Thanks to Etienne BERNARD <eb@via.ecp.fr> for his
  * little manual for Lex and Yacc
    http://www.via.ecp.fr/~eb/textes/minimanlexyacc-english.html
  * sample code in ippl (http://www.via.ecp.fr/~hugo/ippl)
    which helped me to understand Lex and Yacc

  200010xx : sam & tibob : various config options
    
 */

/*
 *  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 FITNESS 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.
 */

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "config.h"
#include "filter.h"
#include "init.h"
#include "data.h"
#include "utils.h"

void yyerror(char *);
void parseerror(char *, int);
int yylex(void);
int checkdump = 2;
int yyfailed = 0;

extern int line;
extern int promisc;

extern char *device;
extern struct OptionsType Options;
extern struct AllLogsType *pAllLogs;
extern int yydebug;

ipfm_timezone tz;

#ifdef YYDEBUG
 yydebug = 1;
#endif
%}


%union {
  char               *stringval;
  double             doubleval;
  int (*SortFunc)(const void *, const void *);
  long               longval;
  struct ipfm_filter *filterval;
  struct ipfm_host   hostval;
}


%token AFTER
%token APPEND
%token ALWAYS
%token BOTH
%token CLEAR
%token DEVICE
%token DUMP
%token EOL
%token EVERY
%token FILENAME
%token FROM
%token LOCALTIME
%token LOG
%token NEVER
%token NEWLOG
%token NOT
%token NONE
%token REPLACE
%token SLASH
%token SORT
%token TO
%token UNIVERSALTIME
%token WITH


%token<SortFunc>  SORTFUNC
%token<longval>   DATETYPE
%token<longval>   DNS
%token<longval>   NUMBER
%token<longval>   PROMISC
%token<stringval> IP
%token<stringval> STRING

%type<filterval> Rule
%type<hostval>   Domain
%type<longval>   After
%type<longval>   Clear
%type<longval>   Date
%type<longval>   Direction
%type<longval>   Dump
%type<longval>   Not
%type<stringval> Device

%start Input
%%

Input:
        /* Empty */
      | Input Line
      ;

Line:
        EOL
      | APPEND EOL {
          pAllLogs->Append = 1;
        }
      | REPLACE EOL {
          pAllLogs->Append = 0;
        }
      | Rule EOL {
          $1->next = pAllLogs->Filter; pAllLogs->Filter = $1;
        }
      | Dump EOL {
          if (2 != checkdump) {
            parseerror("Misplaced DUMP directive", line);
            checkdump = -1;
          } else {
            checkdump = 1;
            pAllLogs->DumpInterval = $1;
            pAllLogs->NextDump = ((time(NULL) / $1) + 1) * $1;
          }
        }
      | Dump After EOL {
          if (2 != checkdump) {
            parseerror("Misplaced DUMP directive", line);
            checkdump = -1;
          } else {
            checkdump = 1;
            pAllLogs->DumpInterval = $1;
            /* We can be sure that NextDump > time(NULL)
               _and_ that it's the closest value. sam did the maths. */
            pAllLogs->NextDump = (((time(NULL) - $2) / $1) + 1) * $1 + $2;
          }
        }
      | CLEAR NEVER EOL {
          pAllLogs->ClearInterval = 0;
          pAllLogs->ClearCounter = 0;
        }
      | CLEAR ALWAYS EOL {
          pAllLogs->ClearInterval = 1;
          pAllLogs->ClearCounter = 1;
        }
      | Clear EOL {
          if ((0 >= checkdump) || !pAllLogs->DumpInterval) {
            parseerror("Misplaced CLEAR directive", line);
            checkdump = -1;
          } else {
            checkdump = 0;
            if ($1 != ($1 / pAllLogs->DumpInterval * pAllLogs->DumpInterval)) {
              parseerror("DUMP interval does not divide CLEAR interval", line);
            } else {
              /* A quite complicated calculation to check when the
                 next clear will occur. The method is the same as the
                 one used to calculate NextDump, except we have to
                 divide by DumpInterval to get a counter, not a date. */
              pAllLogs->ClearInterval = $1 / pAllLogs->DumpInterval;
              pAllLogs->ClearCounter =
                ( (((pAllLogs->NextDump) / $1) + 1) * $1
                  - ((pAllLogs->NextDump / pAllLogs->DumpInterval)
                     * pAllLogs->DumpInterval) ) / pAllLogs->DumpInterval + 1;
            }
          }
        }
      | Clear After EOL {
          if ((0 >= checkdump) || !pAllLogs->DumpInterval) {
            parseerror("Misplaced CLEAR directive", line);
            checkdump = -1;
          } else {
            checkdump = 0;
            if ($2 != ($2 / pAllLogs->DumpInterval * pAllLogs->DumpInterval)) {
              parseerror("DUMP interval does not divide CLEAR delay", line);
            } else if ($1 != ($1 / pAllLogs->DumpInterval
                              * pAllLogs->DumpInterval)) {
              parseerror("DUMP interval does not divide CLEAR interval", line);
            } else {
              /* Check when the next clear will occur (see above) */
              pAllLogs->ClearInterval = $1 / pAllLogs->DumpInterval;
              pAllLogs->ClearCounter =
                ( (((pAllLogs->NextDump - $2) / $1) + 1) * $1 + $2
                  - ((pAllLogs->NextDump / pAllLogs->DumpInterval)
                     * pAllLogs->DumpInterval) ) / pAllLogs->DumpInterval + 1;
            }
          }
        }
      | FILENAME STRING EOL {
          xfree(pAllLogs->LogFile);
          pAllLogs->LogFile = $2;
        }
      | Device EOL {
          if (NULL != device) xfree(device);
          device = $1;
        }
      | SORT SORTFUNC {
          pAllLogs->Sort = 1;
          pAllLogs->SortFunc = $2;
        }
      | PROMISC {
          promisc = $1;
        }
      | DNS {
          pAllLogs->ReverseLookup = $1;
        }
      | NEWLOG EOL {
          struct AllLogsType *pNewLog;
          pNewLog = (struct AllLogsType *) xmalloc (sizeof(struct AllLogsType));
          pNewLog->Next = pAllLogs;
          pNewLog->Filter = NULL;
          pNewLog->Data = NULL;
          pNewLog->DumpInterval = DEFAULT_OPTIONS_DUMPINTERVAL;
          pNewLog->ClearInterval = DEFAULT_OPTIONS_CLEARINTERVAL;
          pNewLog->ClearCounter = DEFAULT_OPTIONS_CLEARCOUNTER;
          pNewLog->NextDump = ((time(NULL) / DEFAULT_OPTIONS_DUMPINTERVAL) + 1) * DEFAULT_OPTIONS_DUMPINTERVAL;
          pNewLog->LogFile = strdup(DEFAULT_OPTIONS_LOGFILE);
          pNewLog->Sort = DEFAULT_OPTIONS_SORT;
          pNewLog->SortFunc = NULL;
          pNewLog->ReverseLookup = DEFAULT_OPTIONS_REVERSELOOKUP;
          pNewLog->Append = DEFAULT_OPTIONS_APPEND;
  
          checkdump = 2;
          pAllLogs = pNewLog;
        }
      | LOCALTIME EOL {
          tz = local;
        }
      | UNIVERSALTIME EOL {
          tz = UTC;
        }
      | error EOL {
          parseerror("Skipping invalid line", line);
        }
      ;

Rule:
        LOG Direction Domain Not WITH Domain {
          $$ = (struct ipfm_filter *) xmalloc(sizeof(struct ipfm_filter));
	  $$->tlog  = $2;
	  $$->thost = $3;
	  $$->olog  = $4;
	  $$->ohost = $6;
        }
      | LOG Direction Domain {
          $$ = (struct ipfm_filter *) xmalloc(sizeof(struct ipfm_filter));
	  $$->tlog  = $2;
	  $$->thost = $3;
	  $$->olog  = 1;
	  $$->ohost.ip = 0;
	  $$->ohost.mask = 0;
        }
      ;

Direction:
        NONE {
          $$ = IPFM_LOG_NONE;
        }
      | FROM {
          $$ = IPFM_LOG_FROM;
        }
      | TO {
          $$ = IPFM_LOG_TO;
        }
      | BOTH {
          $$ = IPFM_LOG_BOTH;
        }
      | /* Nothing */ {
          $$ = IPFM_LOG_BOTH;
        }
      ;

Not:
        NOT {
          /* NO LOG */
          $$ = 0;
        }
      | /* Notiong -> LOG */ {
          $$ = 1;
        }
      ;

Domain:
        IP SLASH NUMBER {
#ifdef HAVE_INET_ATON
          struct in_addr inp;
          inet_aton($1, &inp);
          $$.ip = inp.s_addr;
#else
          $$.ip = inet_addr($1);
#endif
          if ($3 < 1 || $3 > 32) {
            parseerror("Invalid CIDR netmask", line);
          }
          $$.mask = htonl(~((1 << (32 - $3)) - 1));

          xfree($1);
        }
      | IP SLASH IP {
#ifdef HAVE_INET_ATON
	  struct in_addr inp;
	  inet_aton($1, &inp);
	  $$.ip = inp.s_addr;
	  inet_aton($3, &inp);
	  $$.mask = inp.s_addr;
#else
	  $$.ip = inet_addr($1);
	  $$.mask = inet_addr($3);
#endif
	  xfree($1); xfree($3);
	}
      | IP {
#ifdef HAVE_INET_ATON
          struct in_addr inp;
          inet_aton($1, &inp);
          $$.ip = inp.s_addr;
#else
	  $$.ip = inet_addr($1);
#endif
          $$.mask = 0xffffffff;
	  xfree($1);
        }
      | /* Nothing */ {
	  $$.ip   = 0;
	  $$.mask = 0;
	}
      ;

Dump:
        DUMP EVERY Date {
	  $$ = $3;
        }
      ;

Clear:
        CLEAR EVERY Date {
	  $$ = $3;
        }
      ;

After:
        AFTER Date {
	  $$ = $2;
        }
      ;

Date:
        Date NUMBER DATETYPE {
          $$ = $1 + $2 * $3;
        }
      | NUMBER DATETYPE {
          $$ = $1 * $2;
        }
      ;

Device:
        DEVICE STRING {
          $$ = $2;
        }
      ;

%%

void yyerror(char *s) {
  yyfailed = 1;
}

void parseerror(char *s, int line) {
  yyfailed = 1;
  fprintf(stderr, "%s : parse error line %d: %s\n", Options.ConfigFile, line, s);
}

