/*
 *   Packet filtering code for KA9Q.
 *
 *   Copyright 1992 David F. Mischler
 *   This code may be freely distributed as long as this copyright
 *   notice is preserved.
 */

/****************************************************************************
*	$Id: ipfilter.c 1.3 94/01/04 14:09:32 ROOT_DOS Exp $
*	14 Jun 93	1.2		GT	Fix warnings.
*	07 Nov 93	1.3		GT	Conditional compilation.
*
*	ATARI Version by David Nash - dnash@chaos.demon.co.uk
*
*  conditionally include "mbuf.h"
*
****************************************************************************/

#include <limits.h>
#include "config.h"

#ifdef FILTER

#ifndef _MBUF_H
#include "mbuf.h"
#endif

#ifndef _FILTER_H
#include "filter.h"
#endif

#ifndef _IFACE_H
#include "iface.h"
#endif

#ifndef _ICMP_H
#include "icmp.h"
#endif

#ifndef _IP_H
#include "ip.h"
#endif

#ifndef _NETUSER_H
#include "netuser.h"
#endif

#include	"socket.h"

#define SDWIDTH 21	/* Width of a displayed source or destination */

/*
 *   Information for TCP header kludges.
 */
#define CODEBITS_ACK	16
#define CODEBITS_OFFSET 13
#define CODEBITS_SYN	2
#define SRC_PORT_OFFSET 0	/* TCP & UDP */
#define DST_PORT_OFFSET 2	/* TCP & UDP */


/*
 *   Function to return a non-zero value if an IP packet
 *   matches a filter entry.
 */
static
int
pkt_ip( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
	/*
	 *   If source address doesn't match then get out.
	 */
	if ( fp->src.addr == ( ip->source & fp->src.mask ) ) {
		if ( fp->src.exclude )
			return 0;
	}
	else {
		if ( fp->src.exclude == 0 )
			return 0;
	}

	/*
	 *   If destination address doesn't match then get out.
	 */
	if ( fp->dest.addr == ( ip->dest & fp->dest.mask ) ) {
		if ( fp->dest.exclude )
			return 0;
	}
	else {
		if ( fp->dest.exclude == 0 )
			return 0;
	}

	return 1;
}


/*
 *   Function to return a non-zero value if an ICMP packet
 *   matches a filter entry.
 */
static
int
pkt_icmp( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
	/*
	 *   Check protocol type.
	 */
	if ( ip->protocol != ICMP_PTCL )
		return 0;

	/*
	 *   Check source & destination addresses.
	 */
	return pkt_ip( bp, ip, fp );
}

/*
 *   Function to return a non-zero value if an ICMP REDIRECT
 *   packet matches a filter entry.
 */
static
int
pkt_icmprd( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
	/*
	 *   Check protocol type and source & destination addresses.
	 */
	if ( pkt_icmp( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check ICMP message type.
	 */
	if ( bp->data[ 0 ] == ICMP_REDIRECT )
		return 1;

	return 0;
}


/*
 *   Function to return a non-zero value if an ICMP packet
 *   other than a REDIRECT matches a filter entry.
 */
static
int
pkt_icmpxrd( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
	/*
	 *   Check protocol type and source & destination addresses.
	 */
	if ( pkt_icmp( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check ICMP message type.
	 */
	if ( bp->data[ 0 ] != ICMP_REDIRECT )
		return 1;

	return 0;
}


/*
 *   Function to return a non-zero value if a TCP packet
 *   matches a filter entry.
 */
static
int
pkt_tcp( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
unsigned short	port;

	/*
	 *   Check protocol type.
	 */
	if ( ip->protocol != TCP_PTCL )
		return 0;

	/*
	 *   Check source & destination addresses.
	 */
	if ( pkt_ip( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check source port.
	 */
	if ( fp->src.port ) {
		port = get16( &bp->data[SRC_PORT_OFFSET] );
		if ( fp->src.port > port || fp->src.hiport < port )
			return 0;
	}

	/*
	 *   Check destination port.
	 */
	if ( fp->dest.port ) {
		port = get16( &bp->data[DST_PORT_OFFSET] );
		if ( fp->dest.port > port || fp->dest.hiport < port )
			return 0;
	}
	return 1;
}


/*
 *   Function to return a non-zero value if a TCP packet
 *   with SYN set and ACK clear matches a filter entry.
 */
static
int
pkt_tcpsyn( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
unsigned char	codebits;

	/*
	 *   Check for TCP protocol type and matching addresses.
	 */
	if ( pkt_tcp( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check that SYN is set and ACK is clear.
	 */
	codebits = bp->data[ CODEBITS_OFFSET ];
	if ( ( codebits & CODEBITS_SYN ) && ( codebits & CODEBITS_ACK ) == 0 )
		return 1;

	return 0;
}


/*
 *   Function to return a non-zero value if a TCP packet
 *   with SYN clear or ACK set matches a filter entry.
 */
static
int
pkt_tcpxsyn( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
unsigned char	codebits;

	/*
	 *   Check for TCP protocol type and matching addresses.
	 */
	if ( pkt_tcp( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check that SYN is clear or ACK is set.
	 */
	codebits = bp->data[ CODEBITS_OFFSET ];
	if ( ( codebits & CODEBITS_SYN ) == 0 || ( codebits & CODEBITS_ACK ) )
		return 1;

	return 0;
}


/*
 *   Function to return a non-zero value if a UDP packet
 *   matches a filter entry.
 */
static
int
pkt_udp( struct mbuf *bp, struct ip *ip, struct filter *fp )
{
unsigned short	port;

	/*
	 *   Check protocol type.
	 */
	if ( ip->protocol != UDP_PTCL )
		return 0;

	/*
	 *   Check source & destination addresses.
	 */
	if ( pkt_ip( bp, ip, fp ) == 0 )
		return 0;

	/*
	 *   Check source port.
	 */
	if ( fp->src.port ) {
		port = get16( &bp->data[SRC_PORT_OFFSET] );
		if ( fp->src.port > port || fp->src.hiport < port )
			return 0;
	}

	/*
	 *   Check destination port.
	 */
	if ( fp->dest.port ) {
		port = get16( &bp->data[DST_PORT_OFFSET] );
		if ( fp->dest.port > port || fp->dest.hiport < port )
			return 0;
	}
	return 1;
}


/*
 *   Table of filter actions (indexes must match FILTER_ACTION_*).
 */
static char *Acts[] = {
	"?", "deny", "permit"
};

/*
 *   Table of packet type names and matching functions.
 */
static struct {
	char *name;
	int  (*function) __ARGS((struct mbuf *bp, struct ip *ip, struct filter *fp));
} Types[] = {
	{ "*",		pkt_ip },		/* Any IP packet	*/
	{ "icmp",		pkt_icmp },	/* Any ICMP packet	*/
	{ "icmprd",	pkt_icmprd },	/* ICMP redirect	*/
	{ "icmpxrd",	pkt_icmpxrd },	/* ICMP except redirect	*/
	{ "tcp",		pkt_tcp },	/* Any TCP packet	*/
	{ "tcpsyn",	pkt_tcpsyn },	/* TCP SYN packet	*/
	{ "tcpxsyn",	pkt_tcpxsyn },	/* TCP except SYN	*/
	{ "udp",		pkt_udp },	/* Any UDP packet	*/
	{ NULL,		NULL }
};

/*
 *   Function to parse a filter source or destination spec.
 *   A non-zero return value indicates a syntax problem.
 */
static
int
sdparse( char *p, struct filtersd *sdp )
{
char	*addrp;		/* Pointer to address specification	*/
char	*bitp;		/* Pointer to bit count			*/

	sdp->addr = 0L;
	sdp->mask = ~0L;
	sdp->bits = 32;
	sdp->exclude = 0;
	sdp->port = 0;
	sdp->hiport = 0;

	/*
	 *   Look for '!' in address to specify address exclusion.
	 */
	if ( *p == '!' ) {
		p += 1;
		sdp->exclude = 1;
	}
	addrp = p;

	/*
	 *   Look for '/' in spec to separate number of bits.
	 */
	if ((bitp = strchr( p, '/' )) != 0) {
		*bitp++ = '\0';
		p = bitp;
	}

	/*
	 *   Look for ':' in spec to separate port number.
	 */
	if ((p = strchr( p, ':' )) != 0) {
		*p++ = '\0';
		sdp->hiport = sdp->port = atoi( p );

		/*
		 *   '+' in port spec indicates >= port.
		 */
		if ( strchr( p, '+' ) )
			sdp->hiport = USHRT_MAX;
		/*
		 *   '-' in port spec indicates port range.
		 */
		else if ((p = strchr( p, '-' )) != 0) {
			p += 1;
			sdp->hiport = atoi( p );
		}
		if ( sdp->port > sdp->hiport ) {
			tprintf( "Bad port range\n" );
			return -1;
		}
	}

	/*
	 *   Evaluate number of bits if necessary.
	 */
	if ( bitp )
		sdp->mask <<= ( 32 - (sdp->bits = atoi( bitp )) );

	/*
	 *   Evaluate host/net address.
	 */
	if ( strcmp( "*", addrp ) == 0 ) {
		sdp->bits = 0;
		sdp->mask = 0L;
	}
	else {
		if ( ( sdp->addr = resolve( addrp ) ) == 0 ) {
			tprintf( Badhost, addrp );
			return -1;
		}
	}

	sdp->addr &= sdp->mask;

	return 0;
}

/*
 *   Function to list a source or destination address.
 */
static
void
listaddr( struct filtersd *sdp )
{
int	i = 0;

	if ( sdp->exclude ) {
		tputc( '!' );
		i = 1;
	}

	if ( sdp->addr == 0L && sdp->bits == 0 ) {
		tputc( '*' );
		i += 1;
	}
	else {
		i += tprintf( "%s", inet_ntoa( sdp->addr ) );
		if ( sdp->bits != 32 )
			i += tprintf( "/%d", sdp->bits );
	}

	if ( sdp->port ) {
		i += tprintf( ":%u", sdp->port );

		if ( sdp->hiport == USHRT_MAX ) {
			tputc( '+' );
			i += 1;
		}
		else if ( sdp->hiport > sdp->port )
			i += tprintf( "-%u", sdp->hiport );
	}
		
	for ( ; i < SDWIDTH ; i++ )
		tputc( ' ' );
}

/*
 *   Function to list a single filter entry.
 */
static
void
listfilter( struct filter *fp, char *iface, char *direct )
{
int	i;

	tprintf( "%s %-6s %-3s ", iface, Acts[fp->action], direct );
	for ( i = 0 ; Types[i].name ; i++ ) {
		if ( Types[i].function == fp->type ) {
			tprintf( "%-7s ", Types[i].name );
			break;
		}
	}

	listaddr( &fp->src );
	tputc( ' ' );
	listaddr( &fp->dest );
	tprintf( " %lu\n", fp->matches );
}

/*
 *  Function to process the IP FILTER command.
 */
int
doipfilter( int argc, char *argv[], void *p )
{
int		action;
struct filter	*fp;	/* Filter entry pointer			*/
struct filter	**fpp;	/* Pointer to filter entry pointer	*/
struct iface	*ifp;	/* Interface structure pointer		*/
int		i;
int		(*type)();
struct filtersd	src,dest;


	/*
	 *   Make sure interface is good.
	 */
	if (( ifp = if_lookup( argv[1] ) ) == NULLIF ) {
		tprintf( "Interface \"%s\" unknown\n", argv[1] );
		return 1;
	}

	/*
	 *   Check action.
	 */
	if ( strcmp(argv[2],"delete") == 0 ) {
		/*
		 *   Delete entire filter set.
		 */
		while ( ifp->infilter ) {
			fp = ifp->infilter;
			ifp->infilter = fp->next;
			free( (char *) fp );
		}
		while ( ifp->outfilter ) {
			fp = ifp->outfilter;
			ifp->outfilter = fp->next;
			free( (char *) fp );
		}
		return 0;
	}
	else if ( strcmp(argv[2],"list") == 0 ) {
		/*
		 *   List entire filter set.
		 */
		fp = ifp->infilter;
		while ( fp ) {
			listfilter( fp, ifp->name, "in" );
			fp = fp->next;
		}
		fp = ifp->outfilter;
		while ( fp ) {
			listfilter( fp, ifp->name, "out" );
			fp = fp->next;
		}
		return 0;
	}
	else if ( strcmp(argv[2],"deny") == 0 ) {
		action = FILTER_ACTION_DENY;
	}
	else if ( strcmp(argv[2],"permit") == 0 ) {
		action = FILTER_ACTION_PERMIT;
	}
	else {
		tprintf( "Unknown action \"%s\"\n", argv[2] );
		return 2;
	}

	/*
	 *   Complain if not enough arguments.
	 */
	if ( argc < 7 ) {
		tprintf("ip filter <iface> <act> <dir> <type> <src> <dest>\n");
		return -1;
	}

	/*
	 *   Check direction.
	 */
	if ( strncmp(argv[3],"in",2) == 0 ) {
		fpp = &ifp->infilter;
	}
	else if ( strncmp(argv[3],"out",3) == 0 ) {
		fpp = &ifp->outfilter;
	}
	else {
		tprintf( "Unknown direction \"%s\"\n", argv[3] );
		return 3;
	}

	/*
	 *   Check packet type.
	 */
	for ( i = 0 ; Types[i].name ; i++ ) {
		if ( strcmp(argv[4],Types[i].name) == 0 ) {
			type = Types[i].function;
			break;
		}
	}
	if ( Types[i].name == NULL ) {
		tprintf( "Unknown packet type \"%s\"\n", argv[4] );
		return 4;
	}

	/*
	 *   Parse source specification.
	 */
	if ( sdparse( argv[5], &src ) )
		return 5;

	/*
	 *   Parse destination specification.
	 */
	if ( sdparse( argv[6], &dest ) )
		return 6;

	/*
	 *   Append filter entry to list.
	 */
	while ( *fpp )
		fpp = &((*fpp)->next);

	*fpp = fp = (struct filter *) callocw( 1, sizeof( struct filter ) );
	fp->action = action;
	fp->type = type;
	fp->src = src;
	fp->dest = dest;

	return 0;
}

/*
 *  Function to apply a filter specification to a packet.
 *  A non-zero return indicates that the packet should be dropped.
 */
int
ip_filter(struct mbuf *bp, struct ip *ip, struct filter *fp)
{
	/*
	 *   Pass all IP fragments except the first.
	 */
	if ( ip->offset != 0 )
		return 0;

	/*
	 *   Walk the filter list until an entry matches the packet.
	 */
	for ( ; fp ; fp = fp->next ) {
		/*
		 *   If packet doesn't match try the next entry.
		 */
		if ( (*fp->type)( bp, ip, fp ) == 0 )
			continue;

		fp->matches += 1;

		/*
		 *   Take specified action.
		 */
		if ( fp->action == FILTER_ACTION_PERMIT )
			return 0;
		return -1;
	}
	return 1;	/* Deny all packets not explicitly permitted	*/
}

#endif 	/* ifdef FILTER */
