/*
 * Copyright (C) 1995-2001 by Darren Reed.
 *
 * See the IPFILTER.LICENCE file for details on licencing.
 */
#if defined(KERNEL) || defined(_KERNEL)
# undef KERNEL
# undef _KERNEL
# define        KERNEL	1
# define        _KERNEL	1
#endif
#include <sys/param.h>
#if defined(__hpux) && (HPUXREV >= 1111) && !defined(_KERNEL)
# include <sys/kern_svcs.h>
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/errno.h>
#if !defined(_KERNEL)
# include <stdlib.h>
# include <string.h>
# define _KERNEL
# ifdef __OpenBSD__
struct file;
# endif
# include <sys/uio.h>
# undef _KERNEL
#else
# include <sys/systm.h>
# if !defined(__svr4__) && !defined(__SVR4)
#  include <sys/mbuf.h>
# endif
#endif
#include <sys/socket.h>
#if !defined(__hpux) && !defined(__osf__) && !defined(linux) && !defined(AIX)
# include <sys/ioccom.h>
#endif
#ifdef __FreeBSD__
# include <sys/filio.h>
# include <sys/malloc.h>
#else
# include <sys/ioctl.h>
#endif

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

#include <net/if.h>


#include "netinet/ip_compat.h"
#include "netinet/ip_fil.h"
#include "netinet/ip_state.h"
#include "netinet/ip_scan.h"
/* END OF INCLUDES */

#if !defined(lint)
static const char sccsid[] = "@(#)ip_state.c	1.8 6/5/96 (C) 1993-2000 Darren Reed";
static const char rcsid[] = "@(#)$Id: ip_scan.c,v 2.40.2.10 2007/06/02 21:22:28 darrenr Exp $";
#endif

#ifdef	IPFILTER_SCAN	/* endif at bottom of file */


ipscan_t	*ipsc_list = NULL,
		*ipsc_tail = NULL;
ipscanstat_t	ipsc_stat;
# ifdef USE_MUTEXES
ipfrwlock_t	ipsc_rwlock;
# endif

# ifndef isalpha
#  define	isalpha(x)	(((x) >= 'A' && 'Z' >= (x)) || \
				 ((x) >= 'a' && 'z' >= (x)))
# endif


int ipsc_add __P((caddr_t));
int ipsc_delete __P((caddr_t));
struct ipscan *ipsc_lookup __P((char *));
int ipsc_matchstr __P((sinfo_t *, char *, int));
int ipsc_matchisc __P((ipscan_t *, ipstate_t *, int, int, int *));
int ipsc_match __P((ipstate_t *));

static int	ipsc_inited = 0;


int ipsc_init()
{
	RWLOCK_INIT(&ipsc_rwlock, "ip scan rwlock");
	ipsc_inited = 1;
	return 0;
}


void fr_scanunload()
{
	if (ipsc_inited == 1) {
		RW_DESTROY(&ipsc_rwlock);
		ipsc_inited = 0;
	}
}


int ipsc_add(data)
caddr_t data;
{
	ipscan_t *i, *isc;
	int err;

	KMALLOC(isc, ipscan_t *);
	if (!isc)
		return ENOMEM;

	err = copyinptr(data, isc, sizeof(*isc));
	if (err) {
		KFREE(isc);
		return err;
	}

	WRITE_ENTER(&ipsc_rwlock);

	i = ipsc_lookup(isc->ipsc_tag);
	if (i) {
		RWLOCK_EXIT(&ipsc_rwlock);
		KFREE(isc);
		return EEXIST;
	}

	if (ipsc_tail) {
		ipsc_tail->ipsc_next = isc;
		isc->ipsc_pnext = &ipsc_tail->ipsc_next;
		ipsc_tail = isc;
	} else {
		ipsc_list = isc;
		ipsc_tail = isc;
		isc->ipsc_pnext = &ipsc_list;
	}
	isc->ipsc_next = NULL;

	isc->ipsc_hits = 0;
	isc->ipsc_fref = 0;
	isc->ipsc_sref = 0;
	isc->ipsc_active = 0;

	ipsc_stat.iscs_entries++;
	RWLOCK_EXIT(&ipsc_rwlock);
	return 0;
}


int ipsc_delete(data)
caddr_t data;
{
	ipscan_t isc, *i;
	int err;

	err = copyinptr(data, &isc, sizeof(isc));
	if (err)
		return err;

	WRITE_ENTER(&ipsc_rwlock);

	i = ipsc_lookup(isc.ipsc_tag);
	if (i == NULL)
		err = ENOENT;
	else {
		if (i->ipsc_fref) {
			RWLOCK_EXIT(&ipsc_rwlock);
			return EBUSY;
		}

		*i->ipsc_pnext = i->ipsc_next;
		if (i->ipsc_next)
			i->ipsc_next->ipsc_pnext = i->ipsc_pnext;
		else {
			if (i->ipsc_pnext == &ipsc_list)
				ipsc_tail = NULL;
			else
				ipsc_tail = *(*i->ipsc_pnext)->ipsc_pnext;
		}

		ipsc_stat.iscs_entries--;
		KFREE(i);
	}
	RWLOCK_EXIT(&ipsc_rwlock);
	return err;
}


struct ipscan *ipsc_lookup(tag)
char *tag;
{
	ipscan_t *i;

	for (i = ipsc_list; i; i = i->ipsc_next)
		if (!strcmp(i->ipsc_tag, tag))
			return i;
	return NULL;
}


int ipsc_attachfr(fr)
struct frentry *fr;
{
	ipscan_t *i;

	if (fr->fr_isctag[0]) {
		READ_ENTER(&ipsc_rwlock);
		i = ipsc_lookup(fr->fr_isctag);
		if (i != NULL) {
			ATOMIC_INC32(i->ipsc_fref);
		}
		RWLOCK_EXIT(&ipsc_rwlock);
		if (i == NULL)
			return ENOENT;
		fr->fr_isc = i;
	}
	return 0;
}


int ipsc_attachis(is)
struct ipstate *is;
{
	frentry_t *fr;
	ipscan_t *i;

	READ_ENTER(&ipsc_rwlock);
	fr = is->is_rule;
	if (fr) {
		i = fr->fr_isc;
		if ((i != NULL) && (i != (ipscan_t *)-1)) {
			is->is_isc = i;
			ATOMIC_INC32(i->ipsc_sref);
			if (i->ipsc_clen)
				is->is_flags |= IS_SC_CLIENT;
			else
				is->is_flags |= IS_SC_MATCHC;
			if (i->ipsc_slen)
				is->is_flags |= IS_SC_SERVER;
			else
				is->is_flags |= IS_SC_MATCHS;
		}
	}
	RWLOCK_EXIT(&ipsc_rwlock);
	return 0;
}


int ipsc_detachfr(fr)
struct frentry *fr;
{
	ipscan_t *i;

	i = fr->fr_isc;
	if (i != NULL) {
		ATOMIC_DEC32(i->ipsc_fref);
	}
	return 0;
}


int ipsc_detachis(is)
struct ipstate *is;
{
	ipscan_t *i;

	READ_ENTER(&ipsc_rwlock);
	if ((i = is->is_isc) && (i != (ipscan_t *)-1)) {
		ATOMIC_DEC32(i->ipsc_sref);
		is->is_isc = NULL;
		is->is_flags &= ~(IS_SC_CLIENT|IS_SC_SERVER);
	}
	RWLOCK_EXIT(&ipsc_rwlock);
	return 0;
}


/*
 * 'string' compare for scanning
 */
int ipsc_matchstr(sp, str, n)
sinfo_t *sp;
char *str;
int n;
{
	char *s, *t, *up;
	int i = n;

	if (i > sp->s_len)
		i = sp->s_len;
	up = str;

	for (s = sp->s_txt, t = sp->s_msk; i; i--, s++, t++, up++)
		switch ((int)*t)
		{
		case '.' :
			if (*s != *up)
				return 1;
			break;
		case '?' :
			if (!ISALPHA(*up) || ((*s & 0x5f) != (*up & 0x5f)))
				return 1;
			break;
		case '*' :
			break;
		}
	return 0;
}


/*
 * Returns 3 if both server and client match, 2 if just server,
 * 1 if just client
 */
int ipsc_matchisc(isc, is, cl, sl, maxm)
ipscan_t *isc;
ipstate_t *is;
int cl, sl, maxm[2];
{
	int i, j, k, n, ret = 0, flags;

	flags = is->is_flags;

	/*
	 * If we've already matched more than what is on offer, then
	 * assume we have a better match already and forget this one.
	 */
	if (maxm != NULL) {
		if (isc->ipsc_clen < maxm[0])
			return 0;
		if (isc->ipsc_slen < maxm[1])
			return 0;
		j = maxm[0];
		k = maxm[1];
	} else {
		j = 0;
		k = 0;
	}

	if (!isc->ipsc_clen)
		ret = 1;
	else if (((flags & (IS_SC_MATCHC|IS_SC_CLIENT)) == IS_SC_CLIENT) &&
		 cl && isc->ipsc_clen) {
		i = 0;
		n = MIN(cl, isc->ipsc_clen);
		if ((n > 0) && (!maxm || (n >= maxm[1]))) {
			if (!ipsc_matchstr(&isc->ipsc_cl, is->is_sbuf[0], n)) {
				i++;
				ret |= 1;
				if (n > j)
					j = n;
			}
		}
	}

	if (!isc->ipsc_slen)
		ret |= 2;
	else if (((flags & (IS_SC_MATCHS|IS_SC_SERVER)) == IS_SC_SERVER) &&
		 sl && isc->ipsc_slen) {
		i = 0;
		n = MIN(cl, isc->ipsc_slen);
		if ((n > 0) && (!maxm || (n >= maxm[1]))) {
			if (!ipsc_matchstr(&isc->ipsc_sl, is->is_sbuf[1], n)) {
				i++;
				ret |= 2;
				if (n > k)
					k = n;
			}
		}
	}

	if (maxm && (ret == 3)) {
		maxm[0] = j;
		maxm[1] = k;
	}
	return ret;
}


int ipsc_match(is)
ipstate_t *is;
{
	int i, j, k, n, cl, sl, maxm[2];
	ipscan_t *isc, *lm;
	tcpdata_t *t;

	for (cl = 0, n = is->is_smsk[0]; n & 1; n >>= 1)
		cl++;
	for (sl = 0, n = is->is_smsk[1]; n & 1; n >>= 1)
		sl++;

	j = 0;
	isc = is->is_isc;
	if (isc != NULL) {
		/*
		 * Known object to scan for.
		 */
		i = ipsc_matchisc(isc, is, cl, sl, NULL);
		if (i & 1) {
			is->is_flags |= IS_SC_MATCHC;
			is->is_flags &= ~IS_SC_CLIENT;
		} else if (cl >= isc->ipsc_clen)
			is->is_flags &= ~IS_SC_CLIENT;
		if (i & 2) {
			is->is_flags |= IS_SC_MATCHS;
			is->is_flags &= ~IS_SC_SERVER;
		} else if (sl >= isc->ipsc_slen)
			is->is_flags &= ~IS_SC_SERVER;
	} else {
		i = 0;
		lm = NULL;
		maxm[0] = 0;
		maxm[1] = 0;
		for (k = 0, isc = ipsc_list; isc; isc = isc->ipsc_next) {
			i = ipsc_matchisc(isc, is, cl, sl, maxm);
			if (i) {
				/*
				 * We only want to remember the best match
				 * and the number of times we get a best
				 * match.
				 */
				if ((j == 3) && (i < 3))
					continue;
				if ((i == 3) && (j != 3))
					k = 1;
				else
					k++;
				j = i;
				lm = isc;
			}
		}
		if (k == 1)
			isc = lm;
		if (isc == NULL)
			return 0;

		/*
		 * No matches or partial matches, so reset the respective
		 * search flag.
		 */
		if (!(j & 1))
			is->is_flags &= ~IS_SC_CLIENT;

		if (!(j & 2))
			is->is_flags &= ~IS_SC_SERVER;

		/*
		 * If we found the best match, then set flags appropriately.
		 */
		if ((j == 3) && (k == 1)) {
			is->is_flags &= ~(IS_SC_SERVER|IS_SC_CLIENT);
			is->is_flags |= (IS_SC_MATCHS|IS_SC_MATCHC);
		}
	}

	/*
	 * If the acknowledged side of a connection has moved past the data in
	 * which we are interested, then reset respective flag.
	 */
	t = &is->is_tcp.ts_data[0];
	if (t->td_end > is->is_s0[0] + 15)
		is->is_flags &= ~IS_SC_CLIENT;

	t = &is->is_tcp.ts_data[1];
	if (t->td_end > is->is_s0[1] + 15)
		is->is_flags &= ~IS_SC_SERVER;

	/*
	 * Matching complete ?
	 */
	j = ISC_A_NONE;
	if ((is->is_flags & IS_SC_MATCHALL) == IS_SC_MATCHALL) {
		j = isc->ipsc_action;
		ipsc_stat.iscs_acted++;
	} else if ((is->is_isc != NULL) &&
		   ((is->is_flags & IS_SC_MATCHALL) != IS_SC_MATCHALL) &&
		   !(is->is_flags & (IS_SC_CLIENT|IS_SC_SERVER))) {
		/*
		 * Matching failed...
		 */
		j = isc->ipsc_else;
		ipsc_stat.iscs_else++;
	}

	switch (j)
	{
	case  ISC_A_CLOSE :
		/*
		 * If as a result of a successful match we are to
		 * close a connection, change the "keep state" info.
		 * to block packets and generate TCP RST's.
		 */
		is->is_pass &= ~FR_RETICMP;
		is->is_pass |= FR_RETRST;
		break;
	default :
		break;
	}

	return i;
}


/*
 * check if a packet matches what we're scanning for
 */
int ipsc_packet(fin, is)
fr_info_t *fin;
ipstate_t *is;
{
	int i, j, rv, dlen, off, thoff;
	u_32_t seq, s0;
	tcphdr_t *tcp;

	rv = !IP6_EQ(&fin->fin_fi.fi_src, &is->is_src);
	tcp = fin->fin_dp;
	seq = ntohl(tcp->th_seq);

	if (!is->is_s0[rv])
		return 1;

	/*
	 * check if this packet has more data that falls within the first
	 * 16 bytes sent in either direction.
	 */
	s0 = is->is_s0[rv];
	off = seq - s0;
	if ((off > 15) || (off < 0))
		return 1;
	thoff = TCP_OFF(tcp) << 2;
	dlen = fin->fin_dlen - thoff;
	if (dlen <= 0)
		return 1;
	if (dlen > 16)
		dlen = 16;
	if (off + dlen > 16)
		dlen = 16 - off;

	j = 0xffff >> (16 - dlen);
	i = (0xffff & j) << off;
#ifdef _KERNEL
	COPYDATA(*(mb_t **)fin->fin_mp, fin->fin_plen - fin->fin_dlen + thoff,
		 dlen, (caddr_t)is->is_sbuf[rv] + off);
#endif
	is->is_smsk[rv] |= i;
	for (j = 0, i = is->is_smsk[rv]; i & 1; i >>= 1)
		j++;
	if (j == 0)
		return 1;

	(void) ipsc_match(is);
#if 0
	/*
	 * There is the potential here for plain text passwords to get
	 * buffered and stored for some time...
	 */
	if (!(is->is_flags & IS_SC_CLIENT))
		bzero(is->is_sbuf[0], sizeof(is->is_sbuf[0]));
	if (!(is->is_flags & IS_SC_SERVER))
		bzero(is->is_sbuf[1], sizeof(is->is_sbuf[1]));
#endif
	return 0;
}


int fr_scan_ioctl(data, cmd, mode, uid, ctx)
caddr_t data;
ioctlcmd_t cmd;
int mode, uid;
void *ctx;
{
	ipscanstat_t ipscs;
	int err = 0;

	switch (cmd)
	{
	case SIOCADSCA :
		err = ipsc_add(data);
		break;
	case SIOCRMSCA :
		err = ipsc_delete(data);
		break;
	case SIOCGSCST :
		bcopy((char *)&ipsc_stat, (char *)&ipscs, sizeof(ipscs));
		ipscs.iscs_list = ipsc_list;
		err = BCOPYOUT(&ipscs, data, sizeof(ipscs));
		if (err != 0)
			err = EFAULT;
		break;
	default :
		err = EINVAL;
		break;
	}

	return err;
}
#endif	/* IPFILTER_SCAN */
