diff options
| author | Alex Harpin <development@landsofshadow.co.uk> | 2015-10-02 07:43:42 +0100 | 
|---|---|---|
| committer | Alex Harpin <development@landsofshadow.co.uk> | 2015-10-02 07:43:42 +0100 | 
| commit | ef5ae91676c8ada2a12ea72f889a54452dd94981 (patch) | |
| tree | 0a962905ab9d0c2322f627928521a10c4cb5e20f /src/helpers | |
| parent | 9f9a63cecdc6ac4f449d3eacda6c591f0de9fbf3 (diff) | |
| parent | 8845f3db20c951fcf1db3229a818cfd185f17f2e (diff) | |
| download | conntrack-tools-upstream.tar.gz conntrack-tools-upstream.zip  | |
Merge remote-tracking branch 'source/master' into upstreamupstream
Diffstat (limited to 'src/helpers')
| -rw-r--r-- | src/helpers/Makefile.am | 42 | ||||
| -rw-r--r-- | src/helpers/amanda.c | 203 | ||||
| -rw-r--r-- | src/helpers/dhcpv6.c | 123 | ||||
| -rw-r--r-- | src/helpers/ftp.c | 605 | ||||
| -rw-r--r-- | src/helpers/rpc.c | 488 | ||||
| -rw-r--r-- | src/helpers/sane.c | 173 | ||||
| -rw-r--r-- | src/helpers/ssdp.c | 134 | ||||
| -rw-r--r-- | src/helpers/tftp.c | 138 | ||||
| -rw-r--r-- | src/helpers/tns.c | 408 | 
9 files changed, 2314 insertions, 0 deletions
diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am new file mode 100644 index 0000000..78ef7aa --- /dev/null +++ b/src/helpers/Makefile.am @@ -0,0 +1,42 @@ +include $(top_srcdir)/Make_global.am + +pkglib_LTLIBRARIES = ct_helper_amanda.la \ +		     ct_helper_dhcpv6.la \ +		     ct_helper_ftp.la	\ +		     ct_helper_rpc.la	\ +		     ct_helper_tftp.la	\ +		     ct_helper_tns.la	\ +		     ct_helper_sane.la	\ +		     ct_helper_ssdp.la + +ct_helper_amanda_la_SOURCES = amanda.c +ct_helper_amanda_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_amanda_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_dhcpv6_la_SOURCES = dhcpv6.c +ct_helper_dhcpv6_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_dhcpv6_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_ftp_la_SOURCES = ftp.c +ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_rpc_la_SOURCES = rpc.c +ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tftp_la_SOURCES = tftp.c +ct_helper_tftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tns_la_SOURCES = tns.c +ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_sane_la_SOURCES = sane.c +ct_helper_sane_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_sane_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_ssdp_la_SOURCES = ssdp.c +ct_helper_ssdp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ssdp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/amanda.c b/src/helpers/amanda.c new file mode 100644 index 0000000..9e6c4e7 --- /dev/null +++ b/src/helpers/amanda.c @@ -0,0 +1,203 @@ +/* + * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Adapted from: + * + * Amanda extension for IP connection tracking + * + * (C) 2002 by Brian J. Murrell <netfilter@interlinx.bc.ca> + * based on HW's ip_conntrack_irc.c as well as other modules + * (C) 2006 Patrick McHardy <kaber@trash.net> + * + * 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. + */ +#include "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include <errno.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/udp.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_udp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +static int nat_amanda(struct pkt_buff *pkt, uint32_t ctinfo, +		      unsigned int matchoff, unsigned int matchlen, +		      struct nf_expect *exp) +{ +	char buffer[sizeof("65535")]; +	uint16_t port, initial_port; +	unsigned int ret; +	const struct nf_conntrack *expected; +	struct nf_conntrack *nat_tuple; + +	nat_tuple = nfct_new(); +	if (nat_tuple == NULL) +		return NF_ACCEPT; + +	expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + +	/* Connection comes from client. */ +	initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); +	nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, IP_CT_DIR_ORIGINAL); + +	/* libnetfilter_conntrack needs this */ +	nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); +	nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + +	/* When you see the packet, we need to NAT it the same as the +	 * this one (ie. same IP: it will be TCP and master is UDP). */ +	nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + +	/* Try to get same port: if not, try to change it. */ +	for (port = ntohs(initial_port); port != 0; port++) { +		int res; + +		nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); +		nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + +		res = cthelper_add_expect(exp); +		if (res == 0) +			break; +		else if (res != -EBUSY) { +			port = 0; +			break; +		} +	} + +	if (port == 0) { +		pr_debug("all ports in use\n"); +		return NF_DROP; +	} + +	sprintf(buffer, "%u", port); +	ret = nfq_udp_mangle_ipv4(pkt, matchoff, matchlen, buffer, +				  strlen(buffer)); +	if (ret != NF_ACCEPT) { +		pr_debug("cannot mangle packet\n"); +		cthelper_del_expect(exp); +	} +	return ret; +} + +static char amanda_buffer[65536]; +static unsigned int master_timeout = 300; + +enum amanda_strings { +	SEARCH_CONNECT, +	SEARCH_NEWLINE, +	SEARCH_DATA, +	SEARCH_MESG, +	SEARCH_INDEX, +}; + +static const char *conns[] = { "DATA ", "MESG ", "INDEX " }; + +static int +amanda_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +		 struct myct *myct, uint32_t ctinfo) +{ +	struct nf_expect *exp; +	char *data, *data_limit, *tmp; +	unsigned int dataoff, i; +	uint16_t port, len; +	int ret = NF_ACCEPT; +	struct iphdr *iph; +	union nfct_attr_grp_addr saddr, daddr; + +	/* Only look at packets from the Amanda server */ +	if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) +		return NF_ACCEPT; + +	/* increase the UDP timeout of the master connection as replies from +	 * Amanda clients to the server can be quite delayed */ +	nfct_set_attr_u32(myct->ct, ATTR_TIMEOUT, master_timeout); + +	/* No data? */ +	iph = (struct iphdr *)pktb_network_header(pkt); +	dataoff = iph->ihl*4 + sizeof(struct udphdr); +	if (dataoff >= pktb_len(pkt)) { +		pr_debug("amanda_help: pktlen = %u\n", pktb_len(pkt)); +		return NF_ACCEPT; +	} + +	memcpy(amanda_buffer, pktb_network_header(pkt) + dataoff, +	       pktb_len(pkt) - dataoff); +	data = amanda_buffer; +	data_limit = amanda_buffer + pktb_len(pkt) - dataoff; +	*data_limit = '\0'; + +	/* Search for the CONNECT string */ +	data = strstr(data, "CONNECT "); +	if (!data) +		goto out; +	data += strlen("CONNECT "); + +	/* Only search first line. */ +	if ((tmp = strchr(data, '\n'))) +		*tmp = '\0'; + +	for (i = 0; i < ARRAY_SIZE(conns); i++) { +		char *match = strstr(data, conns[i]); +		if (!match) +			continue; +		tmp = data = match + strlen(conns[i]); +		port = strtoul(data, &data, 10); +		len = data - tmp; +		if (port == 0 || len > 5) +			break; + +		exp = nfexp_new(); +		if (exp == NULL) +			return NF_DROP; + +		cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr); +		cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr); +		cthelper_get_port_src(myct->ct, MYCT_DIR_ORIG, &port); + +		if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr, +					 IPPROTO_TCP, NULL, &port, 0)) { +			nfexp_destroy(exp); +			return NF_DROP; +		} + +		if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { +			ret = nat_amanda(pkt, ctinfo, tmp - amanda_buffer, +					 len, exp); +		} else +			myct->exp = exp; +	} +out: +	return ret; +} + +static struct ctd_helper amanda_helper = { +	.name		= "amanda", +	.l4proto	= IPPROTO_UDP, +	.cb		= amanda_helper_cb, +	.policy		= { +		[0] = { +			.name			= "amanda", +			.expect_max		= ARRAY_SIZE(conns), +			.expect_timeout		= 180, +		}, +	}, +}; + +void __attribute__ ((constructor)) amanda_init(void); + +void amanda_init(void) +{ +	helper_register(&amanda_helper); +} diff --git a/src/helpers/dhcpv6.c b/src/helpers/dhcpv6.c new file mode 100644 index 0000000..73632ec --- /dev/null +++ b/src/helpers/dhcpv6.c @@ -0,0 +1,123 @@ +/* + * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Adapted from: + * + * DHCPv6 multicast connection tracking helper. + * + * (c) 2012 Google Inc. + * + * Original author: Darren Willis <djw@google.com> + * + * 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. + */ + +#include "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include <errno.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/udp.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_udp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +#define DHCPV6_CLIENT_PORT 546 + +static uint16_t dhcpv6_port; + +/* Timeouts for DHCPv6 replies, in seconds, indexed by message type. */ +static const int dhcpv6_timeouts[] = { +	0,	/* No message has type 0. */ +	120,	/* Solicit. */ +	0,	/* Advertise. */ +	30,	/* Request. */ +	4,	/* Confirm. */ +	600,	/* Renew. */ +	600,	/* Rebind. */ +	0,	/* Reply. */ +	1,	/* Release. */ +	1,	/* Decline. */ +	0,	/* Reconfigure. */ +	120,	/* Information Request. */ +	0,	/* Relay-forward. */ +	0	/* Relay-reply. */ +}; + +static inline int ipv6_addr_is_multicast(const struct in6_addr *addr) +{ +	return (addr->s6_addr32[0] & htonl(0xFF000000)) == htonl(0xFF000000); +} + +static int +dhcpv6_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +		 struct myct *myct, uint32_t ctinfo) +{ +	struct iphdr *iph = (struct iphdr *)pktb_network_header(pkt); +	struct ip6_hdr *ip6h = (struct ip6_hdr *)pktb_network_header(pkt); +	int dir = CTINFO2DIR(ctinfo); +	union nfct_attr_grp_addr addr; +	struct nf_expect *exp; +	uint8_t *dhcpv6_msg_type; + +	if (iph->version != 6 || !ipv6_addr_is_multicast(&ip6h->ip6_dst)) +		return NF_ACCEPT; + +	dhcpv6_msg_type = pktb_network_header(pkt) + protoff + sizeof(struct udphdr); +	if (*dhcpv6_msg_type > ARRAY_SIZE(dhcpv6_timeouts)) { +		printf("Dropping DHCPv6 message with bad type %u\n", +			*dhcpv6_msg_type); +		return NF_DROP; +	} + +	exp = nfexp_new(); +	if (exp == NULL) +		return NF_ACCEPT; + +	cthelper_get_addr_src(myct->ct, dir, &addr); + +	if (cthelper_expect_init(exp, myct->ct, 0, NULL, &addr, +				 IPPROTO_UDP, NULL, &dhcpv6_port, +				 NF_CT_EXPECT_PERMANENT)) { +		nfexp_destroy(exp); +		return NF_DROP; +	} + +	myct->exp = exp; + +	if (dhcpv6_timeouts[*dhcpv6_msg_type] > 0) { +		nfct_set_attr_u32(myct->ct, ATTR_TIMEOUT, +				  dhcpv6_timeouts[*dhcpv6_msg_type]); +	} + +	return NF_ACCEPT; +} + +static struct ctd_helper dhcpv6_helper = { +	.name		= "dhcpv6", +	.l4proto	= IPPROTO_UDP, +	.cb		= dhcpv6_helper_cb, +	.policy		= { +		[0] = { +			.name			= "dhcpv6", +			.expect_max		= 1, +			.expect_timeout		= 300, +		}, +	}, +}; + +void __attribute__ ((constructor)) dhcpv6_init(void); + +void dhcpv6_init(void) +{ +	dhcpv6_port = htons(DHCPV6_CLIENT_PORT); +	helper_register(&dhcpv6_helper); +} diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c new file mode 100644 index 0000000..24ee877 --- /dev/null +++ b/src/helpers/ftp.c @@ -0,0 +1,605 @@ +/* + * (C) 2010-2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Based on: kernel-space FTP extension for connection tracking. + * + * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com> + * + * Original copyright notice: + * + * (C) 1999-2001 Paul `Rusty' Russell + * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> + * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.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 "conntrackd.h" +#include "network.h"	/* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include <ctype.h>	/* for isdigit */ +#include <errno.h> + +#define _GNU_SOURCE +#include <netinet/tcp.h> + +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_tcp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +static bool loose; /* XXX: export this as config option. */ + +#define NUM_SEQ_TO_REMEMBER 2 + +/* This structure exists only once per master */ +struct ftp_info { +	/* Valid seq positions for cmd matching after newline */ +	uint32_t seq_aft_nl[MYCT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; +	/* 0 means seq_match_aft_nl not set */ +	int seq_aft_nl_num[MYCT_DIR_MAX]; +}; + +enum nf_ct_ftp_type { +	/* PORT command from client */ +	NF_CT_FTP_PORT, +	/* PASV response from server */ +	NF_CT_FTP_PASV, +	/* EPRT command from client */ +	NF_CT_FTP_EPRT, +	/* EPSV response from server */ +	NF_CT_FTP_EPSV, +}; + +static int +get_ipv6_addr(const char *src, size_t dlen, struct in6_addr *dst, uint8_t term) +{ +	const char *end; +	int ret = in6_pton(src, min_t(size_t, dlen, 0xffff), +				(uint8_t *)dst, term, &end); +	if (ret > 0) +		return (int)(end - src); +	return 0; +} + +static int try_number(const char *data, size_t dlen, uint32_t array[], +		      int array_size, char sep, char term) +{ +	uint32_t len; +	int i; + +	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; +			pr_debug("Char %u (got %u nums) `%u' unexpected\n", +				 len, i, *data); +			return 0; +		} +	} +	pr_debug("Failed to fill %u numbers separated by %c\n", +		 array_size, sep); +	return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, int start, size_t dlen, char delim, +		    struct myct_man *cmd) +{ +	uint16_t tmp_port = 0; +	uint32_t i; + +	for (i = start; i < dlen; i++) { +		/* Finished? */ +		if (data[i] == delim) { +			if (tmp_port == 0) +				break; +			cmd->u.port = htons(tmp_port); +			pr_debug("get_port: return %d\n", tmp_port); +			return i + 1; +		} +		else if (data[i] >= '0' && data[i] <= '9') +			tmp_port = tmp_port*10 + data[i] - '0'; +		else { /* Some other crap */ +			pr_debug("get_port: invalid char.\n"); +			break; +		} +	} +	return 0; +} + +/* Returns 0, or length of numbers: 192,168,1,1,5,6 */ +static int try_rfc959(const char *data, size_t dlen, struct myct_man *cmd, +		      uint16_t l3protonum, char term) +{ +	int length; +	uint32_t array[6]; + +	length = try_number(data, dlen, array, 6, ',', term); +	if (length == 0) +		return 0; + +	cmd->u3.ip =  htonl((array[0] << 24) | (array[1] << 16) | +			   (array[2] << 8) | array[3]); +	cmd->u.port = htons((array[4] << 8) | array[5]); +	return length; +} + +/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */ +static int try_eprt(const char *data, size_t dlen, +		    struct myct_man *cmd, uint16_t l3protonum, char term) +{ +	char delim; +	int length; + +	/* First character is delimiter, then "1" for IPv4 or "2" for IPv6, +	   then delimiter again. */ +	if (dlen <= 3) { +		pr_debug("EPRT: too short\n"); +		return 0; +	} +	delim = data[0]; +	if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) { +		pr_debug("try_eprt: invalid delimitter.\n"); +		return 0; +	} + +	if ((l3protonum == PF_INET && data[1] != '1') || +	    (l3protonum == PF_INET6 && data[1] != '2')) { +		pr_debug("EPRT: invalid protocol number.\n"); +		return 0; +	} + +	pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim); +	if (data[1] == '1') { +		uint32_t array[4]; + +		/* Now we have IP address. */ +		length = try_number(data + 3, dlen - 3, array, 4, '.', delim); +		if (length != 0) +			cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) +					   | (array[2] << 8) | array[3]); +	} else { +		/* Now we have IPv6 address. */ +		length = get_ipv6_addr(data + 3, dlen - 3, +				       (struct in6_addr *)cmd->u3.ip6, delim); +	} + +	if (length == 0) +		return 0; +	pr_debug("EPRT: Got IP address!\n"); +	/* Start offset includes initial "|1|", and trailing delimiter */ +	return get_port(data, 3 + length + 1, dlen, delim, cmd); +} + +/* Returns 0, or length of numbers: |||6446| */ +static int try_epsv_response(const char *data, size_t dlen, +			     struct myct_man *cmd, +			     uint16_t l3protonum, 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, cmd); +} + +static struct ftp_search { +	const char *pattern; +	size_t plen; +	char skip; +	char term; +	enum nf_ct_ftp_type ftptype; +	int (*getnum)(const char *, size_t, struct myct_man *, uint16_t, char); +} search[MYCT_DIR_MAX][2] = { +	[MYCT_DIR_ORIG] = { +		{ +			.pattern	= "PORT", +			.plen		= sizeof("PORT") - 1, +			.skip		= ' ', +			.term		= '\r', +			.ftptype	= NF_CT_FTP_PORT, +			.getnum		= try_rfc959, +		}, +		{ +			.pattern	= "EPRT", +			.plen		= sizeof("EPRT") - 1, +			.skip		= ' ', +			.term		= '\r', +			.ftptype	= NF_CT_FTP_EPRT, +			.getnum		= try_eprt, +		}, +	}, +	[MYCT_DIR_REPL] = { +		{ +			.pattern	= "227 ", +			.plen		= sizeof("227 ") - 1, +			.skip		= '(', +			.term		= ')', +			.ftptype	= NF_CT_FTP_PASV, +			.getnum		= try_rfc959, +		}, +		{ +			.pattern	= "229 ", +			.plen		= sizeof("229 ") - 1, +			.skip		= '(', +			.term		= ')', +			.ftptype	= NF_CT_FTP_EPSV, +			.getnum		= try_epsv_response, +		}, +	}, +}; + +static int ftp_find_pattern(struct pkt_buff *pkt, +			    unsigned int dataoff, unsigned int dlen, +			    const char *pattern, size_t plen, +			    char skip, char term, +			    unsigned int *matchoff, unsigned int *matchlen, +			    struct myct_man *cmd, +			    int (*getnum)(const char *, size_t, +					  struct myct_man *cmd, +					  uint16_t, char), +			    int dir) +{ +	char *data = (char *)pktb_network_header(pkt) + dataoff; +	int numlen; +	uint32_t i; + +	if (dlen == 0) +		return 0; + +	/* short packet, skip partial matching. */ +	if (dlen <= plen) +		return 0; + +	if (strncmp(data, pattern, plen) != 0) +		return 0; + +	pr_debug("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 0; + +	/* Skip over the last character */ +	i++; + +	pr_debug("Skipped up to `%c'!\n", skip); + +	numlen = getnum(data + i, dlen - i, cmd, PF_INET, term); +	if (!numlen) +		return 0; + +	pr_debug("Match succeded!\n"); +	return 1; +} + +/* Look up to see if we're just after a \n. */ +static int find_nl_seq(uint32_t seq, struct ftp_info *info, int dir) +{ +	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(uint32_t nl_seq, struct ftp_info *info, int dir) +{ +	int i, oldest; + +	/* 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 (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 (before(info->seq_aft_nl[dir][0], info->seq_aft_nl[dir][1])) +			oldest = 0; +		else +			oldest = 1; + +		if (after(nl_seq, info->seq_aft_nl[dir][oldest])) +			info->seq_aft_nl[dir][oldest] = nl_seq; +	} +} + +static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type, +			      char *buffer, size_t buflen, +			      uint32_t addr, uint16_t port) +{ +	switch (type) { +	case NF_CT_FTP_PORT: +	case NF_CT_FTP_PASV: +		return snprintf(buffer, buflen, "%u,%u,%u,%u,%u,%u", +				((unsigned char *)&addr)[0], +				((unsigned char *)&addr)[1], +				((unsigned char *)&addr)[2], +				((unsigned char *)&addr)[3], +				port >> 8, +				port & 0xFF); +	case NF_CT_FTP_EPRT: +		return snprintf(buffer, buflen, "|1|%u.%u.%u.%u|%u|", +				((unsigned char *)&addr)[0], +				((unsigned char *)&addr)[1], +				((unsigned char *)&addr)[2], +				((unsigned char *)&addr)[3], +				port); +	case NF_CT_FTP_EPSV: +		return snprintf(buffer, buflen, "|||%u|", port); +	} + +	return 0; +} + +/* So, this packet has hit the connection tracking matching code. +   Mangle it, and change the expectation to match the new version. */ +static unsigned int nf_nat_ftp(struct pkt_buff *pkt, +			       int dir, +			       int ctinfo, +			       enum nf_ct_ftp_type type, +			       unsigned int matchoff, +			       unsigned int matchlen, +			       struct nf_conntrack *ct, +			       struct nf_expect *exp) +{ +	union nfct_attr_grp_addr newip; +	uint16_t port; +	char buffer[sizeof("|1|255.255.255.255|65535|")]; +	unsigned int buflen; +	const struct nf_conntrack *expected; +	struct nf_conntrack *nat_tuple; +	uint16_t initial_port; + +	pr_debug("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen); + +	/* Connection will come from wherever this packet goes, hence !dir */ +	cthelper_get_addr_dst(ct, !dir, &newip); + +	expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + +	nat_tuple = nfct_new(); +	if (nat_tuple == NULL) +		return NF_ACCEPT; + +	initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + +	nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + +	/* libnetfilter_conntrack needs this */ +	nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); +	nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + +	/* When you see the packet, we need to NAT it the same as the +	 * this one. */ +	nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + +	/* Try to get same port: if not, try to change it. */ +	for (port = ntohs(initial_port); port != 0; port++) { +		int ret; + +		nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); +		nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + +		ret = cthelper_add_expect(exp); +		if (ret == 0) +			break; +		else if (ret != -EBUSY) { +			port = 0; +			break; +		} +	} + +	if (port == 0) +		return NF_DROP; + +	buflen = nf_nat_ftp_fmt_cmd(type, buffer, sizeof(buffer), +					newip.ip, port); +	if (!buflen) +		goto out; + +	if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) +		goto out; + +	return NF_ACCEPT; + +out: +	cthelper_del_expect(exp); +	return NF_DROP; +} + +static int +ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +	      struct myct *myct, uint32_t ctinfo) +{ +	struct tcphdr *th; +	unsigned int dataoff; +	unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */ +	unsigned int datalen; +	unsigned int i; +	int found = 0, ends_in_nl; +	uint32_t seq; +	int ret = NF_ACCEPT; +	struct myct_man cmd; +	union nfct_attr_grp_addr addr; +	union nfct_attr_grp_addr daddr; +	int dir = CTINFO2DIR(ctinfo); +	struct ftp_info *ftp_info = myct->priv_data; +	struct nf_expect *exp = NULL; + +	memset(&cmd, 0, sizeof(struct myct_man)); +	memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + +	/* Until there's been traffic both ways, don't look in packets. */ +	if (ctinfo != IP_CT_ESTABLISHED && +	    ctinfo != IP_CT_ESTABLISHED_REPLY) { +		pr_debug("ftp: Conntrackinfo = %u\n", ctinfo); +		goto out; +	} + +	th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + +	dataoff = protoff + th->doff * 4; +	datalen = pktb_len(pkt) - dataoff; + +	ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 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), ftp_info, dir)) { +		/* Now if this ends in \n, update ftp info. */ +		pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n", +		ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)", +		ftp_info->seq_aft_nl[dir][0], +		ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)", +		ftp_info->seq_aft_nl[dir][1]); +		goto out_update_nl; +	} + +	/* Initialize IP/IPv6 addr to expected address (it's not mentioned +	   in EPSV responses) */ +	cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO); +	nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3); + +	for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { +		found = ftp_find_pattern(pkt, dataoff, datalen, +					 search[dir][i].pattern, +					 search[dir][i].plen, +					 search[dir][i].skip, +					 search[dir][i].term, +					 &matchoff, &matchlen, +					 &cmd, +					 search[dir][i].getnum, +					 dir); +		if (found) break; +	} +	if (found == 0) /* No match */ +		goto out_update_nl; + +	pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", +		 matchlen, pktb_network_header(pkt) + matchoff, +		 matchlen, ntohl(th->seq) + matchoff); + +	/* 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.  */ +	cthelper_get_addr_dst(myct->ct, !dir, &daddr); + +	cthelper_get_addr_src(myct->ct, dir, &addr); + +	/* Update the ftp info */ +	if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) && +	    memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) { +		/* 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. */ +		if (cmd.l3num == PF_INET) { +			pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n", +				 &cmd.u3.ip, &addr); +		} else { +			pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n", +				 cmd.u3.ip6, &addr); +		} +		/* 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; +		} +		memcpy(&daddr, &cmd.u3, sizeof(cmd.u3)); +	} + +	exp = nfexp_new(); +	if (exp == NULL) +		goto out_update_nl; + +	cthelper_get_addr_src(myct->ct, !dir, &addr); + +	if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, IPPROTO_TCP, +				 NULL, &cmd.u.port, 0)) { +		pr_debug("conntrack_ftp: failed to init expectation\n"); +		goto out_update_nl; +	} + +	/* Now, NAT might want to mangle the packet, and register the +	 * (possibly changed) expectation itself. */ +	if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { +		ret = nf_nat_ftp(pkt, dir, ctinfo, search[dir][i].ftptype, +				 matchoff, matchlen, myct->ct, exp); +		goto out_update_nl; +	} + +	/* Can't expect this?  Best to drop packet now. */ +	if (cthelper_add_expect(exp) < 0) { +		pr_debug("conntrack_ftp: cannot add expectation: %s\n", +			strerror(errno)); +		ret = NF_DROP; +		goto out_update_nl; +	} + +out_update_nl: +	if (exp != NULL) +		nfexp_destroy(exp); + +	/* 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, ftp_info, dir); +out: +	return ret; +} + +static struct ctd_helper ftp_helper = { +	.name		= "ftp", +	.l4proto	= IPPROTO_TCP, +	.cb		= ftp_helper_cb, +	.priv_data_len	= sizeof(struct ftp_info), +	.policy		= { +		[0] = { +			.name			= "ftp", +			.expect_max		= 1, +			.expect_timeout		= 300, +		}, +	}, +}; + +void __attribute__ ((constructor)) ftp_init(void); + +void ftp_init(void) +{ +	helper_register(&ftp_helper); +} diff --git a/src/helpers/rpc.c b/src/helpers/rpc.c new file mode 100644 index 0000000..82493c2 --- /dev/null +++ b/src/helpers/rpc.c @@ -0,0 +1,488 @@ +/* + * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Based on: RPC extension for conntrack. + * + * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com> + * + * Original copyright notice: + * + * (C) 2000 by Marcelo Barbosa Lima <marcelo.lima@dcc.unicamp.br> + * (C) 2001 by Rusty Russell <rusty@rustcorp.com.au> + * (C) 2002,2003 by Ian (Larry) Latter <Ian.Latter@mq.edu.au> + * (C) 2004,2005 by David Stes <stes@pandora.be> + * + * 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 "conntrackd.h" +#include "network.h"	/* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include <errno.h> + +#include <rpc/rpc_msg.h> +#include <rpc/pmap_prot.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> + +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_tcp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +/* RFC 1050: RPC: Remote Procedure Call Protocol Specification Version 2 */ +/* RFC 1014: XDR: External Data Representation Standard */ +#define SUPPORTED_RPC_VERSION	2 + +struct rpc_info { +	/* XID */ +	uint32_t xid; +	/* program */ +	uint32_t pm_prog; +	/* program version */ +	uint32_t pm_vers; +	/* transport protocol: TCP|UDP */ +	uint32_t pm_prot; +}; + +/* So, this packet has hit the connection tracking matching code. +   Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_rpc(struct pkt_buff *pkt, int dir, struct nf_expect *exp, +	   uint8_t proto, uint32_t *port_ptr) +{ +	const struct nf_conntrack *expected; +	struct nf_conntrack *nat_tuple; +	uint16_t initial_port, port; + +	expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + +	nat_tuple = nfct_new(); +	if (nat_tuple == NULL) +		return NF_ACCEPT; + +	initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + +	nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + +	/* libnetfilter_conntrack needs this */ +	nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); +	nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, proto); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + +	/* When you see the packet, we need to NAT it the same as the +	 * this one. */ +	nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + +	/* Try to get same port: if not, try to change it. */ +	for (port = ntohs(initial_port); port != 0; port++) { +		int ret; + +		nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); +		nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + +		ret = cthelper_add_expect(exp); +		if (ret == 0) +			break; +		else if (ret != -EBUSY) { +			port = 0; +			break; +		} +	} +	nfct_destroy(nat_tuple); + +	if (port == 0) +		return NF_DROP; + +	*port_ptr = htonl(port); + +	return NF_ACCEPT; +} + +#define OFFSET(o, n)	((o) += n) +#define ROUNDUP(n)	((((n) + 3)/4)*4) + +static int +rpc_call(const uint32_t *data, uint32_t offset, uint32_t datalen, +	 struct rpc_info *rpc_info) +{ +	uint32_t p, r; + +	/* RPC CALL message body */ + +	/* call_body { +	 *	rpcvers +	 *	prog +	 *	vers +	 *	proc +	 *	opaque_auth cred +	 *	opaque_auth verf +	 *	pmap +	 * } +	 * +	 * opaque_auth { +	 *	flavour +	 *	opaque[len] <= MAX_AUTH_BYTES +	 * } +	 */ +	if (datalen < OFFSET(offset, 4*4 + 2*2*4)) { +		pr_debug("RPC CALL: too short packet: %u < %u\n", +			 datalen, offset); +		return -1; +	} +	/* Check rpcversion */ +	p = IXDR_GET_INT32(data); +	if (p != SUPPORTED_RPC_VERSION) { +		pr_debug("RPC CALL: wrong rpcvers %u != %u\n", +			 p, SUPPORTED_RPC_VERSION); +		return -1; +	} +	/* Skip non-portmap requests */ +	p = IXDR_GET_INT32(data); +	if (p != PMAPPROG) { +		pr_debug("RPC CALL: not portmap %u != %lu\n", +			 p, PMAPPROG); +		return -1; +	} +	/* Check portmap version */ +	p = IXDR_GET_INT32(data); +	if (p != PMAPVERS) { +		pr_debug("RPC CALL: wrong portmap version %u != %lu\n", +			 p, PMAPVERS); +		return -1; +	} +	/* Skip non PMAPPROC_GETPORT requests */ +	p = IXDR_GET_INT32(data); +	if (p != PMAPPROC_GETPORT) { +		pr_debug("RPC CALL: not PMAPPROC_GETPORT %u != %lu\n", +			 p, PMAPPROC_GETPORT); +		return -1; +	} +	/* Check and skip credentials */ +	r = IXDR_GET_INT32(data); +	p = IXDR_GET_INT32(data); +	pr_debug("RPC CALL: cred: %u %u (%u, %u)\n", +		 r, p, datalen, offset); +	if (p > MAX_AUTH_BYTES) { +		pr_debug("RPC CALL: invalid sized cred %u > %u\n", +			 p, MAX_AUTH_BYTES); +		return -1; +	} +	r = ROUNDUP(p); +	if (datalen < OFFSET(offset, r)) { +		pr_debug("RPC CALL: too short to carry cred: %u < %u, %u\n", +			 datalen, offset, r); +		return -1; +	} +	data += r/4; +	/* Check and skip verifier */ +	r = IXDR_GET_INT32(data); +	p = IXDR_GET_INT32(data); +	pr_debug("RPC CALL: verf: %u %u (%u, %u)\n", +		 r, p, datalen, offset); +	if (p > MAX_AUTH_BYTES) { +		pr_debug("RPC CALL: invalid sized verf %u > %u\n", +			 p, MAX_AUTH_BYTES); +		return -1; +	} +	r = ROUNDUP(p); +	if (datalen < OFFSET(offset, r)) { +		pr_debug("RPC CALL: too short to carry verf: %u < %u, %u\n", +			 datalen, offset, r); +		return -1; +	} +	data += r/4; +	/* pmap { +	 *	prog +	 *	vers +	 *	prot +	 *	port +	 * } +	 */ +	/* Check CALL size */ +	if (datalen != offset + 4*4) { +		pr_debug("RPC CALL: invalid size to carry pmap: %u != %u\n", +			 datalen, offset + 4*4); +		return -1; +	} +	rpc_info->pm_prog = IXDR_GET_INT32(data); +	rpc_info->pm_vers = IXDR_GET_INT32(data); +	rpc_info->pm_prot = IXDR_GET_INT32(data); +	/* Check supported protocols */ +	if (!(rpc_info->pm_prot == IPPROTO_TCP +	      || rpc_info->pm_prot == IPPROTO_UDP)) { +		pr_debug("RPC CALL: unsupported protocol %u", +			 rpc_info->pm_prot); +		return -1; +	} +	p = IXDR_GET_INT32(data); +	/* Check port: must be zero */ +	if (p != 0) { +		pr_debug("RPC CALL: port is nonzero %u\n", +			 ntohl(p)); +		return -1; +	} +	pr_debug("RPC CALL: processed: xid %u, prog %u, vers %u, prot %u\n", +		 rpc_info->xid, rpc_info->pm_prog, +		 rpc_info->pm_vers, rpc_info->pm_prot); + +	return 0; +} + +static int +rpc_reply(uint32_t *data, uint32_t offset, uint32_t datalen, +	  struct rpc_info *rpc_info, uint32_t **port_ptr) +{ +	uint16_t port; +	uint32_t p, r; + +	/* RPC REPLY message body */ + +	/* reply_body { +	 *	reply_stat +	 *	xdr_union { +	 *		accepted_reply +	 *		rejected_reply +	 *	} +	 * } +	 * accepted_reply { +	 *	opaque_auth verf +	 *	accept_stat +	 *	xdr_union { +	 *		port +	 *		struct mismatch_info +	 *	} +	 * } +	 */ + +	/* Check size: reply status */ +	if (datalen < OFFSET(offset, 4)) { +		pr_debug("RPC REPL: too short, missing rp_stat: %u < %u\n", +			 datalen, offset); +		return -1; +	} +	p = IXDR_GET_INT32(data); +	/* Check accepted request */ +	if (p != MSG_ACCEPTED) { +		pr_debug("RPC REPL: not accepted %u != %u\n", +			 p, MSG_ACCEPTED); +		return -1; +	} +	/* Check and skip verifier */ +	if (datalen < OFFSET(offset, 2*4)) { +		pr_debug("RPC REPL: too short, missing verf: %u < %u\n", +			 datalen, offset); +		return -1; +	} +	r = IXDR_GET_INT32(data); +	p = IXDR_GET_INT32(data); +	pr_debug("RPC REPL: verf: %u %u (%u, %u)\n", +		 r, p, datalen, offset); +	if (p > MAX_AUTH_BYTES) { +		pr_debug("RPC REPL: invalid sized verf %u > %u\n", +			 p, MAX_AUTH_BYTES); +		return -1; +	} +	r = ROUNDUP(p); +	/* verifier + ac_stat + port */ +	if (datalen != OFFSET(offset, r) + 2*4) { +		pr_debug("RPC REPL: invalid size to carry verf and " +			 "success: %u != %u\n", +			 datalen, offset + 2*4); +		return -1; +	} +	data += r/4; +	/* Check success */ +	p = IXDR_GET_INT32(data); +	if (p != SUCCESS) { +		pr_debug("RPC REPL: not success %u != %u\n", +			 p, SUCCESS); +		return -1; +	} +	/* Get port */ +	*port_ptr = data; +	port = IXDR_GET_INT32(data); /* -Wunused-but-set-parameter */ +	if (port == 0) { +		pr_debug("RPC REPL: port is zero\n"); +		return -1; +	} +	pr_debug("RPC REPL: processed: xid %u, prog %u, vers %u, " +		 "prot %u, port %u\n", +		 rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, +		 rpc_info->pm_prot, port); +	return 0; +} + +static int +rpc_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +	      struct myct *myct, uint32_t ctinfo) +{ +	int dir = CTINFO2DIR(ctinfo); +	unsigned int offset = protoff, datalen; +	uint32_t *data, *port_ptr = NULL, xid; +	uint16_t port; +	uint8_t proto = nfct_get_attr_u8(myct->ct, ATTR_L4PROTO); +	enum msg_type rm_dir; +	struct rpc_info *rpc_info = myct->priv_data; +	union nfct_attr_grp_addr addr, daddr; +	struct nf_expect *exp = NULL; +	int ret = NF_ACCEPT; + +	/* Until there's been traffic both ways, don't look into TCP packets. */ +	if (proto == IPPROTO_TCP +	    && ctinfo != IP_CT_ESTABLISHED +	    && ctinfo != IP_CT_ESTABLISHED_REPLY) { +		pr_debug("TCP RPC: Conntrackinfo = %u\n", ctinfo); +		return ret; +	} +	if (proto == IPPROTO_TCP) { +		struct tcphdr *th = +			(struct tcphdr *) (pktb_network_header(pkt) + protoff); +		offset += th->doff * 4; +	} else { +		offset += sizeof(struct udphdr); +	} +	/* Skip broken headers */ +	if (offset % 4) { +		pr_debug("RPC: broken header: offset %u%%4 != 0\n", offset); +		return ret; +	} + +	/* Take into Record Fragment header */ +	if (proto == IPPROTO_TCP) +		offset += 4; + +	datalen = pktb_len(pkt); +	data = (uint32_t *)(pktb_network_header(pkt) + offset); + +	/* rpc_msg { +	 *	xid +	 *	direction +	 *	xdr_union { +	 *		call_body +	 *		reply_body +	 *	} +	 * } +	 */ + +	 /* Check minimal msg size: xid + direction */ +	if (datalen < OFFSET(offset, 2*4)) { +		pr_debug("RPC: too short packet: %u < %u\n", +			 datalen, offset); +		return ret; +	} +	xid = IXDR_GET_INT32(data); +	rm_dir = IXDR_GET_INT32(data); + +	/* Check direction */ +	if (!((rm_dir == CALL && dir == MYCT_DIR_ORIG) +	      || (rm_dir == REPLY && dir == MYCT_DIR_REPL))) { +		pr_debug("RPC: rm_dir != dir %u != %u\n", rm_dir, dir); +		goto out; +	} + +	if (rm_dir == CALL) { +		if (rpc_call(data, offset, datalen, rpc_info) < 0) +			goto out; + +		rpc_info->xid = xid; + +		return ret; +	} else { +		/* Check XID */ +		if (xid != rpc_info->xid) { +			pr_debug("RPC REPL: XID does not match: %u != %u\n", +				 xid, rpc_info->xid); +			goto out; +		} +		if (rpc_reply(data, offset, datalen, rpc_info, &port_ptr) < 0) +			goto out; + +		port = IXDR_GET_INT32(port_ptr); +		port = htons(port); + +		/* 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.  */ +		cthelper_get_addr_dst(myct->ct, !dir, &daddr); +		cthelper_get_addr_src(myct->ct, !dir, &addr); + +		exp = nfexp_new(); +		if (exp == NULL) +			goto out; + +		if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, +					 rpc_info->pm_prot, +					 NULL, &port, NF_CT_EXPECT_PERMANENT)) { +			pr_debug("RPC: failed to init expectation\n"); +			goto out_exp; +		} + +		/* Now, NAT might want to mangle the packet, and register the +		 * (possibly changed) expectation itself. */ +		if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { +			ret = nf_nat_rpc(pkt, dir, exp, rpc_info->pm_prot, +					 port_ptr); +			goto out_exp; +		} + +		/* Can't expect this?  Best to drop packet now. */ +		if (cthelper_add_expect(exp) < 0) { +			pr_debug("RPC: cannot add expectation: %s\n", +				 strerror(errno)); +			ret = NF_DROP; +		} +	} + +out_exp: +	nfexp_destroy(exp); +out: +	rpc_info->xid = 0; +	return ret; +} + +static struct ctd_helper rpc_helper_tcp = { +	.name		= "rpc", +	.l4proto	= IPPROTO_TCP, +	.cb		= rpc_helper_cb, +	.priv_data_len	= sizeof(struct rpc_info), +	.policy		= { +		{ +			.name			= "rpc", +			.expect_max		= 1, +			.expect_timeout		= 300, +		}, +	}, +}; + +static struct ctd_helper rpc_helper_udp = { +	.name		= "rpc", +	.l4proto	= IPPROTO_UDP, +	.cb		= rpc_helper_cb, +	.priv_data_len	= sizeof(struct rpc_info), +	.policy		= { +		{ +			.name			= "rpc", +			.expect_max		= 1, +			.expect_timeout		= 300, +		}, +	}, +}; + +void __attribute__ ((constructor)) rpc_init(void); + +void rpc_init(void) +{ +	helper_register(&rpc_helper_tcp); +	helper_register(&rpc_helper_udp); +} diff --git a/src/helpers/sane.c b/src/helpers/sane.c new file mode 100644 index 0000000..c30f4ba --- /dev/null +++ b/src/helpers/sane.c @@ -0,0 +1,173 @@ +/* + * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Port this helper to userspace. + */ + +/* SANE connection tracking helper + * (SANE = Scanner Access Now Easy) + * For documentation about the SANE network protocol see + * http://www.sane-project.org/html/doc015.html + */ + +/* + * Copyright (C) 2007 Red Hat, Inc. + * Author: Michal Schmidt <mschmidt@redhat.com> + * Based on the FTP conntrack helper (net/netfilter/nf_conntrack_ftp.c): + *  (C) 1999-2001 Paul `Rusty' Russell + *  (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> + *  (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org> + *  (C) 2003 Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp> + * + * 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 "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include <errno.h> +#include <netinet/ip.h> +#define _GNU_SOURCE +#include <netinet/tcp.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_tcp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +enum sane_state { +	SANE_STATE_NORMAL, +	SANE_STATE_START_REQUESTED, +}; + +struct sane_request { +	uint32_t RPC_code; +#define SANE_NET_START      7   /* RPC code */ + +	uint32_t handle; +}; + +struct sane_reply_net_start { +	uint32_t status; +#define SANE_STATUS_SUCCESS 0 + +	uint16_t zero; +	uint16_t port; +	/* other fields aren't interesting for conntrack */ +}; + +struct nf_ct_sane_master { +	enum sane_state state; +}; + +static int +sane_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +		 struct myct *myct, uint32_t ctinfo) +{ +	unsigned int dataoff, datalen; +	const struct tcphdr *th; +	void *sb_ptr; +	int ret = NF_ACCEPT; +	int dir = CTINFO2DIR(ctinfo); +	struct nf_ct_sane_master *ct_sane_info = myct->priv_data; +	struct nf_expect *exp; +	struct sane_request *req; +	struct sane_reply_net_start *reply; +	union nfct_attr_grp_addr saddr; +	union nfct_attr_grp_addr daddr; + +	/* Until there's been traffic both ways, don't look in packets. */ +	if (ctinfo != IP_CT_ESTABLISHED && +	    ctinfo != IP_CT_ESTABLISHED_REPLY) +		return NF_ACCEPT; + +	th = (struct tcphdr *)(pktb_network_header(pkt) + protoff); + +	/* No data? */ +	dataoff = protoff + th->doff * 4; +	if (dataoff >= pktb_len(pkt)) +		return NF_ACCEPT; + +	datalen = pktb_len(pkt) - dataoff; + +	sb_ptr = pktb_network_header(pkt) + dataoff; + +	if (dir == MYCT_DIR_ORIG) { +		if (datalen != sizeof(struct sane_request)) +			goto out; + +		req = sb_ptr; +		if (req->RPC_code != htonl(SANE_NET_START)) { +			/* Not an interesting command */ +			ct_sane_info->state = SANE_STATE_NORMAL; +			goto out; +		} + +		/* We're interested in the next reply */ +		ct_sane_info->state = SANE_STATE_START_REQUESTED; +		goto out; +	} + +	/* Is it a reply to an uninteresting command? */ +	if (ct_sane_info->state != SANE_STATE_START_REQUESTED) +		goto out; + +	/* It's a reply to SANE_NET_START. */ +	ct_sane_info->state = SANE_STATE_NORMAL; + +	if (datalen < sizeof(struct sane_reply_net_start)) { +		pr_debug("nf_ct_sane: NET_START reply too short\n"); +		goto out; +	} + +	reply = sb_ptr; +	if (reply->status != htonl(SANE_STATUS_SUCCESS)) { +		/* saned refused the command */ +		pr_debug("nf_ct_sane: unsuccessful SANE_STATUS = %u\n", +			 ntohl(reply->status)); +		goto out; +	} + +	/* Invalid saned reply? Ignore it. */ +	if (reply->zero != 0) +		goto out; + +	exp = nfexp_new(); +	if (exp == NULL) +		return NF_DROP; + +	cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr); +	cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr); + +	if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr, +				 IPPROTO_TCP, NULL, &reply->port, 0)) { +		nfexp_destroy(exp); +		return NF_DROP; +	} +	myct->exp = exp; +out: +	return ret; +} + +static struct ctd_helper sane_helper = { +	.name		= "sane", +	.l4proto	= IPPROTO_TCP, +	.priv_data_len	= sizeof(struct nf_ct_sane_master), +	.cb		= sane_helper_cb, +	.policy		= { +		[0] = { +			.name			= "sane", +			.expect_max		= 1, +			.expect_timeout		= 5 * 60, +		}, +	}, +}; + +static void __attribute__ ((constructor)) sane_init(void) +{ +	helper_register(&sane_helper); +} diff --git a/src/helpers/ssdp.c b/src/helpers/ssdp.c new file mode 100644 index 0000000..bc41087 --- /dev/null +++ b/src/helpers/ssdp.c @@ -0,0 +1,134 @@ +/* + * SSDP connection tracking helper + * (SSDP = Simple Service Discovery Protocol) + * For documentation about SSDP see + * http://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol + * + * Copyright (C) 2014 Ashley Hughes <ashley.hughes@blueyonder.co.uk> + * Based on the SSDP conntrack helper (nf_conntrack_ssdp.c), + * :http://marc.info/?t=132945775100001&r=1&w=2 + *  (C) 2012 Ian Pilcher <arequipeno@gmail.com> + * + * 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 "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include <errno.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_tcp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +#define SSDP_MCAST_ADDR		"239.255.255.250" +#define UPNP_MCAST_LL_ADDR	"FF02::C" /* link-local */ +#define UPNP_MCAST_SL_ADDR	"FF05::C" /* site-local */ + +#define SSDP_M_SEARCH		"M-SEARCH" +#define SSDP_M_SEARCH_SIZE	(sizeof SSDP_M_SEARCH - 1) + +static int ssdp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +			  struct myct *myct, uint32_t ctinfo) +{ +	int ret = NF_ACCEPT; +	union nfct_attr_grp_addr daddr, saddr, taddr; +	struct iphdr *net_hdr = (struct iphdr *)pktb_network_header(pkt); +	int good_packet = 0; +	struct nf_expect *exp; +	uint16_t port; +	unsigned int dataoff; +	void *sb_ptr; + +	cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr); +	switch (nfct_get_attr_u8(myct->ct, ATTR_L3PROTO)) { +	case AF_INET: +		inet_pton(AF_INET, SSDP_MCAST_ADDR, &(taddr.ip)); +		if (daddr.ip == taddr.ip) +			good_packet = 1; +		break; +	case AF_INET6: +		inet_pton(AF_INET6, UPNP_MCAST_LL_ADDR, &(taddr.ip6)); +		if (daddr.ip6[0] == taddr.ip6[0] && +		    daddr.ip6[1] == taddr.ip6[1] && +		    daddr.ip6[2] == taddr.ip6[2] && +		    daddr.ip6[3] == taddr.ip6[3]) { +			good_packet = 1; +			break; +		} +		inet_pton(AF_INET6, UPNP_MCAST_SL_ADDR, &(taddr.ip6)); +		if (daddr.ip6[0] == taddr.ip6[0] && +		    daddr.ip6[1] == taddr.ip6[1] && +		    daddr.ip6[2] == taddr.ip6[2] && +		    daddr.ip6[3] == taddr.ip6[3]) { +			good_packet = 1; +			break; +		} +		break; +	default: +		break; +	} + +	if (!good_packet) { +		pr_debug("ssdp_help: destination address not multicast; ignoring\n"); +		return NF_ACCEPT; +	} + +	/* No data? Ignore */ +	dataoff = net_hdr->ihl*4 + sizeof(struct udphdr); +	if (dataoff >= pktb_len(pkt)) { +		pr_debug("ssdp_help: UDP payload too small for M-SEARCH; ignoring\n"); +		return NF_ACCEPT; +	} + +	sb_ptr = pktb_network_header(pkt) + dataoff; + +	if (memcmp(sb_ptr, SSDP_M_SEARCH, SSDP_M_SEARCH_SIZE) != 0) { +		pr_debug("ssdp_help: UDP payload does not begin with 'M-SEARCH'; ignoring\n"); +		return NF_ACCEPT; +	} + +	cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr); +	cthelper_get_port_src(myct->ct, MYCT_DIR_ORIG, &port); + +	exp = nfexp_new(); +	if (exp == NULL) +		return NF_DROP; + +	if (cthelper_expect_init(exp, myct->ct, 0, NULL, &saddr, +				 IPPROTO_UDP, NULL, &port, +				 NF_CT_EXPECT_PERMANENT)) { +		nfexp_destroy(exp); +		return NF_DROP; +	} +	myct->exp = exp; + +	return ret; +} + +static struct ctd_helper ssdp_helper = { +	.name		= "ssdp", +	.l4proto	= IPPROTO_UDP, +	.priv_data_len	= 0, +	.cb		= ssdp_helper_cb, +	.policy		= { +		[0] = { +			.name		= "ssdp", +			.expect_max	= 1, +			.expect_timeout	= 5 * 60, +		}, +	}, +}; + +static void __attribute__ ((constructor)) ssdp_init(void) +{ +	helper_register(&ssdp_helper); +} diff --git a/src/helpers/tftp.c b/src/helpers/tftp.c new file mode 100644 index 0000000..45591c6 --- /dev/null +++ b/src/helpers/tftp.c @@ -0,0 +1,138 @@ +/* + * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Adapted from: + * + * (C) 2001-2002 Magnus Boden <mb@ozaba.mine.nu> + * (C) 2006-2012 Patrick McHardy <kaber@trash.net> + * 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 "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include <errno.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/udp.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_udp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +struct tftphdr { +	uint16_t opcode; +}; + +#define TFTP_OPCODE_READ	1 +#define TFTP_OPCODE_WRITE	2 +#define TFTP_OPCODE_DATA	3 +#define TFTP_OPCODE_ACK		4 +#define TFTP_OPCODE_ERROR	5 + +static unsigned int nat_tftp(struct pkt_buff *pkt, uint32_t ctinfo, +			     struct nf_conntrack *ct, struct nf_expect *exp) +{ +	struct nf_conntrack *nat_tuple; +	static uint32_t zero[4] = { 0, 0, 0, 0 }; + +	nat_tuple = nfct_new(); +	if (nat_tuple == NULL) +		return NF_ACCEPT; + +	switch (nfct_get_attr_u8(ct, ATTR_L3PROTO)) { +	case AF_INET: +		nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); +		nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); +		nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); +		break; +	case AF_INET6: +		nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET6); +		nfct_set_attr(nat_tuple, ATTR_IPV6_SRC, &zero); +		nfct_set_attr(nat_tuple, ATTR_IPV6_DST, &zero); +		break; +	} +	nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_UDP); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, +			  nfct_get_attr_u16(ct, ATTR_PORT_SRC)); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + +	nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, MYCT_DIR_REPL); +	nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); +	nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + +	return NF_ACCEPT; +} + +static int +tftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +	       struct myct *myct, uint32_t ctinfo) +{ +	const struct tftphdr *tfh; +	struct nf_expect *exp; +	unsigned int ret = NF_ACCEPT; +	union nfct_attr_grp_addr saddr, daddr; +	uint16_t dport; + +	tfh = (struct tftphdr *)(pktb_network_header(pkt) + protoff + sizeof(struct udphdr)); + +	switch (ntohs(tfh->opcode)) { +	case TFTP_OPCODE_READ: +	case TFTP_OPCODE_WRITE: +		/* RRQ and WRQ works the same way */ +		exp = nfexp_new(); +		if (exp == NULL) { +			pr_debug("cannot alloc expectation\n"); +			return NF_DROP; +		} + +		cthelper_get_addr_src(myct->ct, MYCT_DIR_REPL, &saddr); +		cthelper_get_addr_dst(myct->ct, MYCT_DIR_REPL, &daddr); +		cthelper_get_port_dst(myct->ct, MYCT_DIR_REPL, &dport); + +		if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr, +					 IPPROTO_UDP, NULL, &dport, 0)) { +			nfexp_destroy(exp); +			return NF_DROP; +		} + +		if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) +			ret = nat_tftp(pkt, ctinfo, myct->ct, exp); + +		myct->exp = exp; +		break; +	case TFTP_OPCODE_DATA: +	case TFTP_OPCODE_ACK: +		pr_debug("Data/ACK opcode\n"); +		break; +	case TFTP_OPCODE_ERROR: +		pr_debug("Error opcode\n"); +		break; +	default: +		pr_debug("Unknown opcode\n"); +	} +	return ret; +} + +static struct ctd_helper tftp_helper = { +	.name		= "tftp", +	.l4proto	= IPPROTO_UDP, +	.cb		= tftp_helper_cb, +	.policy		= { +		[0] = { +			.name			= "tftp", +			.expect_max		= 1, +			.expect_timeout		= 5 * 60, +		}, +	}, +}; + +static void __attribute__ ((constructor)) tftp_init(void) +{ +	helper_register(&tftp_helper); +} diff --git a/src/helpers/tns.c b/src/helpers/tns.c new file mode 100644 index 0000000..2b4fed4 --- /dev/null +++ b/src/helpers/tns.c @@ -0,0 +1,408 @@ +/* + * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * Sponsored by Vyatta Inc. <http://www.vyatta.com> + * + * 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 "conntrackd.h" +#include "network.h"	/* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include <ctype.h>	/* for isdigit */ +#include <errno.h> + +#define _GNU_SOURCE +#include <netinet/tcp.h> + +#include <libmnl/libmnl.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_queue/libnetfilter_queue_tcp.h> +#include <libnetfilter_queue/pktbuff.h> +#include <linux/netfilter.h> + +/* TNS SQL*Net Version 2 */ +enum tns_types { +       TNS_TYPE_CONNECT        = 1, +       TNS_TYPE_ACCEPT	       = 2, +       TNS_TYPE_ACK	       = 3, +       TNS_TYPE_REFUSE	       = 4, +       TNS_TYPE_REDIRECT       = 5, +       TNS_TYPE_DATA	       = 6, +       TNS_TYPE_NULL	       = 7, +       TNS_TYPE_ABORT	       = 9, +       TNS_TYPE_RESEND	       = 11, +       TNS_TYPE_MARKER	       = 12, +       TNS_TYPE_ATTENTION      = 13, +       TNS_TYPE_CONTROL        = 14, +       TNS_TYPE_MAX	       = 19, +}; + +struct tns_header { +       uint16_t len; +       uint16_t csum; +       uint8_t type; +       uint8_t reserved; +       uint16_t header_csum; +}; + +struct tns_redirect { +       uint16_t data_len; +}; + +struct tns_info { +       /* Scan next DATA|REDIRECT packet */ +       bool parse; +}; + +static int try_number(const char *data, size_t dlen, uint32_t array[], +		      int array_size, char sep, char term) +{ +	uint32_t len; +	int i; + +	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 { +			/* Skip spaces. */ +			if (*data == ' ') +				continue; +			/* Unexpected character; true if it's the +			   terminator and we're finished. */ +			if (*data == term && i == array_size - 1) +				return len; +			pr_debug("Char %u (got %u nums) `%c' unexpected\n", +				 len, i, *data); +			return 0; +		} +	} +	pr_debug("Failed to fill %u numbers separated by %c\n", +		 array_size, sep); +	return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, size_t dlen, char delim, +		    struct myct_man *cmd) +{ +	uint16_t tmp_port = 0; +	uint32_t i; + +	for (i = 0; i < dlen; i++) { +		/* Finished? */ +		if (data[i] == delim) { +			if (tmp_port == 0) +				break; +			cmd->u.port = htons(tmp_port); +			pr_debug("get_port: return %d\n", tmp_port); +			return i + 1; +		} +		else if (data[i] >= '0' && data[i] <= '9') +			tmp_port = tmp_port*10 + data[i] - '0'; +		else if (data[i] == ' ') /* Skip spaces */ +			continue; +		else { /* Some other crap */ +			pr_debug("get_port: invalid char `%c'\n", data[i]); +			break; +		} +	} +	return 0; +} + +/* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ +/* FIXME: handle hostnames */ + +/* Returns 0, or length of port number */ +static unsigned int +find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, +	     struct myct_man *cmd, unsigned int *numoff) +{ +	const char *data = (const char *)pktb_network_header(pkt) + dataoff +						+ sizeof(struct tns_header); +	int length, offset, ret; +	uint32_t array[4]; +	const char *p, *start; + +	p = strstr(data, "("); +	if (!p) +		return 0; + +	p = strstr(p+1, "HOST="); +	if (!p) { +		pr_debug("HOST= not found\n"); +		return 0; +	} + +	start = p + strlen("HOST="); +	offset = (int)(p - data) + strlen("HOST="); +	*numoff = offset + sizeof(struct tns_header); +	data += offset; + +	length = try_number(data, dlen - offset, array, 4, '.', ')'); +	if (length == 0) +		return 0; + +	cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | +			   (array[2] << 8) | array[3]); + +	p = strstr(data+length, "("); +	if (!p) +		return 0; + +	p = strstr(p, "PORT="); +	if (!p) { +		pr_debug("PORT= not found\n"); +		return 0; +	} + +	p += strlen("PORT="); +	ret = get_port(p, dlen - offset - length, ')', cmd); +	if (ret == 0) +		return 0; + +	p += ret; +	return (int)(p - start); +} + +static inline uint16_t +nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) +{ +	uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; + +	return htons(l); +} + +/* So, this packet has hit the connection tracking matching code. +   Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, +	   struct nf_conntrack *ct, int dir, +	   unsigned int matchoff, unsigned int matchlen) +{ +	union nfct_attr_grp_addr newip; +	char buffer[sizeof("255.255.255.255)(PORT=65535)")]; +	unsigned int buflen; +	const struct nf_conntrack *expected; +	struct nf_conntrack *nat_tuple; +	uint16_t initial_port, port; + +	/* Connection will come from wherever this packet goes, hence !dir */ +	cthelper_get_addr_dst(ct, !dir, &newip); + +	expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + +	nat_tuple = nfct_new(); +	if (nat_tuple == NULL) +		return NF_ACCEPT; + +	initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + +	nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + +	/* libnetfilter_conntrack needs this */ +	nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); +	nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); +	nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); +	nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + +	/* When you see the packet, we need to NAT it the same as the +	 * this one. */ +	nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + +	/* Try to get same port: if not, try to change it. */ +	for (port = ntohs(initial_port); port != 0; port++) { +		int ret; + +		nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); +		nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + +		ret = cthelper_add_expect(exp); +		if (ret == 0) +			break; +		else if (ret != -EBUSY) { +			port = 0; +			break; +		} +	} +	nfct_destroy(nat_tuple); + +	if (port == 0) +		return NF_DROP; + +	buflen = snprintf(buffer, sizeof(buffer), +				"%u.%u.%u.%u)(PORT=%u)", +                                ((unsigned char *)&newip.ip)[0], +                                ((unsigned char *)&newip.ip)[1], +                                ((unsigned char *)&newip.ip)[2], +                                ((unsigned char *)&newip.ip)[3], port); +	if (!buflen) +		goto out; + +	if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) +		goto out; + +	if (buflen != matchlen) { +		/* FIXME: recalculate checksum */ +		tns->csum = 0; +		tns->header_csum = 0; + +		tns->len = nton(tns->len,  matchlen, buflen); +		if (tns->type == TNS_TYPE_REDIRECT) { +			struct tns_redirect *r; + +			r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); + +			r->data_len = nton(r->data_len, matchlen, buflen); +		} +	} + +	return NF_ACCEPT; + +out: +	cthelper_del_expect(exp); +	return NF_DROP; +} + +static int +tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, +	      struct myct *myct, uint32_t ctinfo) +{ +	struct tcphdr *th; +	struct tns_header *tns; +	int dir = CTINFO2DIR(ctinfo); +	unsigned int dataoff, datalen, numoff = 0, numlen; +	struct tns_info *tns_info = myct->priv_data; +	union nfct_attr_grp_addr addr; +	struct nf_expect *exp = NULL; +	struct myct_man cmd; +	int ret = NF_ACCEPT; + +	memset(&cmd, 0, sizeof(struct myct_man)); +	memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + +	/* Until there's been traffic both ways, don't look into TCP packets. */ +	if (ctinfo != IP_CT_ESTABLISHED +	    && ctinfo != IP_CT_ESTABLISHED_REPLY) { +		pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); +		goto out; +	} +	/* Parse server direction only */ +	if (dir != MYCT_DIR_REPL) { +		pr_debug("TNS: skip client direction\n"); +		goto out; +	} + +	th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + +	dataoff = protoff + th->doff * 4; +	datalen = pktb_len(pkt); + +	if (datalen < sizeof(struct tns_header)) { +		pr_debug("TNS: skip packet with short header\n"); +		goto out; +	} + +	tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); + +	if (tns->type == TNS_TYPE_REDIRECT) { +		struct tns_redirect *redirect; + +		dataoff += sizeof(struct tns_header); +		datalen -= sizeof(struct tns_header); +		redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); +		tns_info->parse = false; + +		if (ntohs(redirect->data_len) == 0) { +			tns_info->parse = true; +			goto out; +		} +		goto parse; +	} + +	/* Parse only the very next DATA packet */ +	if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { +		tns_info->parse = false; +		goto out; +	} +parse: +	numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); +	tns_info->parse = false; +	if (!numlen) +		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.  */ +	cthelper_get_addr_src(myct->ct, !dir, &addr); + +	exp = nfexp_new(); +	if (exp == NULL) +		goto out; + +	if (cthelper_expect_init(exp, myct->ct, 0, +				 &addr, &cmd.u3, +				 IPPROTO_TCP, +				 NULL, &cmd.u.port, 0)) { +		pr_debug("TNS: failed to init expectation\n"); +		goto out_exp; +	} + +	/* Now, NAT might want to mangle the packet, and register the +	 * (possibly changed) expectation itself. +	 */ +	if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { +		ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, +				numoff + sizeof(struct tns_header), numlen); +		goto out_exp; +	} + +	/* Can't expect this?  Best to drop packet now. */ +	if (cthelper_add_expect(exp) < 0) { +		pr_debug("TNS: cannot add expectation: %s\n", +			 strerror(errno)); +		ret = NF_DROP; +		goto out_exp; +	} +	goto out; + +out_exp: +	nfexp_destroy(exp); +out: +	return ret; +} + +static struct ctd_helper tns_helper = { +	.name		= "tns", +	.l4proto	= IPPROTO_TCP, +	.cb		= tns_helper_cb, +	.priv_data_len	= sizeof(struct tns_info), +	.policy		= { +		[0] = { +			.name		= "tns", +			.expect_max	= 1, +			.expect_timeout	= 300, +		}, +	}, +}; + +void __attribute__ ((constructor)) tns_init(void); + +void tns_init(void) +{ +	helper_register(&tns_helper); +}  | 
