root/sys/netpfil/ipfilter/netinet/ip_scan.c
/*
 * Copyright (C) 2012 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>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/errno.h>
#if !defined(_KERNEL)
# include <stdlib.h>
# include <string.h>
# define _KERNEL
# include <sys/uio.h>
# undef _KERNEL
#else
# include <sys/systm.h>
# if !defined(__SVR4)
#  include <sys/mbuf.h>
# endif
#endif
#include <sys/socket.h>
# include <sys/ioccom.h>
#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 */


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


ipscan_t        *ipf_scan_list = NULL,
                *ipf_scan_tail = NULL;
ipscanstat_t    ipf_scan_stat;
# ifdef USE_MUTEXES
ipfrwlock_t     ipf_scan_rwlock;
# endif

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


int ipf_scan_add(caddr_t);
int ipf_scan_remove(caddr_t);
struct ipscan *ipf_scan_lookup(char *);
int ipf_scan_matchstr(sinfo_t *, char *, int);
int ipf_scan_matchisc(ipscan_t *, ipstate_t *, int, int, int *);
int ipf_scan_match(ipstate_t *);

static int      ipf_scan_inited = 0;


int
ipf_scan_init(void)
{
        RWLOCK_INIT(&ipf_scan_rwlock, "ip scan rwlock");
        ipf_scan_inited = 1;
        return (0);
}


void
ipf_scan_unload(ipf_main_softc_t *arg)
{
        if (ipf_scan_inited == 1) {
                RW_DESTROY(&ipf_scan_rwlock);
                ipf_scan_inited = 0;
        }
}


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

        KMALLOC(isc, ipscan_t *);
        if (!isc) {
                ipf_interror = 90001;
                return (ENOMEM);
        }

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

        WRITE_ENTER(&ipf_scan_rwlock);

        i = ipf_scan_lookup(isc->ipsc_tag);
        if (i != NULL) {
                RWLOCK_EXIT(&ipf_scan_rwlock);
                KFREE(isc);
                ipf_interror = 90002;
                return (EEXIST);
        }

        if (ipf_scan_tail) {
                ipf_scan_tail->ipsc_next = isc;
                isc->ipsc_pnext = &ipf_scan_tail->ipsc_next;
                ipf_scan_tail = isc;
        } else {
                ipf_scan_list = isc;
                ipf_scan_tail = isc;
                isc->ipsc_pnext = &ipf_scan_list;
        }
        isc->ipsc_next = NULL;

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

        ipf_scan_stat.iscs_entries++;
        RWLOCK_EXIT(&ipf_scan_rwlock);
        return (0);
}


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

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

        WRITE_ENTER(&ipf_scan_rwlock);

        i = ipf_scan_lookup(isc.ipsc_tag);
        if (i == NULL)
                err = ENOENT;
        else {
                if (i->ipsc_fref) {
                        RWLOCK_EXIT(&ipf_scan_rwlock);
                        ipf_interror = 90003;
                        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 == &ipf_scan_list)
                                ipf_scan_tail = NULL;
                        else
                                ipf_scan_tail = *(*i->ipsc_pnext)->ipsc_pnext;
                }

                ipf_scan_stat.iscs_entries--;
                KFREE(i);
        }
        RWLOCK_EXIT(&ipf_scan_rwlock);
        return (err);
}


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

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


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

        if (fr->fr_isctag != -1) {
                READ_ENTER(&ipf_scan_rwlock);
                i = ipf_scan_lookup(fr->fr_isctag + fr->fr_names);
                if (i != NULL) {
                        ATOMIC_INC32(i->ipsc_fref);
                }
                RWLOCK_EXIT(&ipf_scan_rwlock);
                if (i == NULL) {
                        ipf_interror = 90004;
                        return (ENOENT);
                }
                fr->fr_isc = i;
        }
        return (0);
}


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

        READ_ENTER(&ipf_scan_rwlock);
        fr = is->is_rule;
        if (fr != NULL) {
                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(&ipf_scan_rwlock);
        return (0);
}


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

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


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

        READ_ENTER(&ipf_scan_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(&ipf_scan_rwlock);
        return (0);
}


/*
 * 'string' compare for scanning
 */
int
ipf_scan_matchstr(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
ipf_scan_matchisc(ipscan_t *isc, ipstate_t *is, int cl, int sl, int 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 (!ipf_scan_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 (!ipf_scan_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
ipf_scan_match(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 = ipf_scan_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 = ipf_scan_list; isc; isc = isc->ipsc_next) {
                        i = ipf_scan_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;
                ipf_scan_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;
                ipf_scan_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
ipf_scan_packet(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) ipf_scan_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
ipf_scan_ioctl(caddr_t data, ioctlcmd_t cmd, int mode, int uid, void *ctx)
{
        ipscanstat_t ipscs;
        int err = 0;

        switch (cmd)
        {
        case SIOCADSCA :
                err = ipf_scan_add(data);
                break;
        case SIOCRMSCA :
                err = ipf_scan_remove(data);
                break;
        case SIOCGSCST :
                bcopy((char *)&ipf_scan_stat, (char *)&ipscs, sizeof(ipscs));
                ipscs.iscs_list = ipf_scan_list;
                err = BCOPYOUT(&ipscs, data, sizeof(ipscs));
                if (err != 0) {
                        ipf_interror = 90005;
                        err = EFAULT;
                }
                break;
        default :
                err = EINVAL;
                break;
        }

        return (err);
}
#endif  /* IPFILTER_SCAN */