%{
/*
 * Copyright (c) 2004-2005 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: parser.y 12691 2010-04-01 02:13:24Z wilson.zhu $
 */

#define YYSTYPE long

/* Parser headers. */
#include "filter_rule_types.h"
#include "rule_lib.h"
#include "parser.h"
#include "utilities.h"

/* Endace headers. */
#include "dagutil.h"


/* CVS header. */
static const char * const kParserCvsHeader __attribute__ ((unused)) = "$Id: parser.y 12691 2010-04-01 02:13:24Z wilson.zhu $";

#define VERBOSE 1


static filter_rule_t* uRule = NULL;
static uint8_t uAction;
static uint8_t uProtocol;
static int uBidirectional;
static list_node_t* uSourceAddress;
static int uSourceNegated;
static ipf_port_t* uSourcePort;
static list_node_t* uDestinationAddress;
static int uDestinationNegated;
static ipf_port_t* uDestinationPort;
static int uNegation;

static FILE* uLogfile = NULL;

/* Declarations for routines generated by the flex scanner. */
extern void yy_scan_string(const char *str);
extern int yylex(void);

void yyerror(const char* error_msg);
//int yyparse(void);

void note_construction(const char* comment);
void note_progress(const char* comment);
void note_rule(filter_rule_t* rule);
void note_port(ipf_port_t* port);
void note_ip_address(ip_address_t* address);
void note_ip_address_list(list_node_t* node);
void note_number(int number);
const char* get_token_string(void);
int get_token_number(void);

%}

%start line

/* Action types. */
%token ACTION_ACCEPT
%token ACTION_REJECT

/* Protocols. */
%token PROTOCOL_ICMP
%token PROTOCOL_IP
%token PROTOCOL_TCP
%token PROTOCOL_UDP

/* Directions. */
%token UNIDIRECTIONAL
%token BIDIRECTIONAL

/* Miscellaneous. */
%token TOKEN_ANY
%token TOKEN_BODY
%token TOKEN_COLON
%token TOKEN_COMMA
%token TOKEN_COMMENT
%token TOKEN_DOLLAR
%token TOKEN_ERROR 
%token TOKEN_FORWARD_SLASH
%token TOKEN_HOSTNAME
%token TOKEN_LEFT_SQUARE 
%token TOKEN_NEGATE
%token TOKEN_NUMBER 
%token TOKEN_PERIOD
%token TOKEN_RIGHT_SQUARE 
%token TOKEN_VAR
%token TOKEN_VARIABLE

%%

line : /* Empty. */
	| TOKEN_ERROR
        {
			fprintf(stderr, "%s\n", get_token_string());
			exit(EXIT_FAILURE);
		}
    | comment_line
    | variable_declaration
	| rule_line
	;

comment_line : TOKEN_COMMENT /* Throw away. */
        {
			const char* comment = get_token_string();

			note_construction("comment_line: TOKEN_COMMENT\n");
			note_progress(comment);
			note_progress("\n");

			free((char*) comment);
		}
    ;

variable_declaration : TOKEN_VAR variable TOKEN_DOLLAR TOKEN_VARIABLE  
        { 
			const char* existing_var = get_token_string();
			variable_t* address_var = get_address_variable(existing_var);
			variable_t* port_var = get_port_variable(existing_var);

			note_construction("variable_declaration: TOKEN_VAR variable TOKEN_DOLLAR TOKEN_VARIABLE\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_progress(existing_var);
			note_progress("\n");
			
			if (address_var)
			{
				variable_t* new_var;

				note_ip_address_list(address_var->addr_list);
				
				/* add_address_variable() owns the string after this call. */
				add_address_variable((const char*) $2, address_var->addr_list); 

				new_var = get_address_variable((const char*) $2);

				assert(new_var);

				new_var->inverse = address_var->inverse;
			}
			else if (port_var)
			{
				variable_t* new_var;

				note_port(&port_var->ports);
				
				/* add_port_variable() owns the string after this call. */
				add_port_variable((const char*) $2, &port_var->ports); 

				new_var = get_port_variable((const char*) $2);

				assert(new_var);

				new_var->inverse = address_var->inverse;
			}
			else
			{
				fprintf(stderr, "could not find variable %s\n", existing_var);
				exit(EXIT_FAILURE);
			}

			free((char*) existing_var);
		}
    | TOKEN_VAR variable TOKEN_NEGATE TOKEN_DOLLAR TOKEN_VARIABLE 
        { 
			const char* existing_var = get_token_string();
			variable_t* address_var = get_address_variable(existing_var);
			variable_t* port_var = get_port_variable(existing_var);

			note_construction("variable_declaration: VAR variable TOKEN_NEGATE TOKEN_DOLLAR TOKEN_VARIABLE\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_progress(existing_var);
			note_progress("\n");
			
			if (address_var)
			{
				variable_t* new_var;

				note_ip_address_list(address_var->addr_list);
				
				/* add_address_variable() owns the string after this call. */
				add_address_variable((const char*) $2, address_var->addr_list); 

				/* Negate the variable. */
				new_var = get_address_variable((const char*) $2);
				assert(new_var);
				new_var->inverse = !(address_var->inverse);
			}
			else if (port_var)
			{
				variable_t* new_var;

				note_port(&port_var->ports);
				
				/* add_port_variable() owns the string after this call. */
				add_port_variable((const char*) $2, &port_var->ports); 

				/* Negate the variable. */
				new_var = get_port_variable((const char*) $2);
				assert(new_var);
				new_var->ports.inverse = !(port_var->ports.inverse);
			}
			else
			{
				fprintf(stderr, "could not find variable %s\n", existing_var);
				exit(EXIT_FAILURE);
			}

			free((char*) existing_var);
		}
    | TOKEN_VAR variable definite_ip_spec 
        { 
			note_construction("variable_declaration: TOKEN_VAR variable ip_spec\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_ip_address_list((list_node_t*) $3);

			/* add_address_variable() owns the string after this call. */
			add_address_variable((const char*) $2, (list_node_t*) $3); 
		}
    | TOKEN_VAR variable TOKEN_NEGATE definite_ip_spec 
        { 
			variable_t* address_var = NULL;

			note_construction("variable_declaration: TOKEN_VAR variable TOKEN_NEGATE ip_spec\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_ip_address_list((list_node_t*) $3);

			/* add_address_variable() owns the string after this call. */
			add_address_variable((const char*) $2, (list_node_t*) $3); 

			/* Negate the address list. */
			address_var = get_address_variable((const char*) $2); 

			assert(address_var);
			address_var->inverse = 1;
		}
    | TOKEN_VAR variable definite_port_spec 
        { 
			note_construction("variable_declaration: TOKEN_VAR variable port_spec\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_port((ipf_port_t*) $3);

			/* port_variable() owns the string after this call. */
			add_port_variable((const char*) $2, (ipf_port_t*) $3); 
		}
    | TOKEN_VAR variable TOKEN_NEGATE definite_port_spec 
        { 
			note_construction("variable_declaration: TOKEN_VAR variable TOKEN_NEGATE port_spec\n");
			note_progress((const char*) $2);
			note_progress("\n");
			note_port((ipf_port_t*) $3);

			((ipf_port_t*) $3)->inverse = 1;

			/* port_variable() owns the string after this call. */
			add_port_variable((const char*) $2, (ipf_port_t*) $3); 
		}
    ;

rule_line : action protocol source direction destination body 
        { 
			note_construction("rule_line: action protocol source direction destination body\n");

			/* Build rule structure from the results of the parse. */
			uRule = (filter_rule_t*) malloc(sizeof(filter_rule_t));

			if (uRule)
			{
				memset(uRule, 0, sizeof(filter_rule_t));
				uRule->action = uAction;
				uRule->protocol.protocol = uProtocol;
				uRule->protocol.mask = 0xff;
				uRule->source = uSourceAddress;
				uRule->src_inverse = uSourceNegated;
				uRule->dest = uDestinationAddress;
				uRule->dest_inverse = uDestinationNegated;
				uRule->bidirectional = uBidirectional;
				memcpy(&uRule->src_port, uSourcePort, sizeof(ipf_port_t));
				memcpy(&uRule->dest_port, uDestinationPort, sizeof(ipf_port_t));
			}

			if (uSourcePort)
			{
				dispose_port(uSourcePort);
			}
	
			if (uDestinationPort)
			{
				dispose_port(uDestinationPort);
			}

			note_rule(uRule);
		}
	;

action : ACTION_ACCEPT   { uAction = ACCEPT; note_construction("action: ACCEPT\n"); }
	| ACTION_REJECT      { uAction = REJECT; note_construction("action: REJECT\n"); }
	;

protocol : PROTOCOL_ICMP   { uProtocol = IPPROTO_ICMP; note_construction("protocol: ICMP\n"); }
	| PROTOCOL_IP                { uProtocol = IPPROTO_IP; note_construction("protocol: IP\n"); }
    | PROTOCOL_TCP               { uProtocol = IPPROTO_TCP; note_construction("protocol: TCP\n"); }
    | PROTOCOL_UDP               { uProtocol = IPPROTO_UDP; note_construction("protocol: UDP\n"); }
	;

source : ip_spec port_spec 
        { 
			note_construction("source:\n");
			note_ip_address_list((list_node_t*) $1);
			note_port((ipf_port_t*) $2);

			uSourceAddress = (list_node_t*) $1; 
			uSourcePort = (ipf_port_t*) $2; 
			uSourceNegated = uNegation;
			uNegation = 0;
		}
    ;

direction : BIDIRECTIONAL    { uBidirectional = 1; note_construction("direction: bidirectional\n"); }
    | UNIDIRECTIONAL         { uBidirectional = 0; note_construction("direction: unidirectional\n"); }
    ;

destination : ip_spec port_spec 
        { 
			note_construction("destination:\n");
			note_ip_address_list((list_node_t*) $1);
			note_port((ipf_port_t*) $2);

			uDestinationAddress = (list_node_t*) $1; 
			uDestinationPort = (ipf_port_t*) $2; 
			uDestinationNegated = uNegation;
			uNegation = 0;
		}
    ;

definite_ip_spec : ip_address
        {
			note_construction("definite_ip_spec: ip_address\n");
			note_ip_address_list((list_node_t*) $1);

			$$ = (YYSTYPE) $1;
		}
    | TOKEN_LEFT_SQUARE ip_set TOKEN_RIGHT_SQUARE
        {
			note_construction("definite_ip_spec: TOKEN_LEFT_SQUARE ip_set TOKEN_RIGHT_SQUARE\n");
			note_ip_address_list((list_node_t*) $2);

			$$ = (YYSTYPE) $2;
		}
    ;

ip_spec : TOKEN_DOLLAR variable
        {
			variable_t* address_var = get_address_variable((const char*) $2);

			note_construction("ip_spec: TOKEN_DOLLAR variable\n");
			note_progress((const char*) $2);
			note_progress("\n");

			if (address_var)
			{
				list_node_t* node = clone_address_list(address_var->addr_list);
				
				if (address_var->inverse)
				{
					uNegation = !uNegation;
				}

				$$ = (YYSTYPE) (void*) node;
			}
			else
			{
				fprintf(stderr, "error: could not find address variable: %s\n", (const char*) $2);
				exit(EXIT_FAILURE);
			}

			free((char*) $2);
		}
    | definite_ip_spec
        {
			note_construction("ip_spec: definite_ip_spec\n");
			note_ip_address_list((list_node_t*) $1);

			$$ = (YYSTYPE) $1;
		}
    | TOKEN_ANY
        {
			ip_address_t* address = (ip_address_t*) malloc(sizeof(ip_address_t));
			list_node_t* node = new_list_node(address, NULL);

			memset(address, 0, sizeof(ip_address_t));

			note_construction("ip_spec: TOKEN_ANY\n");
			note_ip_address_list(node);

			$$ = (YYSTYPE) (void*) node;
		}
    | TOKEN_NEGATE ip_spec
        {
			note_construction("ip_spec: TOKEN_NEGATE ip_spec\n");
			note_progress("negation\n");
			note_ip_address_list((list_node_t*) $2);

			uNegation = !uNegation;
			$$ = (YYSTYPE) $2;
		}
    ;

ip_set : ip_address
        {
			note_construction("ip_set: ip_address\n");
			note_ip_address_list((list_node_t*) $1);

			$$ = (YYSTYPE) $1;
		}
    | ip_address TOKEN_COMMA ip_set
        {
			list_node_t* node = (list_node_t*) $1;
			list_node_t* set = (list_node_t*) $3;

			note_construction("ip_set: ip_address TOKEN_COMMA ip_set\n");
			note_ip_address_list((list_node_t*) $1);
			note_ip_address_list((list_node_t*) $3);

			node->next = set;

			$$ = (YYSTYPE) (void*) node;
		}
    ;

ip_address : TOKEN_HOSTNAME
        {
			ip_address_t* address = (ip_address_t*) malloc(sizeof(ip_address_t));
			list_node_t* node = new_list_node(address, NULL);
			const char* name = get_token_string();

			note_construction("ip_address: TOKEN_HOSTNAME\n");

			memset(address, 0, sizeof(ip_address_t));
			address->ip4_addr = inet_addr(name);
			address->mask = 0xffffffff;

			note_ip_address_list(node);

			free((char*) name);
			$$ = (YYSTYPE) (void*) node;
		}
    | network
        {
			note_construction("ip_address: network\n");
			note_ip_address_list((list_node_t*) $1);

			$$ = (YYSTYPE) $1;
		}
    ;

network : TOKEN_HOSTNAME TOKEN_FORWARD_SLASH TOKEN_NUMBER
        {
			ip_address_t* address = (ip_address_t*) malloc(sizeof(ip_address_t));
			list_node_t* node = new_list_node(address, NULL);
			const char* name = get_token_string();
			int network_bits = get_token_number();

			note_construction("network: TOKEN_HOSTNAME TOKEN_FORWARD_SLASH TOKEN_NUMBER\n");
			note_progress(name);
			note_progress("\n");
			note_number(network_bits);

			memset(address, 0, sizeof(ip_address_t));
			address->ip4_addr = inet_addr(name);
			address->mask = 0;

			if (network_bits)
			{
				int index;
				int zero_bits = 32 - network_bits;

				for (index = 0; index < 32; index++)
				{
					if (index >= zero_bits)
					{
						address->mask |= (1 << index);
					}
				}
			}

			/* IP addresses are stored in network byte order, so in order to bitwise AND them we
			 * store masks in network byte order too.
			 */
			address->mask = htonl(address->mask);

			note_ip_address_list(node);

			free((char*) name);
			$$ = (YYSTYPE) (void*) node;
		}
    ;
 
definite_port_spec : single_port
        {
			ipf_port_t* port = new_port();
		
			note_construction("definite_port_spec: single_port\n");

			port->first = htons((int) $1);
			port->last = htons((int) $1);

			note_port(port);

			$$ = (YYSTYPE) (void*) port;
		}
    | port_range
        {
			note_construction("definite_port_spec: port_range\n");
			$$ = (YYSTYPE) $1;
		}
    ;
 
port_spec : TOKEN_DOLLAR variable
        {
			variable_t* port_var = get_port_variable((const char*) $2);

			note_construction("port_spec: TOKEN_DOLLAR variable\n");
			note_progress((const char*) $2);
			note_progress("\n");

			if (port_var)
			{
				ipf_port_t* port = new_port();
				
				memcpy(port, &port_var->ports, sizeof(ipf_port_t));
				
				$$ = (YYSTYPE) (void*) port;
			}
			else
			{
				fprintf(stderr, "error: could not find port variable: %s\n", (const char*) $2);
				exit(EXIT_FAILURE);
			}

			free((char*) $2);
		}
    |  definite_port_spec
        {
			note_construction("port_spec: definite_port_spec\n");
			note_port((ipf_port_t*) $1);
			
			$$ = (YYSTYPE) $1;
		}
    | TOKEN_ANY
        {
			ipf_port_t* port = new_port();
		
			note_construction("port_spec: TOKEN_ANY\n");

			port->any = 1;

			note_port(port);

			$$ = (YYSTYPE) (void*) port;
		}
    | TOKEN_NEGATE port_spec
        {
			note_construction("port_spec: TOKEN_NEGATE port_spec\n");

			((ipf_port_t*) $2)->inverse = !((ipf_port_t*) $2)->inverse;

			note_port((ipf_port_t*) $2);

			$$ = (YYSTYPE) (void*) $2;
		}
    ;

port_range : single_port TOKEN_COLON single_port
        {
			ipf_port_t* port = new_port();
		
			note_construction("port_range: single_port TOKEN_COLON single_port\n");

			port->first = htons((int) $1);
			port->last = htons((int) $3);

			note_port(port);

			$$ = (YYSTYPE) (void*) port;
		}
    | TOKEN_COLON single_port
        {
			ipf_port_t* port = new_port();
		
			note_construction("port_range: TOKEN_COLON single_port\n");
		
			port->last = htons((int) $2);

			note_port(port);

			$$ = (YYSTYPE) (void*) port;
		}
    | single_port TOKEN_COLON
        {
			ipf_port_t* port = new_port();
		
			note_construction("port_range: single_port TOKEN_COLON\n");
		
			port->first = htons((int) $1);

			note_port(port);

			$$ = (YYSTYPE) (void*) port;
		}
    ;

single_port : TOKEN_NUMBER 
        { 
			note_construction("single_port: TOKEN_NUMBER\n");
			$$ = (YYSTYPE) get_token_number(); 
			note_number(get_token_number());
		}
    ;

variable : TOKEN_VARIABLE 
        { 
			note_construction("variable: TOKEN_VARIABLE\n");
			$$ = (YYSTYPE) (void*) get_token_string(); 
			note_progress((const char*) $$);
			note_progress("\n");
		}
    ;


body : TOKEN_BODY
        { 
			note_construction("body: TOKEN_BODY\n");
			$$ = (YYSTYPE) NULL;
			note_progress((const char*) $$);
			note_progress("\n");
		}
    | /* Empty. */
        {
			$$ = (YYSTYPE) NULL;
		}
    ;

%%

void
yyerror(const char* error_msg)
{
	fprintf(stderr, "yyerror(): %s\n", error_msg);
}


void
note_construction(const char* comment)
{
#if VERBOSE
	if (uLogfile)
	{
		fprintf(uLogfile, "\n");
		fprintf(uLogfile, "%s", comment);
	}
#endif /* VERBOSE */
}


void
note_progress(const char* comment)
{
#if VERBOSE
	if (uLogfile)
	{
		fprintf(uLogfile, "%s", comment);
	}
#endif /* VERBOSE */
}


void
note_rule(filter_rule_t* rule)
{
#if VERBOSE
	if (uLogfile)
	{
		display_rule(uLogfile, rule);
		fprintf(uLogfile, "\n");
	}
#endif /* VERBOSE */
}


void
note_port(ipf_port_t* port)
{
#if VERBOSE
	if (uLogfile)
	{
		display_port(uLogfile, port);
		fprintf(uLogfile, "\n");
	}
#endif /* VERBOSE */
}


void
note_ip_address(ip_address_t* address)
{
#if VERBOSE
	if (uLogfile)
	{
		display_ip_address(uLogfile, address);
		fprintf(uLogfile, "\n");
	}
#endif /* VERBOSE */
}


void
note_ip_address_list(list_node_t* node)
{
#if VERBOSE
	if (uLogfile)
	{
		display_ip_address_list(uLogfile, node);
		fprintf(uLogfile, "\n");
	}
#endif /* VERBOSE */
}


void
note_number(int number)
{
#if VERBOSE
	if (uLogfile)
	{
		fprintf(uLogfile, "%d\n", number);
	}
#endif /* VERBOSE */
}


filter_rule_t*
parse_single_line(const char* line, FILE* logfile)
{
	filter_rule_t* result = NULL;

	/* Reset variables. */
	uLogfile = logfile;
	uAction = 0;
	uProtocol = 0;
	uBidirectional = 0;
	uSourceAddress = NULL;
	uSourceNegated = 0;
	uSourcePort = NULL;
	uDestinationAddress = NULL;
	uDestinationNegated = 0;
	uDestinationPort = NULL;
	uNegation = 0;

	yy_scan_string(line);
	yyparse();

	result = uRule;
	uRule = NULL;

	return result;
}
