/* LINTLIBRARY */
/*
 *	Copyright (c) 1995, 1996, Sun Microsystems, Inc.
 *
 * This software is provided by Sun ``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 Sun Microsystems be liable for any direct, indirect,
 * incidental, special, exemplary, or consequential damages.
 *
 * This software is not a product, and is provided for evaluation purposes
 * only.
 *
 * This software may not be resold without the express permission of
 * Sun Microsystems.
 *
 * Feedback on this tool may be sent to packet-shell-owner@sunroof.eng.sun.com.
 */

#pragma ident	"@(#)snoop_pf.c	1.8	99/05/28 SMI"

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/isa_defs.h>

#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/stropts.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <setjmp.h>

#include <sys/pfmod.h>
#include <stdarg.h>
#include "snoop.h"
#include <tcl.h>

/*
 * This module generates code for the kernel packet filter.
 * The kernel packet filter is more efficient since it
 * operates without context switching or moving data into
 * the capture buffer.  On the other hand, it is limited
 * in its filtering ability i.e. can't cope with variable
 * length headers, can't compare the packet size, 1 and 4 octet
 * comparisons are awkward, code space is limited to ENMAXFILTERS
 * halfwords, etc.
 * The parser is the same for the user-level packet filter though
 * more limited in the variety of expressions it can generate
 * code for.  If the pf compiler finds an expression it can't
 * handle, it just gives up - we then fall back to compiling
 * for the user-level filter.
 */

struct packetfilt pf;
ushort_t *pfp;
jmp_buf env;

/* extern */ jmp_buf jmp_env;

int eaddr;	/* need ethernet addr */

static int opstack;	/* operand stack depth */

#define	EQ(val) (strcmp(token, val) == 0)

char *tkp;
char *token;
enum { EOL, ALPHA, NUMBER, FIELD, ADDR_IP, ADDR_ETHER, SPECIAL } tokentype;
uint_t tokenval;

enum direction { ANY, TO, FROM };
enum direction dir;

char pf_error_buf[BUFSIZ];

strioctl(int fd, int cmd, int timout, int len, char *dp)
{
	struct	strioctl	sioc;
	int	rc;

	sioc.ic_cmd = cmd;
	sioc.ic_timout = timout;
	sioc.ic_len = len;
	sioc.ic_dp = dp;
	rc = ioctl(fd, I_STR, &sioc);

	if (rc < 0)
		return (rc);
	else
		return (sioc.ic_len);
}

/* PRINTFLIKE1 */
static void
pr_err(const char *fmt, ...)
{
	va_list ap;
	char buf[BUFSIZ], *p2;
	const char *p1;
	extern int errno;

	p2 = buf;

	for (p1 = fmt; *p1; p1++) {
		if (*p1 == '%' && *(p1+1) == 'm') {
			char *errstr;

			if ((errstr = strerror(errno)) != (char *)NULL) {
				(void) strcpy(p2, errstr);
				p2 += strlen(p2);
			}
			p1++;
		} else {
			*p2++ = *p1;
		}
	}
	if (p2 > buf && *(p2-1) != '\n')
		*p2++ = '\n';
	*p2 = '\0';

	va_start(ap, fmt);
	(void) vsprintf(pf_error_buf, buf, ap);
	va_end(ap);
	longjmp(jmp_env, 1);
}

/*
 * This is the scanner.  Each call returns the next
 * token in the filter expression.  A token is either:
 * EOL:		The end of the line - no more tokens.
 * ALPHA:	A name that begins with a letter and contains
 *		letters or digits, hyphens or underscores.
 * NUMBER:	A number.  The value can be represented as
 * 		a decimal value (1234) or an octal value
 *		that begins with zero (066) or a hex value
 *		that begins with 0x or 0X (0xff).
 * FIELD:	A name followed by a left square bracket.
 * ADDR_IP:	An IP address.  Any sequence of digits
 *		separated by dots e.g. 129.144.40.13
 * ADDR_ETHER:	An ethernet address.  Any sequence of hex
 *		digits separated by colons e.g. 8:0:20:0:76:39
 * SPECIAL:	A special character e.g. ">" or "(".  The scanner
 *		correctly handles digraphs - two special characters
 *		that constitute a single token e.g. "==" or ">=".
 *
 * The current token is maintained in "token" and and its
 * type in "tokentype".  If tokentype is NUMBER then the
 * value is held in "tokenval".
 */

static const char *namechars =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";

static void
next()
{
	static int savechar;
	char *p;
	int base, colons, dots;

	if (*tkp == '\0') {
		token = tkp;
		*tkp = savechar;
	}

	while (isspace(*tkp)) tkp++;
	token = tkp;
	if (*token == '\0') {
		tokentype = EOL;
		return;
	}

	if (isalpha(*tkp)) {
		tokentype = ALPHA;
		tkp += strspn(tkp, namechars);
		if (*tkp == '[') {
			tokentype = FIELD;
			*tkp++ = '\0';
		}
	} else

	if (isdigit(*tkp)) {
		tkp = token + strspn(token, "0123456789abcdefABCDEFXx:.");
		colons = dots = 0;
		for (p = token; p < tkp; p++) {
			if (*p == ':')
				colons++;
			else if (*p == '.')
				dots++;
		}
		if (colons == 5)
			tokentype = ADDR_ETHER;
		else if (dots)
			tokentype = ADDR_IP;
		else {
			tokentype = NUMBER;
			tkp = token;
			base = 10;
			if (*tkp == '0') {
				base = 8;
				tkp++;
				if (*tkp == 'x' || *tkp == 'X')
					base = 16;
			}
			tokenval = strtol(token, &tkp, base);
		}
	} else {
		tokentype = SPECIAL;
		tkp++;
		if ((*token == '=' && *tkp == '=') ||
		    (*token == '>' && *tkp == '=') ||
		    (*token == '<' && *tkp == '=') ||
		    (*token == '!' && *tkp == '='))
				tkp++;
	}

	savechar = *tkp;
	*tkp = '\0';
}

static void
pf_emit(ushort_t x)
{
	if (pfp > &pf.Pf_Filter[ENMAXFILTERS - 1])
		longjmp(env, 1);
	*pfp++ = x;
}

void
pf_codeprint(ushort_t *code, int len)
{
	ushort_t *pc;
	ushort_t *plast = code + len;
	int op, action;

	for (pc = code; pc < plast; pc++) {
		(void) printf("\t%3d: ", pc - code);

		op = *pc & 0xfc00;	/* high 10 bits */
		action = *pc & 0x3ff;	/* low   6 bits */

		switch (action) {
		case ENF_PUSHLIT:
			(void) printf("PUSHLIT ");
			break;
		case ENF_PUSHZERO:
			(void) printf("PUSHZERO ");
			break;
#ifdef ENF_PUSHONE
		case ENF_PUSHONE:
			(void) printf("PUSHONE ");
			break;
#endif
#ifdef ENF_PUSHFFFF
		case ENF_PUSHFFFF:
			(void) printf("PUSHFFFF ");
			break;
#endif
#ifdef ENF_PUSHFF00
		case ENF_PUSHFF00:
			(void) printf("PUSHFF00 ");
			break;
#endif
#ifdef ENF_PUSH00FF
		case ENF_PUSH00FF:
			(void) printf("PUSH00FF ");
			break;
#endif
		}

		if (action >= ENF_PUSHWORD)
			(void) printf("PUSHWORD %d ", action - ENF_PUSHWORD);

		switch (op) {
		case ENF_EQ:
			(void) printf("EQ ");
			break;
		case ENF_LT:
			(void) printf("LT ");
			break;
		case ENF_LE:
			(void) printf("LE ");
			break;
		case ENF_GT:
			(void) printf("GT ");
			break;
		case ENF_GE:
			(void) printf("GE ");
			break;
		case ENF_AND:
			(void) printf("AND ");
			break;
		case ENF_OR:
			(void) printf("OR ");
			break;
		case ENF_XOR:
			(void) printf("XOR ");
			break;
		case ENF_COR:
			(void) printf("COR ");
			break;
		case ENF_CAND:
			(void) printf("CAND ");
			break;
		case ENF_CNOR:
			(void) printf("CNOR ");
			break;
		case ENF_CNAND:
			(void) printf("CNAND ");
			break;
		case ENF_NEQ:
			(void) printf("NEQ ");
			break;
		}

		if (action == ENF_PUSHLIT) {
			pc++;
			(void) printf("\n\t%3d:   %d (0x%04x)",
			    pc - code, *pc, *pc);
		}

		(void) printf("\n");
	}
}

/*
 * Emit packet filter code to check a
 * field in the packet for a particular value.
 * Need different code for each field size.
 * Since the pf can only compare 16 bit quantities
 * we have to use masking to compare byte values.
 * Long word (32 bit) quantities have to be done
 * as two 16 bit comparisons.
 */
static void
pf_compare_value(uint_t offset, uint_t len, uint_t val)
{
	switch (len) {
	case 1:
		pf_emit(ENF_PUSHWORD + offset / 2);
#if defined(_BIG_ENDIAN)
		if (offset % 2)
#else
		if (!(offset % 2))
#endif
		{
#ifdef ENF_PUSH00FF
			pf_emit(ENF_PUSH00FF | ENF_AND);
#else
			pf_emit(ENF_PUSHLIT | ENF_AND);
			pf_emit(0x00FF);
#endif
			pf_emit(ENF_PUSHLIT | ENF_EQ);
			pf_emit(val);
		} else {
#ifdef ENF_PUSHFF00
			pf_emit(ENF_PUSHFF00 | ENF_AND);
#else
			pf_emit(ENF_PUSHLIT | ENF_AND);
			pf_emit(0xFF00);
#endif
			pf_emit(ENF_PUSHLIT | ENF_EQ);
			pf_emit(val << 8);
		}
		break;

	case 2:
		pf_emit(ENF_PUSHWORD + offset / 2);
		pf_emit(ENF_PUSHLIT | ENF_EQ);
		pf_emit(htons((ushort_t)val));
		break;

	case 4:
		pf_emit(ENF_PUSHWORD + offset / 2);
		pf_emit(ENF_PUSHLIT | ENF_EQ);
#if defined(_BIG_ENDIAN)
		pf_emit(val >> 16);
#elif defined(_LITTLE_ENDIAN)
		pf_emit(val & 0xffff);
#else
#error One of _BIG_ENDIAN and _LITTLE_ENDIAN must be defined
#endif
		pf_emit(ENF_PUSHWORD + (offset / 2) + 1);
		pf_emit(ENF_PUSHLIT | ENF_EQ);
#if defined(_BIG_ENDIAN)
		pf_emit(val & 0xffff);
#else
		pf_emit(val >> 16);
#endif
		pf_emit(ENF_AND);
		break;
	}
}

/*
 * Same as above except mask the field value
 * before doing the comparison.
 */
void
pf_compare_value_mask(uint_t offset, uint_t len, uint_t val, uint_t mask)
{
	switch (len) {
	case 1:
		pf_emit(ENF_PUSHWORD + offset / 2);
#if defined(_BIG_ENDIAN)
		if (offset % 2)
#else
		if (!offset % 2)
#endif
		{
			pf_emit(ENF_PUSHLIT | ENF_AND);
			pf_emit(mask & 0x00ff);
			pf_emit(ENF_PUSHLIT | ENF_EQ);
			pf_emit(val);
		} else {
			pf_emit(ENF_PUSHLIT | ENF_AND);
			pf_emit((mask << 8) & 0xff00);
			pf_emit(ENF_PUSHLIT | ENF_EQ);
			pf_emit(val << 8);
		}
		break;

	case 2:
		pf_emit(ENF_PUSHWORD + offset / 2);
		pf_emit(ENF_PUSHLIT | ENF_AND);
		pf_emit(htons((ushort_t)mask));
		pf_emit(ENF_PUSHLIT | ENF_EQ);
		pf_emit(htons((ushort_t)val));
		break;

	case 4:
		pf_emit(ENF_PUSHWORD + offset / 2);
		pf_emit(ENF_PUSHLIT | ENF_AND);
		pf_emit(htons((ushort_t)((mask >> 16) & 0xffff)));
		pf_emit(ENF_PUSHLIT | ENF_EQ);
		pf_emit(htons((ushort_t)((val >> 16) & 0xffff)));

		pf_emit(ENF_PUSHWORD + (offset / 2) + 1);
		pf_emit(ENF_PUSHLIT | ENF_AND);
		pf_emit(htons((ushort_t)(mask & 0xffff)));
		pf_emit(ENF_PUSHLIT | ENF_EQ);
		pf_emit(htons((ushort_t)(val & 0xffff)));

		pf_emit(ENF_AND);
		break;
	}
}

/*
 * Generate pf code to match an IP address.
 * This code checks the source address in
 * ARP packets too, though the packet type
 * is never checked.  At worst, we'll let
 * through too many packets.
 */
static void
pf_ipaddr_match(enum direction which, char *hostname)
{
	uint_t addr;
	struct hostent *hp;

	if (isdigit(*hostname)) {
		addr = inet_addr(hostname);
	} else {
		hp = gethostbyname(hostname);
		if (hp == NULL)
			pr_err("host %s not known", hostname);
		addr = *(uint_t *)hp->h_addr;
	}

	switch (which) {
	case TO:
		pf_compare_value(30, 4, addr);	/* dst IP addr */
		break;
	case FROM:
		pf_compare_value(26, 4, addr);	/* src IP addr */
		pf_compare_value(28, 4, addr);	/* src ARP addr */
		pf_emit(ENF_OR);
		break;
	case ANY:
		pf_compare_value(26, 4, addr);	/* src IP addr */
		pf_compare_value(30, 4, addr);	/* dst IP addr */
		pf_emit(ENF_OR);
		pf_compare_value(28, 4, addr);	/* src ARP addr */
		pf_emit(ENF_OR);
		break;
	}
}

/*
 * Compare ethernet addresses.
 * This routine cheats and does a simple
 * longword comparison i.e. the first
 * two octets of the address are ignored.
 */
static void
pf_etheraddr_match(enum direction which, char *hostname)
{
	uint_t addr;
	struct ether_addr e, *ep;

	if (isdigit(*hostname)) {
		ep = ether_aton(hostname);
		if (ep == NULL)
			pr_err("bad ether addr %s", hostname);
	} else {
		if (ether_hostton(hostname, &e))
			pr_err("cannot obtain ether addr for %s",
				hostname);
		ep = &e;
	}
	(void) memcpy(&addr, (ushort_t *)ep + 1, 4);

	switch (which) {
	case TO:
		pf_compare_value(2, 4, addr);
		break;
	case FROM:
		pf_compare_value(8, 4, addr);
		break;
	case ANY:
		pf_compare_value(2, 4, addr);
		pf_compare_value(8, 4, addr);
		pf_emit(ENF_OR);
		break;
	}
}

/*
 * Emit code to compare the network part of
 * an IP address.
 */
static void
pf_netaddr_match(enum direction which, char *netname)
{
	uint_t addr;
	uint_t mask = 0xff000000;
	struct netent *np;

	if (isdigit(*netname)) {
		addr = inet_network(netname);
	} else {
		np = getnetbyname(netname);
		if (np == NULL)
			pr_err("net %s not known", netname);
		addr = np->n_net;
	}

	/*
	 * Left justify the address and figure
	 * out a mask based on the supplied address.
	 * Set the mask according to the number of zero
	 * low-order bytes.
	 * Note: this works only for whole octet masks.
	 */
	if (addr) {
		while ((addr & ~mask) != 0) {
			mask |= (mask >> 8);
		}
	}

	switch (which) {
	case TO:
		pf_compare_value_mask(30, 4, addr, mask);
		break;
	case FROM:
		pf_compare_value_mask(26, 4, addr, mask);
		break;
	case ANY:
		pf_compare_value_mask(26, 4, addr, mask);
		pf_compare_value_mask(30, 4, addr, mask);
		pf_emit(ENF_OR);
		break;
	}
}

void pf_expression();

static void
pf_primary()
{
	for (;;) {
		if (tokentype == FIELD)
			break;

		if (EQ("ip")) {
			pf_compare_value(12, 2, ETHERTYPE_IP);
			opstack++;
			next();
			break;
		}

		if (EQ("arp")) {
			pf_compare_value(12, 2, ETHERTYPE_ARP);
			opstack++;
			next();
			break;
		}

		if (EQ("rarp")) {
			pf_compare_value(12, 2, ETHERTYPE_REVARP);
			opstack++;
			next();
			break;
		}

		if (EQ("tcp")) {
			pf_compare_value(23, 1, IPPROTO_TCP);
			pf_compare_value(12, 2, ETHERTYPE_IP);
			pf_emit(ENF_AND);
			opstack++;
			next();
			break;
		}

		if (EQ("udp")) {
			pf_compare_value(23, 1, IPPROTO_UDP);
			pf_compare_value(12, 2, ETHERTYPE_IP);
			pf_emit(ENF_AND);
			opstack++;
			next();
			break;
		}

		if (EQ("icmp")) {
			pf_compare_value(23, 1, IPPROTO_ICMP);
			pf_compare_value(12, 2, ETHERTYPE_IP);
			pf_emit(ENF_AND);
			opstack++;
			next();
			break;
		}

		if (EQ("(")) {
			next();
			pf_expression();
			if (EQ(")"))
				next();
			break;
		}

		if (EQ("to") || EQ("dst")) {
			dir = TO;
			next();
			continue;
		}

		if (EQ("from") || EQ("src")) {
			dir = FROM;
			next();
			continue;
		}

		if (EQ("ether")) {
			eaddr = 1;
			next();
			continue;
		}

		if (EQ("proto")) { /* ignore */
			next();
			continue;
		}

		if (EQ("broadcast")) {
			pf_compare_value(0, 4, 0xffffffff);
			opstack++;
			next();
			break;
		}

		if (EQ("multicast")) {
			pf_compare_value_mask(0, 1, 0x01, 0x01);
			opstack++;
			next();
			break;
		}

		if (EQ("ethertype")) {
			next();
			if (tokentype != NUMBER)
				pr_err("ether type expected");
			pf_compare_value(12, 2, tokenval);
			opstack++;
			next();
			break;
		}

		if (EQ("net") || EQ("dstnet") || EQ("srcnet")) {
			if (EQ("dstnet"))
				dir = TO;
			else if (EQ("srcnet"))
				dir = FROM;
			next();
			pf_netaddr_match(dir, token);
			dir = ANY;
			opstack++;
			next();
			break;
		}

		/*
		 * Give up on anything that's obviously
		 * not a primary.
		 */
		if (EQ("and") || EQ("or") ||
		    EQ("not") || EQ("decnet") || EQ("apple") ||
		    EQ("length") || EQ("less") || EQ("greater") ||
		    EQ("port") || EQ("srcport") || EQ("dstport") ||
		    EQ("rpc") || EQ("gateway") || EQ("nofrag")) {
			break;
		}

		if (EQ("host") || EQ("between") ||
		    tokentype == ALPHA || /* assume its a hostname */
		    tokentype == ADDR_IP ||
		    tokentype == ADDR_ETHER) {
			if (EQ("host") || EQ("between"))
				next();
			if (eaddr || tokentype == ADDR_ETHER)
				pf_etheraddr_match(dir, token);
			else
				pf_ipaddr_match(dir, token);
			dir = ANY;
			eaddr = 0;
			opstack++;
			next();
			break;
		}

		break;	/* unknown token */
	}
}

static void
pf_alternation()
{
	int s = opstack;

	pf_primary();
	for (;;) {
		if (EQ("and"))
			next();
		pf_primary();
		if (opstack != s + 2)
			break;
		pf_emit(ENF_AND);
		opstack--;
	}
}

void
pf_expression()
{
	pf_alternation();
	while (EQ("or") || EQ(",")) {
		next();
		pf_alternation();
		pf_emit(ENF_OR);
		opstack--;
	}
}

/*
 * Attempt to compile the expression
 * in the string "e".  If we can generate
 * pf code for it then return 1 - otherwise
 * return 0 and leave it up to the user-level
 * filter.
 */
pf_compile(char *e, int print)
{
	e = strdup(e);
	if (e == NULL)
		pr_err("no mem");
	tkp = e;
	dir = ANY;

	pfp = &pf.Pf_Filter[0];
	if (setjmp(env)) {
		return (0);
	}
	next();
	pf_expression();
	if (tokentype != EOL) {
		return (0);
	}
	pf.Pf_FilterLen = pfp - &pf.Pf_Filter[0];
	pf.Pf_Priority = 5;	/* unimportant, so long as > 2 */
	if (print)
		pf_codeprint(&pf.Pf_Filter[0], pf.Pf_FilterLen);
	return (1);
}

void
pf_setup_pfmod(int netfd)
{
	/*
	 * push and configure the packet filtering module
	 */
	if (ioctl(netfd, I_PUSH, "pfmod") < 0) {
		(void) close(netfd);
		pr_err("ioctl: I_PUSH pfmod: %m");
	}

	if (strioctl(netfd, PFIOCSETF, -1, sizeof (pf), (char *)&pf) < 0) {
		(void) close(netfd);
		pr_err("PFIOCSETF: %m");
	}
}

int
pf_install(char *e, int fd)
{

	if (setjmp(jmp_env)) {
		(void) printf("Bad filter expression: %s\n", e);
		return (TCL_ERROR);
	}
	if (pf_compile(e, 0)) {
		pf_setup_pfmod(fd);
		return (TCL_OK);
	} else {
		return (TCL_ERROR);
	}
}
