include/linux/netfilter_ipv4/ipt_osf.h | 151 +++++ net/ipv4/netfilter/Kconfig | 22 net/ipv4/netfilter/Makefile | 1 net/ipv4/netfilter/ipt_osf.c | 862 +++++++++++++++++++++++++++++++++ 4 files changed, 1036 insertions(+) diff -Nur --exclude '*.orig' linux.org/include/linux/netfilter_ipv4/ipt_osf.h linux/include/linux/netfilter_ipv4/ipt_osf.h --- linux.org/include/linux/netfilter_ipv4/ipt_osf.h 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/linux/netfilter_ipv4/ipt_osf.h 2006-05-04 10:19:37.000000000 +0200 @@ -0,0 +1,151 @@ +/* + * ipt_osf.h + * + * Copyright (c) 2003 Evgeniy Polyakov + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _IPT_OSF_H +#define _IPT_OSF_H + +#define MAXGENRELEN 32 +#define MAXDETLEN 64 + +#define IPT_OSF_GENRE 1 +#define IPT_OSF_SMART 2 +#define IPT_OSF_LOG 4 +#define IPT_OSF_NETLINK 8 +#define IPT_OSF_CONNECTOR 16 + +#define IPT_OSF_LOGLEVEL_ALL 0 +#define IPT_OSF_LOGLEVEL_FIRST 1 + +#ifndef __KERNEL__ +#include +#include + +struct list_head +{ + struct list_head *prev, *next; +}; +#endif + +struct ipt_osf_info +{ + char genre[MAXGENRELEN]; + int len; + unsigned long flags; + int loglevel; + int invert; /* UNSUPPORTED */ +}; + +struct osf_wc +{ + char wc; + unsigned long val; +}; + +/* This struct represents IANA options + * http://www.iana.org/assignments/tcp-parameters + */ +struct osf_opt +{ + unsigned char kind; + unsigned char length; + struct osf_wc wc; +}; + +struct osf_finger +{ + struct list_head flist; + struct osf_wc wss; + unsigned char ttl; + unsigned char df; + unsigned long ss; + unsigned char genre[MAXGENRELEN]; + unsigned char version[MAXGENRELEN], subtype[MAXGENRELEN]; + + /* Not needed, but for consistency with original table from Michal Zalewski */ + unsigned char details[MAXDETLEN]; + + int opt_num; + struct osf_opt opt[MAX_IPOPTLEN]; /* In case it is all NOP or EOL */ + +}; + +struct ipt_osf_nlmsg +{ + struct osf_finger f; + struct iphdr ip; + struct tcphdr tcp; +}; + +#ifdef __KERNEL__ + +#include +#include + + +/* Defines for IANA option kinds */ + +#define OSFOPT_EOL 0 /* End of options */ +#define OSFOPT_NOP 1 /* NOP */ +#define OSFOPT_MSS 2 /* Maximum segment size */ +#define OSFOPT_WSO 3 /* Window scale option */ +#define OSFOPT_SACKP 4 /* SACK permitted */ +#define OSFOPT_SACK 5 /* SACK */ +#define OSFOPT_ECHO 6 +#define OSFOPT_ECHOREPLY 7 +#define OSFOPT_TS 8 /* Timestamp option */ +#define OSFOPT_POCP 9 /* Partial Order Connection Permitted */ +#define OSFOPT_POSP 10 /* Partial Order Service Profile */ +/* Others are not used in current OSF */ + +static struct osf_opt IANA_opts[] = +{ + {0, 1,}, + {1, 1,}, + {2, 4,}, + {3, 3,}, + {4, 2,}, + {5, 1 ,}, /* SACK length is not defined */ + {6, 6,}, + {7, 6,}, + {8, 10,}, + {9, 2,}, + {10, 3,}, + {11, 1,}, /* CC: Suppose 1 */ + {12, 1,}, /* the same */ + {13, 1,}, /* and here too */ + {14, 3,}, + {15, 1,}, /* TCP Alternate Checksum Data. Length is not defined */ + {16, 1,}, + {17, 1,}, + {18, 3,}, + {19, 18,}, + {20, 1,}, + {21, 1,}, + {22, 1,}, + {23, 1,}, + {24, 1,}, + {25, 1,}, + {26, 1,}, +}; + +#endif /* __KERNEL__ */ + +#endif /* _IPT_OSF_H */ diff -Nur --exclude '*.orig' linux.org/net/ipv4/netfilter/Kconfig linux/net/ipv4/netfilter/Kconfig --- linux.org/net/ipv4/netfilter/Kconfig 2006-05-02 23:38:44.000000000 +0200 +++ linux/net/ipv4/netfilter/Kconfig 2006-05-04 10:19:37.000000000 +0200 @@ -606,5 +606,27 @@ Allows altering the ARP packet payload: source and destination hardware and network addresses. +config IP_NF_MATCH_OSF + tristate 'OSF match support' + depends on IP_NF_IPTABLES + help + + The idea of passive OS fingerprint matching exists for quite a long time, + but was created as extension fo OpenBSD pf only some weeks ago. + Original idea was lurked in some OpenBSD mailing list (thanks + grange@open...) and than adopted for Linux netfilter in form of this code. + + Original table was created by Michal Zalewski for + his excellent p0f and than changed a bit for more convenience. + + This module compares some data(WS, MSS, options and it's order, ttl, + df and others) from first SYN packet (actually from packets with SYN + bit set) with hardcoded in fingers[] table ones. + + If you say Y here, try iptables -m osf --help for more information. + + If you want to compile it as a module, say M here and read + Documentation/modules.txt. If unsure, say `N'. + endmenu diff -Nur --exclude '*.orig' linux.org/net/ipv4/netfilter/Makefile linux/net/ipv4/netfilter/Makefile --- linux.org/net/ipv4/netfilter/Makefile 2006-05-02 23:38:44.000000000 +0200 +++ linux/net/ipv4/netfilter/Makefile 2006-05-04 10:19:37.000000000 +0200 @@ -0,0 +0,1 @@ +obj-$(CONFIG_IP_NF_MATCH_OSF) += ipt_osf.o diff -Nur --exclude '*.orig' linux.org/net/ipv4/netfilter/ipt_osf.c linux/net/ipv4/netfilter/ipt_osf.c --- linux.org/net/ipv4/netfilter/ipt_osf.c 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/ipv4/netfilter/ipt_osf.c 2006-05-04 10:19:37.000000000 +0200 @@ -0,0 +1,862 @@ +/* + * ipt_osf.c + * + * Copyright (c) 2003-2005 Evgeniy Polyakov + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * OS fingerprint matching module. + * It simply compares various parameters from SYN packet with + * some hardcoded ones. + * + * Original table was created by Michal Zalewski + * for his p0f. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define OSF_DEBUG + +#ifdef OSF_DEBUG +#define log(x...) printk(KERN_INFO "ipt_osf: " x) +#define loga(x...) printk(x) +#else +#define log(x...) do {} while(0) +#define loga(x...) do {} while(0) +#endif + +#define FMATCH_WRONG 0 +#define FMATCH_OK 1 +#define FMATCH_OPT_WRONG 2 + +#define OPTDEL ',' +#define OSFPDEL ':' +#define MAXOPTSTRLEN 128 +#define OSFFLUSH "FLUSH" + +static rwlock_t osf_lock = RW_LOCK_UNLOCKED; +static spinlock_t ipt_osf_netlink_lock = SPIN_LOCK_UNLOCKED; +static struct list_head finger_list; +static int match(const struct sk_buff *, const struct net_device *, const struct net_device *, + const void *, int, unsigned int, + int *); +static int checkentry(const char *, const struct ipt_ip *, void *, + unsigned int, unsigned int); + +static unsigned long seq, ipt_osf_groups = 1; +static struct sock *nts; + +static struct ipt_match osf_match = { + .name = "osf", + .match = &match, + .checkentry = &checkentry, + .me = THIS_MODULE +}; + + +#ifdef CONFIG_CONNECTOR +#include + +/* + * They should live in connector.h. + */ +#define CN_IDX_OSF 0x0001 +#define CN_VAL_OSF 0x0000 + +static char osf_finger_buf[sizeof(struct ipt_osf_nlmsg) + sizeof(struct cn_msg)]; +static struct cb_id osf_id = {CN_IDX_OSF, CN_VAL_OSF}; +static u32 osf_seq; + +static void ipt_osf_send_connector(struct osf_finger *f, const struct sk_buff *sk) +{ + struct cn_msg *m; + struct ipt_osf_nlmsg *data; + + m = (struct cn_msg *)osf_finger_buf; + data = (struct ipt_osf_nlmsg *)(m+1); + + memcpy(&m->id, &osf_id, sizeof(m->id)); + m->seq = osf_seq++; + m->ack = 0; + m->len = sizeof(*data); + + memcpy(&data->f, f, sizeof(struct osf_finger)); + memcpy(&data->ip, sk->nh.iph, sizeof(struct iphdr)); + memcpy(&data->tcp, (struct tcphdr *)((u_int32_t *)sk->nh.iph + sk->nh.iph->ihl), sizeof(struct tcphdr)); + + cn_netlink_send(m, m->id.idx, GFP_ATOMIC); +} +#else +static void ipt_osf_send_connector(struct osf_finger *f, const struct sk_buff *sk) +{ +} +#endif + +static void ipt_osf_nlsend(struct osf_finger *f, const struct sk_buff *sk) +{ + unsigned int size; + struct sk_buff *skb; + struct ipt_osf_nlmsg *data; + struct nlmsghdr *nlh; + + if (!nts) + return; + + size = NLMSG_SPACE(sizeof(struct ipt_osf_nlmsg)); + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + log("skb_alloc() failed.\n"); + return; + } + + nlh = NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, size - sizeof(*nlh)); + + data = (struct ipt_osf_nlmsg *)NLMSG_DATA(nlh); + + memcpy(&data->f, f, sizeof(struct osf_finger)); + memcpy(&data->ip, sk->nh.iph, sizeof(struct iphdr)); + memcpy(&data->tcp, (struct tcphdr *)((u_int32_t *)sk->nh.iph + sk->nh.iph->ihl), sizeof(struct tcphdr)); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + NETLINK_CB(skb).dst_groups = ipt_osf_groups; +#else + NETLINK_CB(skb).dst_group = ipt_osf_groups; +#endif + netlink_broadcast(nts, skb, 0, ipt_osf_groups, GFP_ATOMIC); + +nlmsg_failure: + return; +} + +static inline int smart_dec(const struct sk_buff *skb, unsigned long flags, unsigned char f_ttl) +{ + struct iphdr *ip = skb->nh.iph; + + if (flags & IPT_OSF_SMART) { + struct in_device *in_dev = in_dev_get(skb->dev); + + for_ifa(in_dev) { + if (inet_ifa_match(ip->saddr, ifa)) { + in_dev_put(in_dev); + return (ip->ttl == f_ttl); + } + } + endfor_ifa(in_dev); + + in_dev_put(in_dev); + return (ip->ttl <= f_ttl); + } + else + return (ip->ttl == f_ttl); +} + +static int +match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, + const void *matchinfo, int offset, unsigned int protoff, + int *hotdrop) +{ + struct ipt_osf_info *info = (struct ipt_osf_info *)matchinfo; + struct iphdr _iph, *ip; + struct tcphdr _tcph, *tcp; + int fmatch = FMATCH_WRONG, fcount = 0; + unsigned long totlen, optsize = 0, window; + unsigned char df, *optp = NULL, *_optp = NULL; + unsigned char opts[MAX_IPOPTLEN]; + char check_WSS = 0; + struct osf_finger *f; + int off; + + if (!info) + return 0; + + off = 0; + + ip = skb_header_pointer(skb, off, sizeof(_iph), &_iph); + if (!ip) + return 0; + + tcp = skb_header_pointer(skb, off + ip->ihl * 4, sizeof(_tcph), &_tcph); + if (!tcp) + return 0; + + if (!tcp->syn) + return 0; + + totlen = ntohs(ip->tot_len); + df = ((ntohs(ip->frag_off) & IP_DF)?1:0); + window = ntohs(tcp->window); + + if (tcp->doff*4 > sizeof(struct tcphdr)) { + optsize = tcp->doff*4 - sizeof(struct tcphdr); + + if (optsize > sizeof(opts)) { + log("%s: BUG: too big options size: optsize=%lu, max=%zu.\n", + __func__, optsize, sizeof(opts)); + optsize = sizeof(opts); + } + + _optp = optp = skb_header_pointer(skb, off + ip->ihl*4 + sizeof(_tcph), optsize, opts); + } + + /* Actually we can create hash/table of all genres and search + * only in appropriate part, but here is initial variant, + * so will use slow path. + */ + read_lock(&osf_lock); + list_for_each_entry(f, &finger_list, flist) { + + if (!(info->flags & IPT_OSF_LOG) && strcmp(info->genre, f->genre)) + continue; + + optp = _optp; + fmatch = FMATCH_WRONG; + + if (totlen == f->ss && df == f->df && + smart_dec(skb, info->flags, f->ttl)) { + unsigned long foptsize; + int optnum; + unsigned short mss = 0; + + check_WSS = 0; + + switch (f->wss.wc) { + case 0: check_WSS = 0; break; + case 'S': check_WSS = 1; break; + case 'T': check_WSS = 2; break; + case '%': check_WSS = 3; break; + default: log("Wrong fingerprint wss.wc=%d, %s - %s\n", + f->wss.wc, f->genre, f->details); + check_WSS = 4; + break; + } + if (check_WSS == 4) + continue; + + /* Check options */ + + foptsize = 0; + for (optnum=0; optnumopt_num; ++optnum) + foptsize += f->opt[optnum].length; + + + if (foptsize > MAX_IPOPTLEN || optsize > MAX_IPOPTLEN || optsize != foptsize) + continue; + + if (!optp) { + fmatch = FMATCH_OK; + loga("\tYEP : matching without options.\n"); + if ((info->flags & IPT_OSF_LOG) && + info->loglevel == IPT_OSF_LOGLEVEL_FIRST) + break; + else + continue; + } + + for (optnum=0; optnumopt_num; ++optnum) { + if (f->opt[optnum].kind == (*optp)) { + unsigned char len = f->opt[optnum].length; + unsigned char *optend = optp + len; + int loop_cont = 0; + + fmatch = FMATCH_OK; + + + switch (*optp) { + case OSFOPT_MSS: + mss = ntohs(*(unsigned short *)(optp+2)); + break; + case OSFOPT_TS: + loop_cont = 1; + break; + } + + if (loop_cont) { + optp = optend; + continue; + } + + if (len != 1) { + /* Skip kind and length fields*/ + optp += 2; + + if (f->opt[optnum].wc.val != 0) { + unsigned long tmp = 0; + + /* Hmmm... It looks a bit ugly. :) */ + memcpy(&tmp, optp, + (len > sizeof(unsigned long)? + sizeof(unsigned long):len)); + /* 2 + 2: optlen(2 bytes) + + * kind(1 byte) + length(1 byte) */ + if (len == 4) + tmp = ntohs(tmp); + else + tmp = ntohl(tmp); + + if (f->opt[optnum].wc.wc == '%') { + if ((tmp % f->opt[optnum].wc.val) != 0) + fmatch = FMATCH_OPT_WRONG; + } + else if (tmp != f->opt[optnum].wc.val) + fmatch = FMATCH_OPT_WRONG; + } + } + + optp = optend; + } else + fmatch = FMATCH_OPT_WRONG; + + if (fmatch != FMATCH_OK) + break; + } + + if (fmatch != FMATCH_OPT_WRONG) { + fmatch = FMATCH_WRONG; + + switch (check_WSS) { + case 0: + if (f->wss.val == 0 || window == f->wss.val) + fmatch = FMATCH_OK; + break; + case 1: /* MSS */ +/* Lurked in OpenBSD */ +#define SMART_MSS 1460 + if (window == f->wss.val*mss || + window == f->wss.val*SMART_MSS) + fmatch = FMATCH_OK; + break; + case 2: /* MTU */ + if (window == f->wss.val*(mss+40) || + window == f->wss.val*(SMART_MSS+40)) + fmatch = FMATCH_OK; + break; + case 3: /* MOD */ + if ((window % f->wss.val) == 0) + fmatch = FMATCH_OK; + break; + } + } + + + if (fmatch == FMATCH_OK) { + fcount++; + log("%s [%s:%s:%s] : %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u hops=%d\n", + f->genre, f->version, + f->subtype, f->details, + NIPQUAD(ip->saddr), ntohs(tcp->source), + NIPQUAD(ip->daddr), ntohs(tcp->dest), + f->ttl - ip->ttl); + if (info->flags & IPT_OSF_NETLINK) { + spin_lock_bh(&ipt_osf_netlink_lock); + ipt_osf_nlsend(f, skb); + spin_unlock_bh(&ipt_osf_netlink_lock); + } + if (info->flags & IPT_OSF_CONNECTOR) { + spin_lock_bh(&ipt_osf_netlink_lock); + ipt_osf_send_connector(f, skb); + spin_unlock_bh(&ipt_osf_netlink_lock); + } + if ((info->flags & IPT_OSF_LOG) && + info->loglevel == IPT_OSF_LOGLEVEL_FIRST) + break; + } + } + } + if (!fcount && (info->flags & (IPT_OSF_LOG | IPT_OSF_NETLINK | IPT_OSF_CONNECTOR))) { + unsigned char opt[4 * 15 - sizeof(struct tcphdr)]; + unsigned int i, optsize; + struct osf_finger fg; + + memset(&fg, 0, sizeof(fg)); + + if ((info->flags & IPT_OSF_LOG)) + log("Unknown: %lu:%d:%d:%lu:", window, ip->ttl, df, totlen); + if (optp) { + optsize = tcp->doff * 4 - sizeof(struct tcphdr); + if (skb_copy_bits(skb, off + ip->ihl*4 + sizeof(struct tcphdr), + opt, optsize) < 0) { + if (info->flags & IPT_OSF_LOG) + loga("TRUNCATED"); + if (info->flags & IPT_OSF_NETLINK) + strcpy(fg.details, "TRUNCATED"); + } else { + for (i = 0; i < optsize; i++) { + if (info->flags & IPT_OSF_LOG) + loga("%02X", opt[i]); + } + if (info->flags & IPT_OSF_NETLINK) + memcpy(fg.details, opt, MAXDETLEN); + } + } + if ((info->flags & IPT_OSF_LOG)) + loga(" %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", + NIPQUAD(ip->saddr), ntohs(tcp->source), + NIPQUAD(ip->daddr), ntohs(tcp->dest)); + + if (info->flags & (IPT_OSF_NETLINK | IPT_OSF_CONNECTOR)) { + fg.wss.val = window; + fg.ttl = ip->ttl; + fg.df = df; + fg.ss = totlen; + strncpy(fg.genre, "Unknown", MAXGENRELEN); + + spin_lock_bh(&ipt_osf_netlink_lock); + if (info->flags & IPT_OSF_NETLINK) + ipt_osf_nlsend(&fg, skb); + if (info->flags & IPT_OSF_CONNECTOR) + ipt_osf_send_connector(&fg, skb); + spin_unlock_bh(&ipt_osf_netlink_lock); + } + } + + read_unlock(&osf_lock); + + if (fcount) + fmatch = FMATCH_OK; + + return (fmatch == FMATCH_OK)?1:0; +} + +static int +checkentry(const char *tablename, + const struct ipt_ip *ip, + void *matchinfo, + unsigned int matchsize, + unsigned int hook_mask) +{ + if (matchsize != IPT_ALIGN(sizeof(struct ipt_osf_info))) + return 0; + if (ip->proto != IPPROTO_TCP) + return 0; + + return 1; +} + +static char * osf_strchr(char *ptr, char c) +{ + char *tmp; + + tmp = strchr(ptr, c); + + while (tmp && tmp+1 && isspace(*(tmp+1))) + tmp++; + + return tmp; +} + +static struct osf_finger * finger_alloc(void) +{ + struct osf_finger *f; + + f = kmalloc(sizeof(struct osf_finger), GFP_KERNEL); + if (f) + memset(f, 0, sizeof(struct osf_finger)); + + return f; +} + +static void finger_free(struct osf_finger *f) +{ + memset(f, 0, sizeof(struct osf_finger)); + kfree(f); +} + + +static void osf_parse_opt(struct osf_opt *opt, int *optnum, char *obuf, int olen) +{ + int i, op; + char *ptr, wc; + unsigned long val; + + ptr = &obuf[0]; + i = 0; + while (ptr != NULL && i < olen) { + val = 0; + op = 0; + wc = 0; + switch (obuf[i]) { + case 'N': + op = OSFOPT_NOP; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + case 'S': + op = OSFOPT_SACKP; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'T': + op = OSFOPT_TS; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + case 'W': + op = OSFOPT_WSO; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + switch (obuf[i+1]) { + case '%': wc = '%'; break; + case 'S': wc = 'S'; break; + case 'T': wc = 'T'; break; + default: wc = 0; break; + } + + *ptr = '\0'; + ptr++; + if (wc) + val = simple_strtoul(&obuf[i+2], NULL, 10); + else + val = simple_strtoul(&obuf[i+1], NULL, 10); + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + case 'M': + op = OSFOPT_MSS; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + if (obuf[i+1] == '%') + wc = '%'; + *ptr = '\0'; + ptr++; + if (wc) + val = simple_strtoul(&obuf[i+2], NULL, 10); + else + val = simple_strtoul(&obuf[i+1], NULL, 10); + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + case 'E': + op = OSFOPT_EOL; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + default: + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) { + ptr++; + i += (int)(ptr-&obuf[i]); + + } else + i++; + break; + } + + opt[*optnum].kind = IANA_opts[op].kind; + opt[*optnum].length = IANA_opts[op].length; + opt[*optnum].wc.wc = wc; + opt[*optnum].wc.val = val; + + (*optnum)++; + } +} + +static int osf_proc_read(char *buf, char **start, off_t off, int count, int *eof, void *data) +{ + struct osf_finger *f = NULL; + int i, __count, err; + + *eof = 1; + __count = count; + count = 0; + + read_lock_bh(&osf_lock); + list_for_each_entry(f, &finger_list, flist) { + log("%s [%s]", f->genre, f->details); + + err = snprintf(buf+count, __count-count, "%s - %s[%s] : %s", + f->genre, f->version, + f->subtype, f->details); + if (err == 0 || __count <= count + err) + break; + else + count += err; + if (f->opt_num) { + loga(" OPT: "); + //count += sprintf(buf+count, " OPT: "); + for (i=0; iopt_num; ++i) { + //count += sprintf(buf+count, "%d.%c%lu; ", + // f->opt[i].kind, (f->opt[i].wc.wc)?f->opt[i].wc.wc:' ', f->opt[i].wc.val); + loga("%d.%c%lu; ", + f->opt[i].kind, (f->opt[i].wc.wc)?f->opt[i].wc.wc:' ', f->opt[i].wc.val); + } + } + loga("\n"); + err = snprintf(buf+count, __count-count, "\n"); + if (err == 0 || __count <= count + err) + break; + else + count += err; + } + read_unlock_bh(&osf_lock); + + return count; +} + +static int osf_proc_write(struct file *file, const char *buffer, unsigned long count, void *data) +{ + int cnt; + unsigned long i; + char obuf[MAXOPTSTRLEN]; + struct osf_finger *finger, *n; + + char *pbeg, *pend; + + if (count == strlen(OSFFLUSH) && !strncmp(buffer, OSFFLUSH, strlen(OSFFLUSH))) { + int i = 0; + write_lock_bh(&osf_lock); + list_for_each_entry_safe(finger, n, &finger_list, flist) { + i++; + list_del(&finger->flist); + finger_free(finger); + } + write_unlock_bh(&osf_lock); + + log("Flushed %d entries.\n", i); + + return count; + } + + + cnt = 0; + for (i=0; iwss.wc = 'S'; + if (pbeg[1] == '%') + finger->wss.val = simple_strtoul(pbeg+2, NULL, 10); + else if (pbeg[1] == '*') + finger->wss.val = 0; + else + finger->wss.val = simple_strtoul(pbeg+1, NULL, 10); + } else if (pbeg[0] == 'T') { + finger->wss.wc = 'T'; + if (pbeg[1] == '%') + finger->wss.val = simple_strtoul(pbeg+2, NULL, 10); + else if (pbeg[1] == '*') + finger->wss.val = 0; + else + finger->wss.val = simple_strtoul(pbeg+1, NULL, 10); + } else if (pbeg[0] == '%') { + finger->wss.wc = '%'; + finger->wss.val = simple_strtoul(pbeg+1, NULL, 10); + } else if (isdigit(pbeg[0])) { + finger->wss.wc = 0; + finger->wss.val = simple_strtoul(pbeg, NULL, 10); + } + + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + finger->ttl = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + finger->df = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + finger->ss = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + cnt = snprintf(obuf, sizeof(obuf), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + if (pbeg[0] == '@' || pbeg[0] == '*') + cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg+1); + else + cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + cnt = snprintf(finger->version, sizeof(finger->version), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) { + *pend = '\0'; + cnt = snprintf(finger->subtype, sizeof(finger->subtype), "%s", pbeg); + pbeg = pend+1; + } + + cnt = snprintf(finger->details, + ((count - (pbeg - buffer)+1) > MAXDETLEN)?MAXDETLEN:(count - (pbeg - buffer)+1), + "%s", pbeg); + + log("%s - %s[%s] : %s\n", + finger->genre, finger->version, + finger->subtype, finger->details); + + osf_parse_opt(finger->opt, &finger->opt_num, obuf, sizeof(obuf)); + + + write_lock_bh(&osf_lock); + list_add_tail(&finger->flist, &finger_list); + write_unlock_bh(&osf_lock); + + return count; +} + +static int __devinit osf_init(void) +{ + int err; + struct proc_dir_entry *p; + + log("Startng OS fingerprint matching module.\n"); + + INIT_LIST_HEAD(&finger_list); + + err = ipt_register_match(&osf_match); + if (err) { + log("Failed to register OS fingerprint matching module.\n"); + return -ENXIO; + } + + p = create_proc_entry("sys/net/ipv4/osf", S_IFREG | 0644, NULL); + if (!p) { + ipt_unregister_match(&osf_match); + return -ENXIO; + } + + p->write_proc = osf_proc_write; + p->read_proc = osf_proc_read; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + nts = netlink_kernel_create(NETLINK_NFLOG, NULL); +#else + nts = netlink_kernel_create(NETLINK_NFLOG, 1, NULL, THIS_MODULE); +#endif + if (!nts) { + log("netlink_kernel_create() failed\n"); + } + + return 0; +} + +static void __devexit osf_fini(void) +{ + struct osf_finger *f, *n; + + remove_proc_entry("sys/net/ipv4/osf", NULL); + ipt_unregister_match(&osf_match); + if (nts && nts->sk_socket) + sock_release(nts->sk_socket); + + list_for_each_entry_safe(f, n, &finger_list, flist) { + list_del(&f->flist); + finger_free(f); + } + + log("OS fingerprint matching module finished.\n"); +} + +module_init(osf_init); +module_exit(osf_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +MODULE_DESCRIPTION("Passive OS fingerprint matching.");