%{
/*
 *   Copyright (c) 2000-2002 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>
#include "policies.h"

extern int line_number;
extern int rflag;
extern int vflag;

char *ip_proto_name(int);

static struct chainv4 this;
static int hard;
static long quad;
static long **list;
static int inerror;

static long otheraddr, othermask;

#define Must(x)	if (inerror = !(x) ) YYERROR
%}

%union {
	int val;
	char *text;
}

%token	NBR
%token	WORD
%token	IN
%token	OUT
%token	UDP
%token	TCP
%token	ICMP
%token	ALLOW
%token	DENY
%token	IP
%token	HARD
%token  SOFT
%token	ALL
%token	SLASH
%token  DASH
%token  COMMA
%token	NEWLINE
%token	DOT
%token	PING
%token	TRACEROUTE
%token	HOST
%token	NET
%token	FLAGS

%%
input:		  /* empty */
		| rule input
;

dir:		  IN		{ this.direction = IN;  }
		| OUT		{ this.direction = OUT; }
;

proto:		  UDP		{ this.proto = UDP; }
		| TCP		{ this.proto = TCP; }
		| ICMP		{ this.proto = ICMP; }
;

proto_prefix:	  /* empty */	{ this.proto = TCP; }
		| proto SLASH
;

port:		  WORD		{   short port;
				    Must(port=getservice($1.text,this.proto));
				    Must(add_port(port));
				}
		| ALL
		| portlist
;

portlist:	  NBR	{   Must(add_port(htons($1.val))); }
		| NBR DASH NBR { Must(add_range(htons($1.val),htons($3.val))); }
		| portlist COMMA portlist
;

service:	  /* empty */
		| PING		{ this.proto = ICMP; add_port(htons(8)); }
		| TRACEROUTE	{ this.proto = UDP; add_port(htons(12)); }
		| proto_prefix port
		| proto
;

permission:	  ALLOW		{   initialize(ALLOW); }
		| DENY		{   initialize(DENY); }
;

mode:		  /* empty */	{ hard=0; }
		| HARD		{ hard=1; }
		| SOFT		{ hard=0; }
;

command:	  mode permission
		| permission mode
;


dotquad:	  NBR DOT NBR DOT NBR DOT NBR ; {
	quad = htonl(($1.val << 24) | ($3.val << 16) | ($5.val << 8) | $7.val);
}

ip_fragment:	  NBR DOT NBR DOT NBR DOT	{
    Must(setMask( cidr4mask(24) ));
    Must(setAddr( htonl(($1.val << 24) | ($3.val << 16) || ($5.val << 8)) ));
							}
		| NBR DOT NBR DOT	{
    Must(setMask( cidr4mask(16) ));
    Must(setAddr( htonl(($1.val << 24) | ($3.val << 16)) ));
					}
		| NBR DOT {
    Must(setMask( cidr4mask(8) ));
    Must(setAddr( htonl($1.val <<24) ));
			    }
;

netmask:	  dotquad	{ Must(setMask(quad)); }
		| SLASH NBR	{ Must(setMask(cidr4mask($2.val))); }
;

ip:		  dotquad	{ Must(setAddr(quad)); }
;

address:	  ip		{ Must(setMask(0xffffffff)); }
		| ip netmask
		| ip_fragment
;

hostname:	  address
		| WORD		{   Must(list = host($1.text)); }
;

host:		  ALL		{   Must(setAddr(0) && setMask(0)); }
		| NET address
		| HOST hostname
		| hostname
;

opt_host:	/* empty */
		| HOST hostname
		| NET address
;

flags:		/* empty */
		| FLAGS NBR	{ this.flag |= $2.val; }
;

acl:		  command opt_host service dir host flags NEWLINE ; { add(); }

rule:		  NEWLINE
		| acl
		| error NEWLINE ;	{   if (inerror)
						inerror = 0;
					    else
						fprintf(stderr,
						    "Syntax error on line %d\n",
						    line_number-1);
					}

%%

long
cidr4mask(int bits)
{
	static int mask[] = {
		0x00, 0x80, 0xc0, 0xe0,
		0xf0, 0xf8, 0xfc, 0xfe
	} ;

	long ret = mask[bits%8];

	switch (bits/8) {
	case 0:		ret<<=24;			break;
	case 1:		ret = 0xff000000 | (ret<<16);	break;
	case 2:		ret = 0xffff0000 | (ret<< 8);	break;
	case 3:		ret |= 0xffffff00;		break;
	default:	ret = 0xffffffff;		break;
	}
	return htonl(ret);
}


add()
{
    if (hard || this.proto != TCP)
	attach("hard", &hard_fw[!!(this.direction==IN)], &this, list);
    else
	attach("soft", &soft_fw[!!(this.direction==IN)], &this, list);
} /* add */


attach(char *why, struct chainv4 **head, struct chainv4 *p, long **list)
{
    if (list) {
	while (*list) {
	    chain(why, head, p, **list, 0xffffffff);
	    ++list;
	}
    }
    else {
	chain(why, head, p, 0, 0);
    }
} /* attach */


chain(char *why, struct chainv4 **head, struct chainv4 *p, long address, long netmask)
{
    struct chainv4 *tmp;

    tmp = malloc(sizeof *tmp);

    memcpy(tmp, p, sizeof *tmp);

    if (netmask != 0)
	if (p->direction == IN) {
	    tmp->srcaddr = address;
	    tmp->srcmask = netmask;
	}
	else {
	    tmp->dstaddr = address;
	    tmp->dstmask = netmask;
	}
 
    if (othermask != 0)
	if (p->direction == IN) {
	    tmp->dstaddr = otheraddr;
	    tmp->dstmask = othermask;
	}
	else {
	    tmp->srcaddr = otheraddr;
	    tmp->srcmask = othermask;
	}

    if (vflag) {
	int srflag = rflag;
	rflag = 0;
    }

    if (vflag) {
	int srflag = rflag;
	rflag = 0;
	print(stderr, why, tmp);
	rflag = srflag;
    }

    if (*head == 0) {
	*head = tmp;
	tmp->next = tmp->prev = tmp;
    }
    else {
	tmp->next = (*head);
	(*head)->prev->next = tmp;
	tmp->prev = (*head)->prev;
	(*head)->prev = tmp;
    }
} /* chain */


int
getservice(char *service, int proto)
{
    struct servent *p;
    char *pr = ip_proto_name(proto);

    if ( (p = getservbyname(service,pr)) != 0)
	return p->s_port;

    fprintf(stderr, "unknown service %s/%s at line %d\n",
			service, pr, line_number);
    return 0;
}


long**
host(char *name)
{
    struct hostent *p = gethostbyname(name);

    if (p == 0) {
	fprintf(stderr, "Unrecognised host %s on line %d\n",
			    name, line_number);
	return 0;
    }
    if (p->h_addr_list == 0) {
	fprintf(stderr, "No IP addresses for host %s on line %d\n",
			    name, line_number);
	return 0;
    }
    return (long**)(p->h_addr_list);
} /* host */


int
add_port(short port)
{
    if (this.nrports >= MAX_PORTS) {
	fprintf(stderr, "Too many ports specified on line %d\n", line_number);
	inerror=1;
	return 0;
    }
    this.port[this.nrports++] = port;
    return 1;
}


add_range(short port1, short port2)
{
    int i;

    if (this.nrports+2 >= MAX_PORTS) {
	fprintf(stderr, "Too many ports specified on line %d\n", line_number);
	return 0;
    }
    if (this.flag & PORT_RANGE) {
	fprintf(stderr, "Can only give one port range on line %d\n",
			line_number);
	return 0;
    }
    this.flag |= PORT_RANGE;
    for (i=this.nrports; i>0; --i)
	this.port[i+1] = this.port[i-1];
    this.port[0] = port1;
    this.port[1] = port2;
    this.nrports += 2;
    return 1;
}


char *
ip_proto_name(int proto)
{
    switch (proto) {
    case UDP:	return "udp";
    case ICMP:	return "icmp";
    default:	return "tcp";
    }
}


void
initialize(int todo)
{
    memset(&this, 0, sizeof this);
    this.action = todo;
    otheraddr = othermask = 0;
    list = 0;
    quad = 0;
}


int
setAddr(long address)
{
    switch (this.direction) {
    case IN:
	this.srcaddr = address;
	return IN;
    case OUT:
	this.dstaddr = address;
	return OUT;
    case 0:
	otheraddr = address;
	return IN+OUT;
    default:
	fprintf(stderr, "attempt to set address on lhs of FROM|TO on line %d\n",
		line_number);
	/* oops! Not defined */
	return 0;
    }
}


int
setMask(long netmask)
{
    switch (this.direction) {
    case IN:
	this.srcmask = netmask;
	return IN;
    case OUT:
	this.dstmask = netmask;
	return OUT;
    case 0:
	othermask = netmask;
	return IN+OUT;
    default:
	fprintf(stderr, "attempt to set netmask on lhs of FROM|TO on line %d\n",
		line_number);
	/* oops! Not defined */
	return 0;
    }
}
