%{
/*
 *  ippl.y - functions parsing the configuration file
 *
 *  Copyright (C) 1998, 1999 Etienne Bernard
 *  *Small* modifications for UDP by Hugo Haas, 1999
 *  Now resolves IP addresses at parsing time whenever possible,
 *    Etienne Bernard 1999
 *  Keeps hostname for refreshing. Added "expire" rule. Hugo Haas 1999
 *  Added a "detailed" and a "noresolve" rule. Hugo Haas 1999
 *  Added a "log-in" rule.
 *
 *  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/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>

#include "configuration.h"
#include "netutils.h"
#include "filter.h"
#include "log.h"

void yyerror(char *);
void print_error(char *, int);
int yylex(void);

const char *proto;

extern struct filter_entry *filter;

extern int line_number;

/* User used to run threads */
char *used_user = NULL;

/* Expiration of DNS data */
unsigned int dns_expire;

/* Protocols logged */
unsigned short log_protocols;

/* Should name resolving be done? */
unsigned short resolve_protocols;

/* Logging format for each protocol */
unsigned short icmp_format;
unsigned short tcp_format;
unsigned short udp_format;

/* Should we end of TCP connections ? */
unsigned short log_closing;

/* Should we use ident requests? */
unsigned short use_ident;

/* Logging information */
extern struct loginfo icmp_log, tcp_log, udp_log;

struct log_info switches;
%}

%union {
  long longval;
  char * stringval;
  struct portrange_struct rangeval;
  struct portranges_struct portdesc;
  union loginfo_union loginfoval;
  struct filter_entry * entryval;
  struct {
    long protocol;
    union loginfo_union loginfoval;
  } protocolval;
  struct {
    struct hostdesc_struct fromval;
    struct hostdesc_struct toval;
  } fromtoval;
  struct hostdesc_struct hostdesc;
}

%token<stringval> IP HOSTMASK IDENTIFIER FILENAME
%token<longval> NUMBER

%token LOGFORMAT DETAILED SHORT NORMAL RESOLVE NORESOLVE IDENT NOIDENT LOGCLOSING NOLOGCLOSING
%token RUN RUNAS EXPIRE LOG_IN LOG IGNORE FROM TO TYPE PORT SRCPORT OPTION COMMA
%token ICMP TCP UDP ALL

%token RANGE SLASH EOL

%type<entryval> Rule
%type<longval> Action ProtoList ProtoItem LogFormat
%type<rangeval> Range
%type<hostdesc> From To
%type<fromtoval> FromTo
%type<protocolval> Protocol
%type<rangeval> Port SrcPort
%type<loginfoval> Type
%type<stringval> Filename
%type<portdesc> PortDesc

%start Input
%%

Input:
          /* Empty */
        | Input Line
        ;

Line:
          EOL

        | IDENT EOL
          { use_ident = TRUE; }
        | NOIDENT EOL
          { use_ident = FALSE; }

        | RESOLVE ProtoList EOL
          { resolve_protocols |= $2; }
        | NORESOLVE ProtoList EOL
          { resolve_protocols &= ~$2; }

        | LOGCLOSING EOL
          { log_closing = TRUE; }
        | NOLOGCLOSING EOL
          { log_closing = FALSE; }

        | LOGFORMAT LogFormat ProtoList EOL
          {
            if ($3 & RUN_ICMP)
              icmp_format = $2;
            if ($3 & RUN_TCP)
              tcp_format = $2;
            if ($3 & RUN_UDP)
              udp_format = $2;
          }

        | RUN ProtoList EOL
          { log_protocols |= $2; }

        | RUNAS IDENTIFIER EOL
          {
            struct passwd *argument = getpwnam($2);
            if (argument != NULL) {
              free(used_user);
              used_user = $2;
            }
            else {
              print_error("unknown user.", line_number);
              free($2);
            }
          }

        | EXPIRE NUMBER EOL
          { dns_expire = $2; }

        | LOG_IN Login EOL

        | Rule EOL
           { if ($1) { $1->next = filter; filter = $1; } }
        | error EOL
          {
            print_error("ignoring invalid line.", line_number-1);
          }
        ;

ProtoList:
          ProtoItem
        | ProtoList ProtoItem
          { $$ = $1 | $2; }
        ;

ProtoItem:
          ICMP
          { $$ = RUN_ICMP; }
        | TCP
          { $$ = RUN_TCP;  }
        | UDP
          { $$ = RUN_UDP;  }
        | ALL
          { $$ = (RUN_ICMP | RUN_TCP | RUN_UDP); }
        | error
          {
            print_error("unknown protocol.", line_number); yyclearin;
            $$ = 0;
          }
        ;

LogFormat:
          NORMAL
          { $$ = LOGFORMAT_NORMAL; }
        | SHORT
          { $$ = LOGFORMAT_SHORT; }
        | DETAILED
          { $$ = LOGFORMAT_DETAILED; }
        ;

Login:
          ICMP Filename
            { if ($2) { log_in_file(&icmp_log, $2); free($2); } }
        | TCP Filename
            { if ($2) { log_in_file(&tcp_log, $2); free($2); } }
        | UDP Filename
            { if ($2) { log_in_file(&udp_log, $2); free($2); } }
        | ALL Filename
          {
            if ($2) {
              log_in_file(&icmp_log, $2);
              log_in_file(&tcp_log, $2);
              log_in_file(&udp_log, $2);
              free($2);
            }
          }
        | error
          { print_error("unknown protocol.", line_number); yyclearin; }
        ;

Filename:
          FILENAME
          { $$ = $1; }
        | error
          {
            print_error("incorrect filename.", line_number); yyclearin;
            $$ = NULL;
          }
        ;

Rule:
          Action
          {
            switches.log = -1;
            switches.ident = use_ident;
            switches.resolve = -1;
            switches.logformat = -1;
            switches.logclosing = log_closing;
          } 
          Switches Protocol FromTo
          {
            $$ = (struct filter_entry *)malloc(sizeof(struct filter_entry));
            $$->log = $1;
            $$->ident = switches.ident;
            $$->logclosing = switches.logclosing;
            $$->resolve = switches.resolve;
            $$->logformat = switches.logformat;
            $$->protocol = $4.protocol;
            $$->loginfo = $4.loginfoval;
            $$->todesc = $5.toval;
            $$->fromdesc = $5.fromval;
          }
        ;

Action:
          LOG     { $$ = 1; }
        | IGNORE  { $$ = 0; }
        ;

Switches:
        
        | OPTION SwitchList
        ;

SwitchList:
          Switch
        | SwitchList COMMA Switch
        ;

Switch:
          IDENT      { switches.ident = TRUE; }
        | NOIDENT    { switches.ident = FALSE; }
        | RESOLVE    { switches.resolve = RUN_ICMP | RUN_TCP | RUN_UDP; }
        | NORESOLVE  { switches.resolve = 0; }
        | SHORT      { switches.logformat = LOGFORMAT_SHORT; }
        | NORMAL     { switches.logformat = LOGFORMAT_NORMAL; }
        | DETAILED   { switches.logformat = LOGFORMAT_DETAILED; }
        | LOGCLOSING { switches.logclosing = TRUE; }
        | NOLOGCLOSING { switches.logclosing = FALSE; }
        ;

Protocol:
          ICMP Type
          { $$.protocol = RUN_ICMP; $$.loginfoval = $2;}
        | TCP
          { proto = "tcp"; }
          PortDesc
          { $$.protocol = RUN_TCP; $$.loginfoval.portranges = $3; }
        | UDP
          { proto = "udp"; }
          PortDesc
          { $$.protocol = RUN_UDP; $$.loginfoval.portranges = $3; }
        | ALL
          { $$.protocol = RUN_ICMP | RUN_TCP | RUN_UDP;
            $$.loginfoval.icmptype = NO_ICMP_TYPE; }
        ;

Type:
          { $$.icmptype = NO_ICMP_TYPE; }
        | TYPE NUMBER { $$.icmptype = $2; }
        ;

PortDesc:
          { $$.dst.range_min = NOT_DEFINED;
            $$.dst.range_max = NOT_DEFINED;
            $$.src.range_min = NOT_DEFINED;
            $$.src.range_max = NOT_DEFINED; }
        | Port
          { $$.dst = $1;
            $$.src.range_min = NOT_DEFINED;
            $$.src.range_max = NOT_DEFINED; }
        | SrcPort
          { $$.dst.range_min = NOT_DEFINED;
            $$.dst.range_max = NOT_DEFINED;
            $$.src = $1; }
        | Port SrcPort
          { $$.dst = $1;
            $$.src = $2; }
        | SrcPort Port
          { $$.src = $1;
            $$.dst = $2; }
        ;

Port:
         PORT Range
          { $$ = $2; }
       ;

SrcPort:
          SRCPORT Range
          { $$ = $2; }
       ;

Range:
          NUMBER               { $$.range_min = $$.range_max = $1; }
        | IDENTIFIER           {
                                 struct servent *s = getservbyname($1, proto);
                                 if (s)
                                   $$.range_min = $$.range_max = ntohs(s->s_port);
                                 else {
                                   $$.range_min = $$.range_max = NOT_DEFINED;
                                   print_error("unknown service.", line_number);
                                   YYERROR;
                                 }
                                 free($1);
                               }
        | RANGE NUMBER         { $$.range_min = NOT_DEFINED;
                                 $$.range_max = $2;                    }
        | NUMBER RANGE         { $$.range_min = $1;
                                 $$.range_max = NOT_DEFINED;           }
        | NUMBER RANGE NUMBER  { $$.range_min = $1; $$.range_max = $3; }
        ;

FromTo:
          {
            $$.fromval.address = $$.fromval.mask = 0;
            $$.fromval.hostname = $$.fromval.hostmask = NULL;
            $$.toval.address = $$.toval.mask = 0;
            $$.toval.hostname = $$.toval.hostmask = NULL;
          }
        | From
          {
            $$.fromval = $1;
            $$.toval.address = $$.toval.mask = 0;
            $$.toval.hostname = $$.toval.hostmask = NULL;
          }
        | From To
          {
            $$.fromval = $1;
            $$.toval = $2;
          }
        | To
          {
            $$.toval = $1;
            $$.fromval.address = $$.fromval.mask = 0;
            $$.fromval.hostname = $$.fromval.hostmask = NULL;
          }
        | To From
          {
            $$.toval = $1;
            $$.fromval = $2;
          }
        ;

From:
          FROM IP
            {
              struct in_addr inp;
              inet_aton($2, &inp);
              $$.address = inp.s_addr;
              $$.mask = 0xffffffff;
              $$.hostname = NULL;
              $$.hostmask = NULL;
              free($2);
            }
        | FROM IP SLASH IP
            {
              struct in_addr inp;
              inet_aton($2, &inp);
              $$.address = inp.s_addr;
              inet_aton($4, &inp);
              $$.mask = inp.s_addr;
              $$.hostname = NULL;              
              $$.hostmask = NULL;
              free($2); free($4);
            }
        | FROM IP SLASH NUMBER
            {
              struct in_addr inp;
              inet_aton($2, &inp);
              $$.address = inp.s_addr;
              $$.mask = htonl(0xffffffff << (32 - $4));
              $$.hostname = NULL;              
              $$.hostmask = NULL;
              free($2);
            }
        | FROM HOSTMASK
            {
              if (strchr($2, '*')) {
                $$.hostmask = $2;
                $$.hostname = NULL;
                $$.address = 0;
                $$.mask = 0;
              } else {
                struct hostent * host;
                if ((host = gethostbyname((const char *)$2)) == 0) {
                  $$.address = 0;
                  $$.mask = 0;
                  print_error("cannot resolve hostname.", line_number);
                } else {
                  memcpy(&($$.address), host->h_addr, host->h_length);
                  $$.mask = 0xffffffff;
                };
                $$.hostname = $2;
                $$.hostmask = NULL;
              }
            }
        | FROM IDENTIFIER
            {
              struct hostent * host;
              if ((host = gethostbyname((const char *)$2)) == 0) {
                $$.address = 0;
                $$.mask = 0;
                print_error("cannot resolve hostname.", line_number);
              } else {
                memcpy(&($$.address), host->h_addr, host->h_length);
                $$.mask = 0xffffffff;
              };
              $$.hostname = $2;
              $$.hostmask = NULL;
            }
        ;

To:
          TO IP
            {
              struct in_addr inp;
              inet_aton($2, &inp);
              $$.address = inp.s_addr;
              $$.mask = 0xffffffff;
              $$.hostname = NULL;
              $$.hostmask = NULL;
              free($2);
            }
        | TO HOSTMASK
            {
              if (strchr($2, '*')) {
                $$.address = $$.mask = 0;
                print_error("wildcards are not allowed in a \"TO\" rule.", line_number);
              } else {
                struct hostent * host;
                if ((host = gethostbyname((const char *)$2)) == 0) {
                  $$.address = 0;
                  $$.mask = 0;
                  print_error("cannot resolve hostname.", line_number);
                } else {
                  memcpy(&($$.address), host->h_addr, host->h_length);
                  $$.mask = 0xffffffff;
                };
                $$.hostname = $2;
                $$.hostmask = NULL;
              }
            }
        | TO IDENTIFIER
            {
              struct hostent * host;
              if ((host = gethostbyname((const char *)$2)) == 0) {
                $$.address = $$.mask = 0;
                print_error("cannot resolve hostname.", line_number);
              } else {
                memcpy(&($$.address), host->h_addr, host->h_length);
                $$.mask = 0xffffffff;
              };
              $$.hostname = $2;
              $$.hostmask = NULL;
            }
        ;

%%

void yyerror(char *s) {
  return;
}

/*
 * print_error
 *
 * Print an error using the logging mechanism
 * NOTE: this means that the parsing may need to lock log_mutex.
 */

void print_error(char *s, int l) {
  extern struct loginfo log;

  log.log(log.level_or_fd, "CFG: Parse error line %d: %s",l,s);
}
