|  | /* | 
|  | * 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$"; | 
|  | #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 */ |