| /* |
| * 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 || (i != (ipscan_t *)-1)) { |
| is->is_isc = i; |
| if (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; |
| } else |
| is->is_flags |= (IS_SC_CLIENT|IS_SC_SERVER); |
| } |
| } |
| 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; |
| BCOPYOUT(&ipscs, data, sizeof(ipscs)); |
| break; |
| default : |
| err = EINVAL; |
| break; |
| } |
| |
| return err; |
| } |
| #endif /* IPFILTER_SCAN */ |