X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv4%2Fnetfilter%2Fip_conntrack_ftp.c;h=0410c99cacae04d835b6f63ed789a8d54d81b4e7;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=14e54c960dd8122711d8c048a9a08199b3c9483a;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/net/ipv4/netfilter/ip_conntrack_ftp.c b/net/ipv4/netfilter/ip_conntrack_ftp.c index 14e54c960..0410c99ca 100644 --- a/net/ipv4/netfilter/ip_conntrack_ftp.c +++ b/net/ipv4/netfilter/ip_conntrack_ftp.c @@ -8,7 +8,6 @@ * published by the Free Software Foundation. */ -#include #include #include #include @@ -16,29 +15,34 @@ #include #include -#include #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rusty Russell "); MODULE_DESCRIPTION("ftp connection tracking helper"); /* This is slow, but it's simple. --RR */ -static char ftp_buffer[65536]; - -DECLARE_LOCK(ip_ftp_lock); -struct module *ip_conntrack_ftp = THIS_MODULE; +static char *ftp_buffer; +static DEFINE_SPINLOCK(ip_ftp_lock); #define MAX_PORTS 8 -static int ports[MAX_PORTS]; +static unsigned short ports[MAX_PORTS]; static int ports_c; -#ifdef MODULE_PARM -MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i"); -#endif +module_param_array(ports, ushort, &ports_c, 0400); static int loose; -MODULE_PARM(loose, "i"); +module_param(loose, bool, 0600); + +unsigned int (*ip_nat_ftp_hook)(struct sk_buff **pskb, + enum ip_conntrack_info ctinfo, + enum ip_ct_ftp_type type, + unsigned int matchoff, + unsigned int matchlen, + struct ip_conntrack_expect *exp, + u32 *seq); +EXPORT_SYMBOL_GPL(ip_nat_ftp_hook); #if 0 #define DEBUGP printk @@ -50,38 +54,49 @@ static int try_rfc959(const char *, size_t, u_int32_t [], char); static int try_eprt(const char *, size_t, u_int32_t [], char); static int try_epsv_response(const char *, size_t, u_int32_t [], char); -static struct ftp_search { - enum ip_conntrack_dir dir; +static const struct ftp_search { const char *pattern; size_t plen; char skip; char term; enum ip_ct_ftp_type ftptype; int (*getnum)(const char *, size_t, u_int32_t[], char); -} search[] = { - { - IP_CT_DIR_ORIGINAL, - "PORT", sizeof("PORT") - 1, ' ', '\r', - IP_CT_FTP_PORT, - try_rfc959, - }, - { - IP_CT_DIR_REPLY, - "227 ", sizeof("227 ") - 1, '(', ')', - IP_CT_FTP_PASV, - try_rfc959, - }, - { - IP_CT_DIR_ORIGINAL, - "EPRT", sizeof("EPRT") - 1, ' ', '\r', - IP_CT_FTP_EPRT, - try_eprt, +} search[IP_CT_DIR_MAX][2] = { + [IP_CT_DIR_ORIGINAL] = { + { + .pattern = "PORT", + .plen = sizeof("PORT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = IP_CT_FTP_PORT, + .getnum = try_rfc959, + }, + { + .pattern = "EPRT", + .plen = sizeof("EPRT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = IP_CT_FTP_EPRT, + .getnum = try_eprt, + }, }, - { - IP_CT_DIR_REPLY, - "229 ", sizeof("229 ") - 1, '(', ')', - IP_CT_FTP_EPSV, - try_epsv_response, + [IP_CT_DIR_REPLY] = { + { + .pattern = "227 ", + .plen = sizeof("227 ") - 1, + .skip = '(', + .term = ')', + .ftptype = IP_CT_FTP_PASV, + .getnum = try_rfc959, + }, + { + .pattern = "229 ", + .plen = sizeof("229 ") - 1, + .skip = '(', + .term = ')', + .ftptype = IP_CT_FTP_EPSV, + .getnum = try_epsv_response, + }, }, }; @@ -244,23 +259,58 @@ static int find_pattern(const char *data, size_t dlen, return 1; } -static int help(struct sk_buff *skb, +/* Look up to see if we're just after a \n. */ +static int find_nl_seq(u32 seq, const struct ip_ct_ftp_master *info, int dir) +{ + unsigned int i; + + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) + if (info->seq_aft_nl[dir][i] == seq) + return 1; + return 0; +} + +/* We don't update if it's older than what we have. */ +static void update_nl_seq(u32 nl_seq, struct ip_ct_ftp_master *info, int dir, + struct sk_buff *skb) +{ + unsigned int i, oldest = NUM_SEQ_TO_REMEMBER; + + /* Look for oldest: if we find exact match, we're done. */ + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { + if (info->seq_aft_nl[dir][i] == nl_seq) + return; + + if (oldest == info->seq_aft_nl_num[dir] + || before(info->seq_aft_nl[dir][i], oldest)) + oldest = i; + } + + if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { + info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; + ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); + } else if (oldest != NUM_SEQ_TO_REMEMBER) { + info->seq_aft_nl[dir][oldest] = nl_seq; + ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); + } +} + +static int help(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { unsigned int dataoff, datalen; - struct tcphdr tcph; - u_int32_t old_seq_aft_nl; - int old_seq_aft_nl_set, ret; - u_int32_t array[6] = { 0 }; + struct tcphdr _tcph, *th; + char *fb_ptr; + int ret; + u32 seq, array[6] = { 0 }; int dir = CTINFO2DIR(ctinfo); unsigned int matchlen, matchoff; struct ip_ct_ftp_master *ct_ftp_info = &ct->help.ct_ftp_info; struct ip_conntrack_expect *exp; - struct ip_ct_ftp_expect *exp_ftp_info; - unsigned int i; - int found = 0; + int found = 0, ends_in_nl; + typeof(ip_nat_ftp_hook) ip_nat_ftp; /* Until there's been traffic both ways, don't look in packets. */ if (ctinfo != IP_CT_ESTABLISHED @@ -269,42 +319,35 @@ static int help(struct sk_buff *skb, return NF_ACCEPT; } - if (skb_copy_bits(skb, skb->nh.iph->ihl*4, &tcph, sizeof(tcph)) != 0) + th = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*4, + sizeof(_tcph), &_tcph); + if (th == NULL) return NF_ACCEPT; - dataoff = skb->nh.iph->ihl*4 + tcph.doff*4; + dataoff = (*pskb)->nh.iph->ihl*4 + th->doff*4; /* No data? */ - if (dataoff >= skb->len) { - DEBUGP("ftp: skblen = %u\n", skb->len); + if (dataoff >= (*pskb)->len) { + DEBUGP("ftp: pskblen = %u\n", (*pskb)->len); return NF_ACCEPT; } - datalen = skb->len - dataoff; - - LOCK_BH(&ip_ftp_lock); - skb_copy_bits(skb, dataoff, ftp_buffer, skb->len - dataoff); - - old_seq_aft_nl_set = ct_ftp_info->seq_aft_nl_set[dir]; - old_seq_aft_nl = ct_ftp_info->seq_aft_nl[dir]; - - DEBUGP("conntrack_ftp: datalen %u\n", datalen); - if (ftp_buffer[datalen - 1] == '\n') { - DEBUGP("conntrack_ftp: datalen %u ends in \\n\n", datalen); - if (!old_seq_aft_nl_set - || after(ntohl(tcph.seq) + datalen, old_seq_aft_nl)) { - DEBUGP("conntrack_ftp: updating nl to %u\n", - ntohl(tcph.seq) + datalen); - ct_ftp_info->seq_aft_nl[dir] = - ntohl(tcph.seq) + datalen; - ct_ftp_info->seq_aft_nl_set[dir] = 1; - } - } + datalen = (*pskb)->len - dataoff; + + spin_lock_bh(&ip_ftp_lock); + fb_ptr = skb_header_pointer(*pskb, dataoff, + (*pskb)->len - dataoff, ftp_buffer); + BUG_ON(fb_ptr == NULL); + + ends_in_nl = (fb_ptr[datalen - 1] == '\n'); + seq = ntohl(th->seq) + datalen; - if(!old_seq_aft_nl_set || - (ntohl(tcph.seq) != old_seq_aft_nl)) { - DEBUGP("ip_conntrack_ftp_help: wrong seq pos %s(%u)\n", + /* Look up to see if we're just after a \n. */ + if (!find_nl_seq(ntohl(th->seq), ct_ftp_info, dir)) { + /* Now if this ends in \n, update ftp info. */ + DEBUGP("ip_conntrack_ftp_help: wrong seq pos %s(%u) or %s(%u)\n", + ct_ftp_info->seq_aft_nl[0][dir] old_seq_aft_nl_set ? "":"(UNSET) ", old_seq_aft_nl); ret = NF_ACCEPT; - goto out; + goto out_update_nl; } /* Initialize IP array to expected address (it's not mentioned @@ -314,17 +357,15 @@ static int help(struct sk_buff *skb, array[2] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 8) & 0xFF; array[3] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF; - for (i = 0; i < ARRAY_SIZE(search); i++) { - if (search[i].dir != dir) continue; - - found = find_pattern(ftp_buffer, skb->len - dataoff, - search[i].pattern, - search[i].plen, - search[i].skip, - search[i].term, + for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { + found = find_pattern(fb_ptr, (*pskb)->len - dataoff, + search[dir][i].pattern, + search[dir][i].plen, + search[dir][i].skip, + search[dir][i].term, &matchoff, &matchlen, array, - search[i].getnum); + search[dir][i].getnum); if (found) break; } if (found == -1) { @@ -334,36 +375,32 @@ static int help(struct sk_buff *skb, this case. */ if (net_ratelimit()) printk("conntrack_ftp: partial %s %u+%u\n", - search[i].pattern, - ntohl(tcph.seq), datalen); + search[dir][i].pattern, + ntohl(th->seq), datalen); ret = NF_DROP; goto out; } else if (found == 0) { /* No match */ ret = NF_ACCEPT; - goto out; + goto out_update_nl; } - DEBUGP("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", - (int)matchlen, data + matchoff, - matchlen, ntohl(tcph.seq) + matchoff); - + DEBUGP("conntrack_ftp: match `%s' (%u bytes at %u)\n", + fb_ptr + matchoff, matchlen, ntohl(th->seq) + matchoff); + /* Allocate expectation which will be inserted */ - exp = ip_conntrack_expect_alloc(); + exp = ip_conntrack_expect_alloc(ct); if (exp == NULL) { - ret = NF_ACCEPT; + ret = NF_DROP; goto out; } - exp_ftp_info = &exp->help.exp_ftp_info; + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + exp->tuple.dst.ip = ct->tuplehash[!dir].tuple.dst.ip; - /* Update the ftp info */ if (htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]) - == ct->tuplehash[dir].tuple.src.ip) { - exp->seq = ntohl(tcph.seq) + matchoff; - exp_ftp_info->len = matchlen; - exp_ftp_info->ftptype = search[i].ftptype; - exp_ftp_info->port = array[4] << 8 | array[5]; - } else { + != ct->tuplehash[dir].tuple.src.ip) { /* Enrico Scholz's passive FTP to partially RNAT'd ftp server: it really wants us to connect to a different IP address. Simply don't record it for @@ -378,36 +415,55 @@ static int help(struct sk_buff *skb, networks, or the packet filter itself). */ if (!loose) { ret = NF_ACCEPT; - goto out; + goto out_put_expect; } + exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16) + | (array[2] << 8) | array[3]); } - exp->tuple = ((struct ip_conntrack_tuple) - { { ct->tuplehash[!dir].tuple.src.ip, - { 0 } }, - { htonl((array[0] << 24) | (array[1] << 16) - | (array[2] << 8) | array[3]), - { .tcp = { htons(array[4] << 8 | array[5]) } }, - IPPROTO_TCP }}); + exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip; + exp->tuple.dst.u.tcp.port = htons(array[4] << 8 | array[5]); + exp->tuple.src.u.tcp.port = 0; /* Don't care. */ + exp->tuple.dst.protonum = IPPROTO_TCP; exp->mask = ((struct ip_conntrack_tuple) - { { 0xFFFFFFFF, { 0 } }, - { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFFFF }}); + { { htonl(0xFFFFFFFF), { 0 } }, + { htonl(0xFFFFFFFF), { .tcp = { htons(0xFFFF) } }, 0xFF }}); exp->expectfn = NULL; + exp->flags = 0; + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + ip_nat_ftp = rcu_dereference(ip_nat_ftp_hook); + if (ip_nat_ftp) + ret = ip_nat_ftp(pskb, ctinfo, search[dir][i].ftptype, + matchoff, matchlen, exp, &seq); + else { + /* Can't expect this? Best to drop packet now. */ + if (ip_conntrack_expect_related(exp) != 0) + ret = NF_DROP; + else + ret = NF_ACCEPT; + } + +out_put_expect: + ip_conntrack_expect_put(exp); - /* Ignore failure; should only happen with NAT */ - ip_conntrack_expect_related(exp, ct); - ret = NF_ACCEPT; +out_update_nl: + /* Now if this ends in \n, update ftp info. Seq may have been + * adjusted by NAT code. */ + if (ends_in_nl) + update_nl_seq(seq, ct_ftp_info,dir, *pskb); out: - UNLOCK_BH(&ip_ftp_lock); + spin_unlock_bh(&ip_ftp_lock); return ret; } static struct ip_conntrack_helper ftp[MAX_PORTS]; -static char ftp_names[MAX_PORTS][10]; +static char ftp_names[MAX_PORTS][sizeof("ftp-65535")]; /* Not __exit: called from init() */ -static void fini(void) +static void ip_conntrack_ftp_fini(void) { int i; for (i = 0; i < ports_c; i++) { @@ -415,25 +471,30 @@ static void fini(void) ports[i]); ip_conntrack_helper_unregister(&ftp[i]); } + + kfree(ftp_buffer); } -static int __init init(void) +static int __init ip_conntrack_ftp_init(void) { int i, ret; char *tmpname; - if (ports[0] == 0) - ports[0] = FTP_PORT; + ftp_buffer = kmalloc(65536, GFP_KERNEL); + if (!ftp_buffer) + return -ENOMEM; + + if (ports_c == 0) + ports[ports_c++] = FTP_PORT; - for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { + for (i = 0; i < ports_c; i++) { ftp[i].tuple.src.u.tcp.port = htons(ports[i]); ftp[i].tuple.dst.protonum = IPPROTO_TCP; - ftp[i].mask.src.u.tcp.port = 0xFFFF; - ftp[i].mask.dst.protonum = 0xFFFF; + ftp[i].mask.src.u.tcp.port = htons(0xFFFF); + ftp[i].mask.dst.protonum = 0xFF; ftp[i].max_expected = 1; - ftp[i].timeout = 0; - ftp[i].flags = IP_CT_HELPER_F_REUSE_EXPECT; - ftp[i].me = ip_conntrack_ftp; + ftp[i].timeout = 5 * 60; /* 5 minutes */ + ftp[i].me = THIS_MODULE; ftp[i].help = help; tmpname = &ftp_names[i][0]; @@ -448,16 +509,12 @@ static int __init init(void) ret = ip_conntrack_helper_register(&ftp[i]); if (ret) { - fini(); + ip_conntrack_ftp_fini(); return ret; } - ports_c++; } return 0; } -PROVIDES_CONNTRACK(ftp); -EXPORT_SYMBOL(ip_ftp_lock); - -module_init(init); -module_exit(fini); +module_init(ip_conntrack_ftp_init); +module_exit(ip_conntrack_ftp_fini);