mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-10-19 09:59:04 +00:00
4acdbdbe50
If a connection tracking helper tells us to expect a connection, and we're already expecting that connection, we simply free the one they gave us and return success. The problem is that NAT helpers (eg. FTP) have to allocate the expectation first (to see what port is available) then rewrite the packet. If that rewrite fails, they try to remove the expectation, but it was freed in ip_conntrack_expect_related. This is one example of a larger problem: having registered the expectation, the pointer is no longer ours to use. Reference counting is needed for ctnetlink anyway, so introduce it now. To have a single "put" path, we need to grab the reference to the connection on creation, rather than open-coding it in the caller. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: David S. Miller <davem@davemloft.net>
500 lines
13 KiB
C
500 lines
13 KiB
C
/* FTP extension for IP connection tracking. */
|
|
|
|
/* (C) 1999-2001 Paul `Rusty' Russell
|
|
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/ctype.h>
|
|
#include <net/checksum.h>
|
|
#include <net/tcp.h>
|
|
|
|
#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
|
|
#include <linux/netfilter_ipv4/ip_conntrack_ftp.h>
|
|
#include <linux/moduleparam.h>
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Rusty Russell <rusty@rustcorp.com.au>");
|
|
MODULE_DESCRIPTION("ftp connection tracking helper");
|
|
|
|
/* This is slow, but it's simple. --RR */
|
|
static char ftp_buffer[65536];
|
|
|
|
static DEFINE_SPINLOCK(ip_ftp_lock);
|
|
|
|
#define MAX_PORTS 8
|
|
static int ports[MAX_PORTS];
|
|
static int ports_c;
|
|
module_param_array(ports, int, &ports_c, 0400);
|
|
|
|
static int loose;
|
|
module_param(loose, int, 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
|
|
#else
|
|
#define DEBUGP(format, args...)
|
|
#endif
|
|
|
|
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;
|
|
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,
|
|
},
|
|
{
|
|
IP_CT_DIR_REPLY,
|
|
"229 ", sizeof("229 ") - 1, '(', ')',
|
|
IP_CT_FTP_EPSV,
|
|
try_epsv_response,
|
|
},
|
|
};
|
|
|
|
static int try_number(const char *data, size_t dlen, u_int32_t array[],
|
|
int array_size, char sep, char term)
|
|
{
|
|
u_int32_t i, len;
|
|
|
|
memset(array, 0, sizeof(array[0])*array_size);
|
|
|
|
/* Keep data pointing at next char. */
|
|
for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) {
|
|
if (*data >= '0' && *data <= '9') {
|
|
array[i] = array[i]*10 + *data - '0';
|
|
}
|
|
else if (*data == sep)
|
|
i++;
|
|
else {
|
|
/* Unexpected character; true if it's the
|
|
terminator and we're finished. */
|
|
if (*data == term && i == array_size - 1)
|
|
return len;
|
|
|
|
DEBUGP("Char %u (got %u nums) `%u' unexpected\n",
|
|
len, i, *data);
|
|
return 0;
|
|
}
|
|
}
|
|
DEBUGP("Failed to fill %u numbers separated by %c\n", array_size, sep);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Returns 0, or length of numbers: 192,168,1,1,5,6 */
|
|
static int try_rfc959(const char *data, size_t dlen, u_int32_t array[6],
|
|
char term)
|
|
{
|
|
return try_number(data, dlen, array, 6, ',', term);
|
|
}
|
|
|
|
/* Grab port: number up to delimiter */
|
|
static int get_port(const char *data, int start, size_t dlen, char delim,
|
|
u_int32_t array[2])
|
|
{
|
|
u_int16_t port = 0;
|
|
int i;
|
|
|
|
for (i = start; i < dlen; i++) {
|
|
/* Finished? */
|
|
if (data[i] == delim) {
|
|
if (port == 0)
|
|
break;
|
|
array[0] = port >> 8;
|
|
array[1] = port;
|
|
return i + 1;
|
|
}
|
|
else if (data[i] >= '0' && data[i] <= '9')
|
|
port = port*10 + data[i] - '0';
|
|
else /* Some other crap */
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Returns 0, or length of numbers: |1|132.235.1.2|6275| */
|
|
static int try_eprt(const char *data, size_t dlen, u_int32_t array[6],
|
|
char term)
|
|
{
|
|
char delim;
|
|
int length;
|
|
|
|
/* First character is delimiter, then "1" for IPv4, then
|
|
delimiter again. */
|
|
if (dlen <= 3) return 0;
|
|
delim = data[0];
|
|
if (isdigit(delim) || delim < 33 || delim > 126
|
|
|| data[1] != '1' || data[2] != delim)
|
|
return 0;
|
|
|
|
DEBUGP("EPRT: Got |1|!\n");
|
|
/* Now we have IP address. */
|
|
length = try_number(data + 3, dlen - 3, array, 4, '.', delim);
|
|
if (length == 0)
|
|
return 0;
|
|
|
|
DEBUGP("EPRT: Got IP address!\n");
|
|
/* Start offset includes initial "|1|", and trailing delimiter */
|
|
return get_port(data, 3 + length + 1, dlen, delim, array+4);
|
|
}
|
|
|
|
/* Returns 0, or length of numbers: |||6446| */
|
|
static int try_epsv_response(const char *data, size_t dlen, u_int32_t array[6],
|
|
char term)
|
|
{
|
|
char delim;
|
|
|
|
/* Three delimiters. */
|
|
if (dlen <= 3) return 0;
|
|
delim = data[0];
|
|
if (isdigit(delim) || delim < 33 || delim > 126
|
|
|| data[1] != delim || data[2] != delim)
|
|
return 0;
|
|
|
|
return get_port(data, 3, dlen, delim, array+4);
|
|
}
|
|
|
|
/* Return 1 for match, 0 for accept, -1 for partial. */
|
|
static int find_pattern(const char *data, size_t dlen,
|
|
const char *pattern, size_t plen,
|
|
char skip, char term,
|
|
unsigned int *numoff,
|
|
unsigned int *numlen,
|
|
u_int32_t array[6],
|
|
int (*getnum)(const char *, size_t, u_int32_t[], char))
|
|
{
|
|
size_t i;
|
|
|
|
DEBUGP("find_pattern `%s': dlen = %u\n", pattern, dlen);
|
|
if (dlen == 0)
|
|
return 0;
|
|
|
|
if (dlen <= plen) {
|
|
/* Short packet: try for partial? */
|
|
if (strnicmp(data, pattern, dlen) == 0)
|
|
return -1;
|
|
else return 0;
|
|
}
|
|
|
|
if (strnicmp(data, pattern, plen) != 0) {
|
|
#if 0
|
|
size_t i;
|
|
|
|
DEBUGP("ftp: string mismatch\n");
|
|
for (i = 0; i < plen; i++) {
|
|
DEBUGP("ftp:char %u `%c'(%u) vs `%c'(%u)\n",
|
|
i, data[i], data[i],
|
|
pattern[i], pattern[i]);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
DEBUGP("Pattern matches!\n");
|
|
/* Now we've found the constant string, try to skip
|
|
to the 'skip' character */
|
|
for (i = plen; data[i] != skip; i++)
|
|
if (i == dlen - 1) return -1;
|
|
|
|
/* Skip over the last character */
|
|
i++;
|
|
|
|
DEBUGP("Skipped up to `%c'!\n", skip);
|
|
|
|
*numoff = i;
|
|
*numlen = getnum(data + i, dlen - i, array, term);
|
|
if (!*numlen)
|
|
return -1;
|
|
|
|
DEBUGP("Match succeeded!\n");
|
|
return 1;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
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;
|
|
else if (oldest != NUM_SEQ_TO_REMEMBER)
|
|
info->seq_aft_nl[dir][oldest] = nl_seq;
|
|
}
|
|
|
|
static int help(struct sk_buff **pskb,
|
|
struct ip_conntrack *ct,
|
|
enum ip_conntrack_info ctinfo)
|
|
{
|
|
unsigned int dataoff, datalen;
|
|
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;
|
|
unsigned int i;
|
|
int found = 0, ends_in_nl;
|
|
|
|
/* Until there's been traffic both ways, don't look in packets. */
|
|
if (ctinfo != IP_CT_ESTABLISHED
|
|
&& ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
|
|
DEBUGP("ftp: Conntrackinfo = %u\n", ctinfo);
|
|
return NF_ACCEPT;
|
|
}
|
|
|
|
th = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*4,
|
|
sizeof(_tcph), &_tcph);
|
|
if (th == NULL)
|
|
return NF_ACCEPT;
|
|
|
|
dataoff = (*pskb)->nh.iph->ihl*4 + th->doff*4;
|
|
/* No data? */
|
|
if (dataoff >= (*pskb)->len) {
|
|
DEBUGP("ftp: pskblen = %u\n", (*pskb)->len);
|
|
return NF_ACCEPT;
|
|
}
|
|
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;
|
|
|
|
/* 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_update_nl;
|
|
}
|
|
|
|
/* Initialize IP array to expected address (it's not mentioned
|
|
in EPSV responses) */
|
|
array[0] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 24) & 0xFF;
|
|
array[1] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 16) & 0xFF;
|
|
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(fb_ptr, (*pskb)->len - dataoff,
|
|
search[i].pattern,
|
|
search[i].plen,
|
|
search[i].skip,
|
|
search[i].term,
|
|
&matchoff, &matchlen,
|
|
array,
|
|
search[i].getnum);
|
|
if (found) break;
|
|
}
|
|
if (found == -1) {
|
|
/* We don't usually drop packets. After all, this is
|
|
connection tracking, not packet filtering.
|
|
However, it is necessary for accurate tracking in
|
|
this case. */
|
|
if (net_ratelimit())
|
|
printk("conntrack_ftp: partial %s %u+%u\n",
|
|
search[i].pattern,
|
|
ntohl(th->seq), datalen);
|
|
ret = NF_DROP;
|
|
goto out;
|
|
} else if (found == 0) { /* No match */
|
|
ret = NF_ACCEPT;
|
|
goto out_update_nl;
|
|
}
|
|
|
|
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(ct);
|
|
if (exp == NULL) {
|
|
ret = NF_DROP;
|
|
goto out;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
if (htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3])
|
|
!= 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
|
|
NAT. */
|
|
DEBUGP("conntrack_ftp: NOT RECORDING: %u,%u,%u,%u != %u.%u.%u.%u\n",
|
|
array[0], array[1], array[2], array[3],
|
|
NIPQUAD(ct->tuplehash[dir].tuple.src.ip));
|
|
|
|
/* Thanks to Cristiano Lincoln Mattos
|
|
<lincoln@cesar.org.br> for reporting this potential
|
|
problem (DMZ machines opening holes to internal
|
|
networks, or the packet filter itself). */
|
|
if (!loose) {
|
|
ret = NF_ACCEPT;
|
|
goto out_put_expect;
|
|
}
|
|
exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16)
|
|
| (array[2] << 8) | array[3]);
|
|
}
|
|
|
|
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 } }, 0xFF }});
|
|
|
|
exp->expectfn = NULL;
|
|
|
|
/* Now, NAT might want to mangle the packet, and register the
|
|
* (possibly changed) expectation itself. */
|
|
if (ip_nat_ftp_hook)
|
|
ret = ip_nat_ftp_hook(pskb, ctinfo, search[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);
|
|
|
|
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);
|
|
out:
|
|
spin_unlock_bh(&ip_ftp_lock);
|
|
return ret;
|
|
}
|
|
|
|
static struct ip_conntrack_helper ftp[MAX_PORTS];
|
|
static char ftp_names[MAX_PORTS][10];
|
|
|
|
/* Not __exit: called from init() */
|
|
static void fini(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ports_c; i++) {
|
|
DEBUGP("ip_ct_ftp: unregistering helper for port %d\n",
|
|
ports[i]);
|
|
ip_conntrack_helper_unregister(&ftp[i]);
|
|
}
|
|
}
|
|
|
|
static int __init init(void)
|
|
{
|
|
int i, ret;
|
|
char *tmpname;
|
|
|
|
if (ports_c == 0)
|
|
ports[ports_c++] = FTP_PORT;
|
|
|
|
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 = 0xFF;
|
|
ftp[i].max_expected = 1;
|
|
ftp[i].timeout = 5 * 60; /* 5 minutes */
|
|
ftp[i].me = THIS_MODULE;
|
|
ftp[i].help = help;
|
|
|
|
tmpname = &ftp_names[i][0];
|
|
if (ports[i] == FTP_PORT)
|
|
sprintf(tmpname, "ftp");
|
|
else
|
|
sprintf(tmpname, "ftp-%d", ports[i]);
|
|
ftp[i].name = tmpname;
|
|
|
|
DEBUGP("ip_ct_ftp: registering helper for port %d\n",
|
|
ports[i]);
|
|
ret = ip_conntrack_helper_register(&ftp[i]);
|
|
|
|
if (ret) {
|
|
fini();
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
module_init(init);
|
|
module_exit(fini);
|