/* $NetBSD: ip_scan.c,v 1.1.1.4 2006/04/04 16:08:43 martti Exp $ */ /* * 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 #if defined(__hpux) && (HPUXREV >= 1111) && !defined(_KERNEL) # include #endif #include #include #include #if !defined(_KERNEL) # include # include # define _KERNEL # ifdef __OpenBSD__ struct file; # endif # include # undef _KERNEL #else # include # if !defined(__svr4__) && !defined(__SVR4) # include # endif #endif #include #if !defined(__hpux) && !defined(__osf__) && !defined(linux) && !defined(AIX) # include #endif #ifdef __FreeBSD__ # include # include #else # include #endif #include #include #include #include #include #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.6 2006/03/26 23:06:49 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) 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) caddr_t data; ioctlcmd_t cmd; int mode; { 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 */