/*
 * Copyright (C) 2001-2004 by Darren Reed.
 *
 * See the IPFILTER.LICENCE file for details on licencing.
 */
%{
#include <sys/types.h>
#include <sys/ioctl.h>
#include "ipf.h"
#include "opts.h"
#include "kmem.h"
#include "ipscan_l.h"
#include "netinet/ip_scan.h"

#define	YYDEBUG	1

extern	char	*optarg;
extern	void	yyerror __P((char *));
extern	int	yyparse __P((void));
extern	int	yylex __P((void));
extern	int	yydebug;
extern	FILE	*yyin;
extern	int	yylineNum;
extern	void	printbuf __P((char *, int, int));


void		printent __P((ipscan_t *));
void		showlist __P((void));
int		getportnum __P((char *));
struct in_addr	gethostip __P((char *));
struct in_addr	combine __P((int, int, int, int));
char		**makepair __P((char *, char *));
void		addtag __P((char *, char **, char **, struct action *));
int		cram __P((char *, char *));
void		usage __P((char *));
int		main __P((int, char **));

int		opts = 0;
int		fd = -1;


%}

%union	{
	char	*str;
	char	**astr;
	u_32_t	num;
	struct	in_addr	ipa;
	struct	action	act;
	union	i6addr	ip6;
}

%type	<str> tag
%type	<act> action redirect result
%type	<ipa> ipaddr
%type	<num> portnum
%type	<astr> matchup onehalf twohalves

%token  <num>   YY_NUMBER YY_HEX
%token  <str>   YY_STR
%token          YY_COMMENT 
%token          YY_CMP_EQ YY_CMP_NE YY_CMP_LE YY_CMP_GE YY_CMP_LT YY_CMP_GT
%token          YY_RANGE_OUT YY_RANGE_IN
%token  <ip6>   YY_IPV6
%token		IPSL_START IPSL_STARTGROUP IPSL_CONTENT

%token	IPSL_CLOSE IPSL_TRACK IPSL_EOF IPSL_REDIRECT IPSL_ELSE

%%
file:	line ';'
	| assign ';'
	| file line ';'
	| file assign ';'
	| YY_COMMENT
	;

line:	IPSL_START dline
	| IPSL_STARTGROUP gline
	| IPSL_CONTENT oline
	;

dline:	cline					{ resetlexer(); }
	| sline					{ resetlexer(); }
	| csline				{ resetlexer(); }
	;

gline:	YY_STR ':' glist '=' action
	;

oline:	cline
	| sline
	| csline
	;

assign:	YY_STR assigning YY_STR
						{ set_variable($1, $3);
						  resetlexer();
						  free($1);
						  free($3);
						}
	;

assigning:
	'='					{ yyvarnext = 1; }
	;

cline:	tag ':' matchup '=' action		{ addtag($1, $3, NULL, &$5); }
	;

sline:	tag ':' '(' ')' ',' matchup '=' action	{ addtag($1, NULL, $6, &$8); }
	;

csline:	tag ':' matchup ',' matchup '=' action	{ addtag($1, $3, $5, &$7); }
	;

glist:	YY_STR
	| glist ',' YY_STR
	;

tag:	YY_STR					{ $$ = $1; }
	;

matchup:
	onehalf					{ $$ = $1; }
	| twohalves				{ $$ = $1; }
	;

action:	result				{ $$.act_val = $1.act_val;
					  $$.act_ip = $1.act_ip;
					  $$.act_port = $1.act_port; }
	| result IPSL_ELSE result	{ $$.act_val = $1.act_val;
					  $$.act_else = $3.act_val;
					  if ($1.act_val == IPSL_REDIRECT) {
						  $$.act_ip = $1.act_ip;
						  $$.act_port = $1.act_port;
					  }
					  if ($3.act_val == IPSL_REDIRECT) {
						  $$.act_eip = $3.act_eip;
						  $$.act_eport = $3.act_eport;
					  }
					}

result:	IPSL_CLOSE				{ $$.act_val = IPSL_CLOSE; }
	| IPSL_TRACK				{ $$.act_val = IPSL_TRACK; }
	| redirect				{ $$.act_val = IPSL_REDIRECT;
						  $$.act_ip = $1.act_ip;
						  $$.act_port = $1.act_port; }
	;

onehalf:
	'(' YY_STR ')'			{ $$ = makepair($2, NULL); }
	;

twohalves:
	'(' YY_STR ',' YY_STR ')'	{ $$ = makepair($2, $4); }
	;

redirect:
	IPSL_REDIRECT '(' ipaddr ')'		{ $$.act_ip = $3;
						  $$.act_port = 0; }
	| IPSL_REDIRECT '(' ipaddr ',' portnum ')'
						{ $$.act_ip = $3;
						  $$.act_port = $5; }
	;


ipaddr:	YY_NUMBER '.' YY_NUMBER '.' YY_NUMBER '.' YY_NUMBER
						{ $$ = combine($1,$3,$5,$7); }
	| YY_STR				{ $$ = gethostip($1);
						  free($1);
						}
	;

portnum:
	YY_NUMBER				{ $$ = htons($1); }
	| YY_STR				{ $$ = getportnum($1);
						  free($1);
						}
	;

%%


static	struct	wordtab	yywords[] = {
	{ "close",		IPSL_CLOSE },
	{ "content",		IPSL_CONTENT },
	{ "else",		IPSL_ELSE },
	{ "start-group",	IPSL_STARTGROUP },
	{ "redirect",		IPSL_REDIRECT },
	{ "start",		IPSL_START },
	{ "track",		IPSL_TRACK },
	{ NULL,		0 }
};


int cram(dst, src)
char *dst;
char *src;
{
	char c, *s, *t, *u;
	int i, j, k;

	c = *src;
	s = src + 1;
	t = strchr(s, c);
	*t = '\0';
	for (u = dst, i = 0; (i <= ISC_TLEN) && (s < t); ) {
		c = *s++;
		if (c == '\\') {
			if (s >= t)
				break;
			j = k = 0;
			do {
				c = *s++;
				if (j && (!ISDIGIT(c) || (c > '7') ||
				     (k >= 248))) {
					*u++ = k, i++;
					j = k = 0;
					s--;
					break;
				}
				i++;

				if (ISALPHA(c) || (c > '7')) {
					switch (c)
					{
					case 'n' :
						*u++ = '\n';
						break;
					case 'r' :
						*u++ = '\r';
						break;
					case 't' :
						*u++ = '\t';
						break;
					default :
						*u++ = c;
						break;
					}
				} else if (ISDIGIT(c)) {
					j = 1;
					k <<= 3;
					k |= (c - '0');
					i--;
				} else
						*u++ = c;
			} while ((i <= ISC_TLEN) && (s <= t) && (j > 0));
		} else
			*u++ = c, i++;
	}
	return i;
}


void printent(isc)
ipscan_t *isc;
{
	char buf[ISC_TLEN+1];
	u_char *u;
	int i, j;

	buf[ISC_TLEN] = '\0';
	bcopy(isc->ipsc_ctxt, buf, ISC_TLEN);
	printf("%s : (\"", isc->ipsc_tag);
	printbuf(isc->ipsc_ctxt, isc->ipsc_clen, 0);

	bcopy(isc->ipsc_cmsk, buf, ISC_TLEN);
	printf("\", \"%s\"), (\"", buf);

	printbuf(isc->ipsc_stxt, isc->ipsc_slen, 0);

	bcopy(isc->ipsc_smsk, buf, ISC_TLEN);
	printf("\", \"%s\") = ", buf);

	switch (isc->ipsc_action)
	{
	case ISC_A_TRACK :
		printf("track");
		break;
	case ISC_A_REDIRECT :
		printf("redirect");
		printf("(%s", inet_ntoa(isc->ipsc_ip));
		if (isc->ipsc_port)
			printf(",%d", isc->ipsc_port);
		printf(")");
		break;
	case ISC_A_CLOSE :
		printf("close");
		break;
	default :
		break;
	}

	if (isc->ipsc_else != ISC_A_NONE) {
		printf(" else ");
		switch (isc->ipsc_else)
		{
		case ISC_A_TRACK :
			printf("track");
			break;
		case ISC_A_REDIRECT :
			printf("redirect");
			printf("(%s", inet_ntoa(isc->ipsc_eip));
			if (isc->ipsc_eport)
				printf(",%d", isc->ipsc_eport);
			printf(")");
			break;
		case ISC_A_CLOSE :
			printf("close");
			break;
		default :
			break;
		}
	}
	printf("\n");

	if (opts & OPT_DEBUG) {
		for (u = (u_char *)isc, i = sizeof(*isc); i; ) {
			printf("#");
			for (j = 32; (j > 0) && (i > 0); j--, i--)
				printf("%s%02x", (j & 7) ? "" : " ", *u++);
			printf("\n");
		}
	}
	if (opts & OPT_VERBOSE) {
		printf("# hits %d active %d fref %d sref %d\n",
			isc->ipsc_hits, isc->ipsc_active, isc->ipsc_fref,
			isc->ipsc_sref);
	}
}


void addtag(tstr, cp, sp, act)
char *tstr;
char **cp, **sp;
struct action *act;
{
	ipscan_t isc, *iscp;

	bzero((char *)&isc, sizeof(isc));

	strncpy(isc.ipsc_tag, tstr, sizeof(isc.ipsc_tag));
	isc.ipsc_tag[sizeof(isc.ipsc_tag) - 1] = '\0';

	if (cp) {
		isc.ipsc_clen = cram(isc.ipsc_ctxt, cp[0]);
		if (cp[1]) {
			if (cram(isc.ipsc_cmsk, cp[1]) != isc.ipsc_clen) {
				fprintf(stderr,
					"client text/mask strings different length\n");
				return;
			}
		}
	}

	if (sp) {
		isc.ipsc_slen = cram(isc.ipsc_stxt, sp[0]);
		if (sp[1]) {
			if (cram(isc.ipsc_smsk, sp[1]) != isc.ipsc_slen) {
				fprintf(stderr,
					"server text/mask strings different length\n");
				return;
			}
		}
	}

	if (act->act_val == IPSL_CLOSE) {
		isc.ipsc_action = ISC_A_CLOSE;
	} else if (act->act_val == IPSL_TRACK) {
		isc.ipsc_action = ISC_A_TRACK;
	} else if (act->act_val == IPSL_REDIRECT) {
		isc.ipsc_action = ISC_A_REDIRECT;
		isc.ipsc_ip = act->act_ip;
		isc.ipsc_port = act->act_port;
		fprintf(stderr, "%d: redirect unsupported\n", yylineNum + 1);
	}

	if (act->act_else == IPSL_CLOSE) {
		isc.ipsc_else = ISC_A_CLOSE;
	} else if (act->act_else == IPSL_TRACK) {
		isc.ipsc_else = ISC_A_TRACK;
	} else if (act->act_else == IPSL_REDIRECT) {
		isc.ipsc_else = ISC_A_REDIRECT;
		isc.ipsc_eip = act->act_eip;
		isc.ipsc_eport = act->act_eport;
		fprintf(stderr, "%d: redirect unsupported\n", yylineNum + 1);
	}

	if (!(opts & OPT_DONOTHING)) {
		iscp = &isc;
		if (opts & OPT_REMOVE) {
			if (ioctl(fd, SIOCRMSCA, &iscp) == -1)
				perror("SIOCADSCA");
		} else {
			if (ioctl(fd, SIOCADSCA, &iscp) == -1)
				perror("SIOCADSCA");
		}
	}

	if (opts & OPT_VERBOSE)
		printent(&isc);
}


char **makepair(s1, s2)
char *s1, *s2;
{
	char **a;

	a = malloc(sizeof(char *) * 2);
	a[0] = s1;
	a[1] = s2;
	return a;
}


struct in_addr combine(a1, a2, a3, a4)
int a1, a2, a3, a4;
{
	struct in_addr in;

	a1 &= 0xff;
	in.s_addr = a1 << 24;
	a2 &= 0xff;
	in.s_addr |= (a2 << 16);
	a3 &= 0xff;
	in.s_addr |= (a3 << 8);
	a4 &= 0xff;
	in.s_addr |= a4;
	in.s_addr = htonl(in.s_addr);
	return in;
}


struct in_addr gethostip(host)
char *host;
{
	struct hostent *hp;
	struct in_addr in;

	in.s_addr = 0;

	hp = gethostbyname(host);
	if (!hp)
		return in;
	bcopy(hp->h_addr, (char *)&in, sizeof(in));
	return in;
}


int getportnum(port)
char *port;
{
	struct servent *s;

	s = getservbyname(port, "tcp");
	if (s == NULL)
		return -1;
	return s->s_port;
}


void showlist()
{
	ipscanstat_t ipsc, *ipscp = &ipsc;
	ipscan_t isc;

	if (ioctl(fd, SIOCGSCST, &ipscp) == -1)
		perror("ioctl(SIOCGSCST)");
	else if (opts & OPT_SHOWLIST) {
		while (ipsc.iscs_list != NULL) {
			if (kmemcpy((char *)&isc, (u_long)ipsc.iscs_list,
				    sizeof(isc)) == -1) {
				perror("kmemcpy");
				break;
			} else {
				printent(&isc);
				ipsc.iscs_list = isc.ipsc_next;
			}
		}
	} else {
		printf("scan entries loaded\t%d\n", ipsc.iscs_entries);
		printf("scan entries matches\t%ld\n", ipsc.iscs_acted);
		printf("negative matches\t%ld\n", ipsc.iscs_else);
	}
}


void usage(prog)
char *prog;
{
	fprintf(stderr, "Usage:\t%s [-dnrv] -f <filename>\n", prog);
	fprintf(stderr, "\t%s [-dlv]\n", prog);
	exit(1);
}


int main(argc, argv)
int argc;
char *argv[];
{
	FILE *fp = NULL;
	int c;

	(void) yysettab(yywords);

	if (argc < 2)
		usage(argv[0]);

	while ((c = getopt(argc, argv, "df:lnrsv")) != -1)
		switch (c)
		{
		case 'd' :
			opts |= OPT_DEBUG;
			yydebug++;
			break;
		case 'f' :
			if (!strcmp(optarg, "-"))
				fp = stdin;
			else {
				fp = fopen(optarg, "r");
				if (!fp) {
					perror("open");
					exit(1);
				}
			}
			yyin = fp;
			break;
		case 'l' :
			opts |= OPT_SHOWLIST;
			break;
		case 'n' :
			opts |= OPT_DONOTHING;
			break;
		case 'r' :
			opts |= OPT_REMOVE;
			break;
		case 's' :
			opts |= OPT_STAT;
			break;
		case 'v' :
			opts |= OPT_VERBOSE;
			break;
		}

	if (!(opts & OPT_DONOTHING)) {
		fd = open(IPL_SCAN, O_RDWR);
		if (fd == -1) {
			perror("open(IPL_SCAN)");
			exit(1);
		}
	}

	if (fp != NULL) {
		yylineNum = 1;

		while (!feof(fp))
			yyparse();
		fclose(fp);
		exit(0);
	}

	if (opts & (OPT_SHOWLIST|OPT_STAT)) {
		showlist();
		exit(0);
	}
	exit(1);
}
