diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 24 | ||||
| -rw-r--r-- | src/cthelper.c | 521 | ||||
| -rw-r--r-- | src/expect.c | 212 | ||||
| -rw-r--r-- | src/helpers.c | 76 | ||||
| -rw-r--r-- | src/helpers/Makefile.am | 9 | ||||
| -rw-r--r-- | src/helpers/ftp.c | 599 | ||||
| -rw-r--r-- | src/main.c | 3 | ||||
| -rw-r--r-- | src/nfct-extensions/helper.c | 619 | ||||
| -rw-r--r-- | src/nfct.c | 6 | ||||
| -rw-r--r-- | src/read_config_lex.l | 5 | ||||
| -rw-r--r-- | src/read_config_yy.y | 200 | ||||
| -rw-r--r-- | src/run.c | 11 | ||||
| -rw-r--r-- | src/stack.c | 56 | ||||
| -rw-r--r-- | src/utils.c | 243 | 
14 files changed, 2578 insertions, 6 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am index bbea176..0b047e9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,7 @@  include $(top_srcdir)/Make_global.am +SUBDIRS = helpers +  AM_YFLAGS = -d  CLEANFILES = read_config_yy.c read_config_lex.c @@ -10,17 +12,24 @@ conntrack_SOURCES = conntrack.c  conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS}  nfct_SOURCES = nfct.c			\ -	       nfct-extensions/timeout.c +	       helpers.c			\ +	       nfct-extensions/timeout.c	\ +	       nfct-extensions/helper.c +  nfct_LDADD = ${LIBMNL_LIBS} 			\  	     ${LIBNETFILTER_CONNTRACK_LIBS}	\ -	     ${LIBNETFILTER_CTTIMEOUT_LIBS} +	     ${LIBNETFILTER_CTTIMEOUT_LIBS}	\ +	     ${LIBNETFILTER_CTHELPER_LIBS}	\ +	     ${libdl_LIBS} + +nfct_LDFLAGS = -export-dynamic  conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \  		    local.c log.c mcast.c udp.c netlink.c vector.c \  		    filter.c fds.c event.c process.c origin.c date.c \  		    cache.c cache-ct.c cache-exp.c \  		    cache_timer.c \ -		    ctnl.c \ +		    ctnl.c cthelper.c \  		    sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \  		    traffic_stats.c stats-mode.c \  		    network.c cidr.c \ @@ -29,11 +38,16 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \  		    tcp.c channel_tcp.c \  		    external_cache.c external_inject.c \  		    internal_cache.c internal_bypass.c \ -		    read_config_yy.y read_config_lex.l +		    read_config_yy.y read_config_lex.l \ +		    stack.c helpers.c utils.c expect.c  # yacc and lex generate dirty code  read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls -conntrackd_LDADD = ${LIBNETFILTER_CONNTRACK_LIBS} +conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \ +		   ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \ +		   ${libdl_LIBS} + +conntrackd_LDFLAGS = -export-dynamic  EXTRA_DIST = read_config_yy.h diff --git a/src/cthelper.c b/src/cthelper.c new file mode 100644 index 0000000..7624dc0 --- /dev/null +++ b/src/cthelper.c @@ -0,0 +1,521 @@ +/* + * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include "conntrackd.h" +#include "log.h" +#include "fds.h" +#include "helper.h" + +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <errno.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <net/ethernet.h> + +#ifndef __aligned_be64 +#define __aligned_be64 unsigned long long __attribute__((aligned(8))) +#endif + +#include <linux/netfilter/nfnetlink_queue.h> + +#include <libmnl/libmnl.h> +#include <libnetfilter_queue/libnetfilter_queue.h> +#include <libnetfilter_cthelper/libnetfilter_cthelper.h> +#include <linux/netfilter.h> +#include <libnetfilter_queue/pktbuff.h> + +void cthelper_kill(void) +{ +	mnl_socket_close(STATE_CTH(nl)); +	free(state.cthelper); +} + +int cthelper_local(int fd, int type, void *data) +{ +	/* No services to obtain information on helpers yet, sorry */ +	return LOCAL_RET_OK; +} + +static struct nlmsghdr * +nfq_build_header(char *buf, int type, uint32_t queue_num) +{ +	struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); +	nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type; +	nlh->nlmsg_flags = NLM_F_REQUEST; + +	struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); +	nfg->nfgen_family = AF_UNSPEC; +	nfg->version = NFNETLINK_V0; +	nfg->res_id = htons(queue_num); + +	return nlh; +} + +static int +pkt_get(void *pkt, uint32_t pktlen, uint16_t proto, uint32_t *protoff) +{ +	switch(proto) { +	case ETHERTYPE_IP: { +		struct iphdr *ip = (struct iphdr *) pkt; + +		/* No room for IPv4 header. */ +		if (pktlen < sizeof(struct iphdr)) { +			dlog(LOG_ERR, "no room for IPv4 header"); +			return -1; +		} + +		/* this is not IPv4, skip. */ +		if (ip->version != 4) { +			dlog(LOG_ERR, "not IPv4, skipping"); +			return -1; +		} + +		*protoff = 4 * ip->ihl; + +		switch (ip->protocol) { +		case IPPROTO_TCP: { +			struct tcphdr *tcph = +				(struct tcphdr *) ((char *)pkt + *protoff); + +			/* No room for IPv4 header plus TCP header. */ +			if (pktlen < *protoff + sizeof(struct tcphdr) +			    || pktlen < *protoff + tcph->doff * 4) { +				dlog(LOG_ERR, "no room for IPv4 + TCP header, skip"); +				return -1; +			} +			return 0; +		} +		case IPPROTO_UDP: +			/* No room for IPv4 header plus UDP header. */ +			if (pktlen < *protoff + sizeof(struct udphdr)) { +				dlog(LOG_ERR, "no room for IPv4 + UDP header, skip"); +				return -1; +			} +			return 0; +		default: +			dlog(LOG_ERR, "not TCP/UDP, skipping"); +			return -1; +		} +		break; +	} +	case ETHERTYPE_IPV6: +		dlog(LOG_ERR, "no IPv6 support sorry"); +		return 0; +	default: +		/* Unknown layer 3 protocol. */ +		dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto); +		return -1; +	} +	return 0; +} + +static int +pkt_verdict_issue(struct ctd_helper_instance *cur, struct myct *myct, +		  uint16_t queue_num, uint32_t id, uint32_t verdict) +{ +	struct nlmsghdr *nlh; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlattr *nest; + +	nlh = nfq_build_header(buf, NFQNL_MSG_VERDICT, queue_num); + +	/* save private data and send it back to kernel-space. */ +	nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data, +			cur->helper->priv_data_len); + +	nfq_nlmsg_verdict_build(nlh, id, verdict); + +	nest = mnl_attr_nest_start(nlh, NFQA_CT); +	if (nest == NULL) +		return -1; + +	nfct_nlmsg_build(nlh, myct->ct); +	mnl_attr_nest_end(nlh, nest); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); +		return -1; +	} + +	return 0; +} + +static int +pkt_verdict_error(uint16_t queue_num, uint32_t id) +{ +	struct nlmsghdr *nlh; +	char buf[MNL_SOCKET_BUFFER_SIZE]; + +	nlh = nfq_build_header(buf, NFQNL_MSG_VERDICT, queue_num); +	nfq_nlmsg_verdict_build(nlh, id, NF_ACCEPT); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); +		return -1; +	} +	return 0; +} + +static struct ctd_helper_instance * +helper_run(void *pkt, uint32_t pktlen, uint32_t protoff, +	   struct myct *myct, uint32_t ctinfo, uint32_t queue_num, +	   int *verdict) +{ +	struct ctd_helper_instance *cur, *helper = NULL; + +	list_for_each_entry(cur, &CONFIG(cthelper).list, head) { +		if (cur->queue_num == queue_num) { +			const void *priv_data; +			struct pkt_buff *pktb; + +			/* retrieve helper private data. */ +			priv_data = nfct_get_attr(myct->ct, ATTR_HELPER_INFO); +			if (priv_data != NULL) { +				myct->priv_data = +					calloc(1, cur->helper->priv_data_len); + +				if (myct->priv_data == NULL) +					continue; + +				memcpy(myct->priv_data, priv_data, +					cur->helper->priv_data_len); +			} + +			/* XXX: 256 bytes for extra allocation for all mangling +			 * we do in helpers. +			 */ +			pktb = pktb_alloc(AF_INET, pkt, pktlen, 256); +			if (pktb == NULL) +				break; + +			*verdict = cur->helper->cb(pktb, protoff, myct, ctinfo); + +			pktb_free(pktb); + +			helper = cur; +			break; +		} +	} +	return helper; +} + +static int nfq_queue_cb(const struct nlmsghdr *nlh, void *data) +{ +	struct nfqnl_msg_packet_hdr *ph = NULL; +	struct nlattr *attr[NFQA_MAX+1] = {}; +	struct nfgenmsg *nfg; +	uint8_t *pkt; +	uint16_t l3num; +	uint32_t id, ctinfo, queue_num = 0, protoff = 0, pktlen; +	struct nf_conntrack *ct = NULL; +	struct myct *myct; +	struct ctd_helper_instance *helper; +	int verdict = NF_ACCEPT; + +	if (nfq_nlmsg_parse(nlh, attr) < 0) { +		dlog(LOG_ERR, "problems parsing message from kernel"); +		return MNL_CB_ERROR; +	} + +	ph = (struct nfqnl_msg_packet_hdr *) +		mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); +	if (ph == NULL) { +		dlog(LOG_ERR, "problems retrieving metaheader"); +		return MNL_CB_ERROR; +	} + +	id = ntohl(ph->packet_id); + +	if (!attr[NFQA_PAYLOAD]) { +		dlog(LOG_ERR, "packet with no payload"); +		goto err; +	} +	if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) { +		dlog(LOG_ERR, "no CT attached to this packet"); +		goto err; +	} + +	pkt = mnl_attr_get_payload(attr[NFQA_PAYLOAD]); +	pktlen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]); + +	nfg = mnl_nlmsg_get_payload(nlh); +	l3num = nfg->nfgen_family; +	queue_num = ntohs(nfg->res_id); + +	if (pkt_get(pkt, pktlen, ntohs(ph->hw_protocol), &protoff)) +		goto err; + +	ct = nfct_new(); +	if (ct == NULL) +		goto err; + +	if (nfct_payload_parse(mnl_attr_get_payload(attr[NFQA_CT]), +			       mnl_attr_get_payload_len(attr[NFQA_CT]), +			       l3num, ct) < 0) { +		dlog(LOG_ERR, "cannot convert message to CT"); +		goto err; +	} + +	myct = calloc(1, sizeof(struct myct)); +	if (myct == NULL) +		goto err; + +	myct->ct = ct; +	ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO])); + +	/* Misconfiguration: if no helper found, accept the packet. */ +	helper = helper_run(pkt, pktlen, protoff, myct, ctinfo, queue_num, +			    &verdict); +	if (!helper) +		goto err; + +	if (pkt_verdict_issue(helper, myct, queue_num, id, verdict) < 0) +		goto err; + +	if (ct != NULL) +		nfct_destroy(ct); +	if (myct && myct->priv_data != NULL) +		free(myct->priv_data); +	if (myct != NULL) +		free(myct); + +	return MNL_CB_OK; +err: +	/* In case of error, we don't want to disrupt traffic. We accept all. +	 * This is connection tracking after all. The policy is not to drop +	 * packet unless we enter some inconsistent state. +	 */ +	pkt_verdict_error(queue_num, id); + +	if (ct != NULL) +		nfct_destroy(ct); + +	return MNL_CB_OK; +} + +static void nfq_cb(void *data) +{ +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	int ret; + +	ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); +	if (ret == -1) { +		dlog(LOG_ERR, "failed to receive message: %s", strerror(errno)); +		return; +	} + +	ret = mnl_cb_run(buf, ret, 0, STATE_CTH(portid), nfq_queue_cb, NULL); +	if (ret < 0){ +		dlog(LOG_ERR, "failed to process message"); +		return; +	} +} + +static int cthelper_setup(struct ctd_helper_instance *cur) +{ +	struct nfct_helper *t; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t seq; +	int j, ret; + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		dlog(LOG_ERR, "cannot allocate object for helper"); +		return -1; +	} + +	nfct_helper_attr_set(t, NFCTH_ATTR_NAME, cur->helper->name); +	nfct_helper_attr_set_u32(t, NFCTH_ATTR_QUEUE_NUM, cur->queue_num); +	nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, cur->l3proto); +	nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, cur->l4proto); +	nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, +					NFCT_HELPER_STATUS_ENABLED); + +	dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", +		cur->helper->name, cur->queue_num); + +	for (j=0; j<CTD_HELPER_POLICY_MAX; j++) { +		struct nfct_helper_policy *p; + +		if (!cur->helper->policy[j].name[0]) +			break; + +		p = nfct_helper_policy_alloc(); +		if (p == NULL) { +			dlog(LOG_ERR, "cannot allocate object for helper"); +			return -1; +		} +		/* FIXME: get existing policy values from the kernel first. */ +		nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, +					cur->helper->policy[j].name); +		nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, +					cur->helper->policy[j].expect_timeout); +		nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, +					cur->helper->policy[j].expect_max); + +		dlog(LOG_NOTICE, "policy name=%s expect_timeout=%d expect_max=%d", +			cur->helper->policy[j].name, +			cur->helper->policy[j].expect_timeout, +			cur->helper->policy[j].expect_max); + +		nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); +	} + +	if (j == 0) { +		dlog(LOG_ERR, "you have to define one policy for helper"); +		return -1; +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, +					  NLM_F_CREATE | NLM_F_ACK, seq); +	nfct_helper_nlmsg_build_payload(nlh, t); + +	nfct_helper_free(t); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "sending cthelper configuration"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, STATE_CTH(portid), NULL, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); +	} +	if (ret == -1) { +		dlog(LOG_ERR, "trying to configure cthelper `%s': %s", +			cur->helper->name, strerror(errno)); +		return -1; +	} + +	return 0; +} + +static int cthelper_nfqueue_setup(struct ctd_helper_instance *cur) +{ +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; + +	nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num); +	nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_BIND); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send bind command"); +		return -1; +	} + +	nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num); +	nfq_nlmsg_cfg_add_copy(nlh, NFQNL_COPY_PACKET, 0xffff); +	mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQNL_F_CONNTRACK)); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send configuration"); +		return -1; +	} + +	return 0; +} + +static int cthelper_configure(struct ctd_helper_instance *cur) +{ +	/* First, configure cthelper. */ +	if (cthelper_setup(cur) < 0) +		return -1; + +	/* Now, we are ready to configure nfqueue attached to this helper. */ +	if (cthelper_nfqueue_setup(cur) < 0) +		return -1; + +	dlog(LOG_NOTICE, "helper `%s' configured successfully", +		cur->helper->name); + +	return 0; +} + +static int nfq_configure(void) +{ +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; + +	nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, 0); +	nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send pf unbind command"); +		return -1; +	} + +	nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, 0); +	nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND); + +	if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { +		dlog(LOG_ERR, "failed to send pf bind command"); +		return -1; +	} + +	return 0; +} + +int cthelper_init(void) +{ +	struct ctd_helper_instance *cur; +	int ret; + +	state.cthelper = calloc(1, sizeof(struct ct_helper_state)); +	if (state.cthelper == NULL) { +		dlog(LOG_ERR, "can't allocate memory for cthelper struct"); +		return -1; +	} + +	STATE_CTH(nl) = mnl_socket_open(NETLINK_NETFILTER); +	if (STATE_CTH(nl) == NULL) { +		dlog(LOG_ERR, "cannot open nfq socket"); +		return -1; +	} +	fcntl(mnl_socket_get_fd(STATE_CTH(nl)), F_SETFL, O_NONBLOCK); + +	if (mnl_socket_bind(STATE_CTH(nl), 0, MNL_SOCKET_AUTOPID) < 0) { +		dlog(LOG_ERR, "cannot bind nfq socket"); +		return -1; +	} +	STATE_CTH(portid) = mnl_socket_get_portid(STATE_CTH(nl)); + +	if (nfq_configure()) +		return -1; + +	list_for_each_entry(cur, &CONFIG(cthelper).list, head) { +		ret = cthelper_configure(cur); +		if (ret < 0) +			return ret; +	} + +	register_fd(mnl_socket_get_fd(STATE_CTH(nl)), nfq_cb, NULL, STATE(fds)); + +	return 0; +} diff --git a/src/expect.c b/src/expect.c new file mode 100644 index 0000000..eab9094 --- /dev/null +++ b/src/expect.c @@ -0,0 +1,212 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@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 (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include "helper.h" + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dlfcn.h> + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, +		  uint32_t class, +		  union nfct_attr_grp_addr *saddr, +		  union nfct_attr_grp_addr *daddr, +		  uint8_t l4proto, uint16_t *sport, uint16_t *dport) +{ +	struct nf_conntrack *expected, *mask; + +	expected = nfct_new(); +	if (!expected) +		return -1; + +	mask = nfct_new(); +	if (!mask) +		return -1; + +	if (saddr) { +		switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { +		int i; +		uint32_t addr[4] = {}; + +		case AF_INET: +			nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); +			nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + +			nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); +			nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); +			break; +		case AF_INET6: +			nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); +			nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + +			for (i=0; i<4; i++) +				memset(addr, 0xffffffff, sizeof(uint32_t)); + +			nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); +			nfct_set_attr(mask, ATTR_IPV6_SRC, addr); +			break; +		default: +			break; +		} +	} else { +		switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { +		int i; +		uint32_t addr[4] = {}; + +		case AF_INET: +			nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); +			nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + +			nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); +			nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); +			break; +		case AF_INET6: +			for (i=0; i<4; i++) +				memset(addr, 0x00000000, sizeof(uint32_t)); + +			nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); +			nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + +			nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); +			nfct_set_attr(mask, ATTR_IPV6_SRC, addr); +			break; +		default: +			break; +		} +	} + +	if (sport) { +		switch(l4proto) { +		case IPPROTO_TCP: +		case IPPROTO_UDP: +			nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); +			nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); +			nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); +			nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); +			break; +		default: +			break; +		} +	} else { +		switch(l4proto) { +		case IPPROTO_TCP: +		case IPPROTO_UDP: +			nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); +			nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); +			nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); +			nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); +			break; +		default: +			break; +		} +	} + +	switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { +	uint32_t addr[4] = {}; +	int i; + +	case AF_INET: +		nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); +		nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); +		nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); +		break; +	case AF_INET6: +		nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); +		nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + +		for (i=0; i<4; i++) +			memset(addr, 0xffffffff, sizeof(uint32_t)); + +		nfct_set_attr(mask, ATTR_IPV6_DST, addr); +		break; +	default: +		break; +	} + +	switch(l4proto) { +	case IPPROTO_TCP: +	case IPPROTO_UDP: +		nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); +		nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); +		nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); +		nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); +		break; +	default: +		break; +	} + +	nfexp_set_attr(exp, ATTR_EXP_MASTER, master); +	nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); +	nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + +	nfct_destroy(expected); +	nfct_destroy(mask); + +	return 0; +} + +static int cthelper_expect_cmd(struct nf_expect *exp, int cmd) +{ +	int ret; +	struct nfct_handle *h; + +	h = nfct_open(EXPECT, 0); +	if (!h) +		return -1; + +	ret = nfexp_query(h, cmd, exp); + +	nfct_close(h); +	return ret; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ +	return cthelper_expect_cmd(exp, NFCT_Q_CREATE); +} + +int cthelper_del_expect(struct nf_expect *exp) +{ +	return cthelper_expect_cmd(exp, NFCT_Q_DESTROY); +} + +void +cthelper_get_addr_src(struct nf_conntrack *ct, int dir, +		      union nfct_attr_grp_addr *addr) +{ +	switch (dir) { +	case MYCT_DIR_ORIG: +		nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr); +		break; +	case MYCT_DIR_REPL: +		nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr); +		break; +	} +} + +void +cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, +		      union nfct_attr_grp_addr *addr) +{ +	switch (dir) { +	case MYCT_DIR_ORIG: +		nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr); +		break; +	case MYCT_DIR_REPL: +		nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr); +		break; +	} +} diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..3e4e6c8 --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,76 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@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 (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include "helper.h" + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dlfcn.h> + +static LIST_HEAD(helper_list); + +void helper_register(struct ctd_helper *helper) +{ +	list_add(&helper->head, &helper_list); +} + +static struct ctd_helper * +__helper_find(const char *helper_name, uint8_t l4proto) +{ +	struct ctd_helper *cur, *helper = NULL; + +	list_for_each_entry(cur, &helper_list, head) { +		if (strncmp(cur->name, helper_name, CTD_HELPER_NAME_LEN) != 0) +			continue; + +		if (cur->l4proto != l4proto) +			continue; + +		helper = cur; +		break; +	} +	return helper; +} + +struct ctd_helper * +helper_find(const char *libdir_path, +	    const char *helper_name, uint8_t l4proto, int flag) +{ +	char path[PATH_MAX]; +	struct ctd_helper *helper; +	struct stat sb; + +	helper = __helper_find(helper_name, l4proto); +	if (helper != NULL) +		return helper; + +	snprintf(path, sizeof(path), "%s/ct_helper_%s.so", +		libdir_path, helper_name); + +	if (stat(path, &sb) != 0) { +		if (errno == ENOENT) +			return NULL; +		fprintf(stderr, "%s: %s\n", path, +			strerror(errno)); +		return NULL; +	} + +	if (dlopen(path, flag) == NULL) { +		fprintf(stderr, "%s: %s\n", path, dlerror()); +		return NULL; +	} + +	return __helper_find(helper_name, l4proto); +} diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am new file mode 100644 index 0000000..2c9d63b --- /dev/null +++ b/src/helpers/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/Make_global.am + +pkglib_LTLIBRARIES = ct_helper_ftp.la + +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) + + diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c new file mode 100644 index 0000000..c6ad7da --- /dev/null +++ b/src/helpers/ftp.c @@ -0,0 +1,599 @@ +/* + * (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> + +#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, u_int8_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|%pI4|%u|", &addr, 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(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)) { +		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); +} @@ -19,6 +19,7 @@  #include "conntrackd.h"  #include "log.h" +#include "helper.h"  #include <sys/types.h>  #include <sys/stat.h> @@ -31,7 +32,7 @@  #include <limits.h>  struct ct_general_state st; -union ct_state state; +struct ct_state state;  static const char usage_daemon_commands[] =  	"Daemon mode commands:\n" diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c new file mode 100644 index 0000000..e8f85bb --- /dev/null +++ b/src/nfct-extensions/helper.c @@ -0,0 +1,619 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <netinet/in.h> +#include <errno.h> +#include <dlfcn.h> + +#include <libmnl/libmnl.h> +#include <linux/netfilter/nfnetlink_cthelper.h> +#include <libnetfilter_cthelper/libnetfilter_cthelper.h> + +#include "nfct.h" +#include "helper.h" + +static void +nfct_cmd_helper_usage(char *argv[]) +{ +	fprintf(stderr, "nfct v%s: Missing command\n" +			"%s helper list|add|delete|get|flush " +			"[parameters...]\n", VERSION, argv[0]); +} + +int +nfct_cmd_helper_parse_params(int argc, char *argv[]) +{ +	int cmd = NFCT_CMD_NONE, ret = 0; + +	if (argc < 3) { +		fprintf(stderr, "nfct v%s: Missing command\n" +				"%s helper list|add|delete|get|flush " +				"[parameters...]\n", VERSION, argv[0]); +		exit(EXIT_FAILURE); +	} +	if (strncmp(argv[2], "list", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_LIST; +	else if (strncmp(argv[2], "add", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_ADD; +	else if (strncmp(argv[2], "delete", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_DELETE; +	else if (strncmp(argv[2], "get", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_GET; +	else if (strncmp(argv[2], "flush", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_FLUSH; +	else if (strncmp(argv[2], "disable", strlen(argv[2])) == 0) +		cmd = NFCT_CMD_DISABLE; +	else { +		fprintf(stderr, "nfct v%s: Unknown command: %s\n", +			VERSION, argv[2]); +		nfct_cmd_helper_usage(argv); +		exit(EXIT_FAILURE); +	} +	switch(cmd) { +	case NFCT_CMD_LIST: +		ret = nfct_cmd_helper_list(argc, argv); +		break; +	case NFCT_CMD_ADD: +		ret = nfct_cmd_helper_add(argc, argv); +		break; +	case NFCT_CMD_DELETE: +		ret = nfct_cmd_helper_delete(argc, argv); +		break; +	case NFCT_CMD_GET: +		ret = nfct_cmd_helper_get(argc, argv); +		break; +	case NFCT_CMD_FLUSH: +		ret = nfct_cmd_helper_flush(argc, argv); +		break; +	case NFCT_CMD_DISABLE: +		ret = nfct_cmd_helper_disable(argc, argv); +		break; +	} + +	return ret; +} + +static int nfct_helper_cb(const struct nlmsghdr *nlh, void *data) +{ +	struct nfct_helper *t; +	char buf[4096]; + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		nfct_perror("OOM"); +		goto err; +	} + +	if (nfct_helper_nlmsg_parse_payload(nlh, t) < 0) { +		nfct_perror("nfct_helper_nlmsg_parse_payload"); +		goto err_free; +	} + +	nfct_helper_snprintf(buf, sizeof(buf), t, 0, 0); +	printf("%s\n", buf); + +err_free: +	nfct_helper_free(t); +err: +	return MNL_CB_OK; +} + +int nfct_cmd_helper_list(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	unsigned int seq, portid; +	int ret; + +	if (argc > 3) { +		nfct_perror("too many arguments"); +		return -1; +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, +						NLM_F_DUMP, seq); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} +	mnl_socket_close(nl); + +	return 0; +} + +int nfct_cmd_helper_add(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t portid, seq; +	struct nfct_helper *t; +	uint16_t l3proto; +	uint8_t l4proto; +	struct ctd_helper *helper; +	int ret, j; + +	if (argc < 6) { +		nfct_perror("missing parameters\n" +			    "syntax: nfct helper add name " +			    "family protocol"); +		return -1; +	} + +	if (strcmp(argv[4], "inet") == 0) +		l3proto = AF_INET; +	else if (strcmp(argv[4], "inet6") == 0) +		l3proto = AF_INET6; +	else { +		nfct_perror("unknown layer 3 protocol"); +		return -1; +	} + +	if (strcmp(argv[5], "tcp") == 0) +		l4proto = IPPROTO_TCP; +	else if (strcmp(argv[5], "udp") == 0) +		l4proto = IPPROTO_UDP; +	else { +		nfct_perror("unsupported layer 4 protocol"); +		return -1; +	} + +	/* XXX use prefix defined in configure.ac. */ +	helper = helper_find("/usr/lib/conntrack-tools", +				argv[3], l4proto, RTLD_LAZY); +	if (helper == NULL) { +		nfct_perror("that helper is not supported"); +		return -1; +	} + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		nfct_perror("OOM"); +		return -1; +	} +	nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); +	nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); +	nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); +	nfct_helper_attr_set_u32(t, NFCTH_ATTR_PRIV_DATA_LEN, +				 helper->priv_data_len); + +	for (j=0; j<CTD_HELPER_POLICY_MAX; j++) { +		struct nfct_helper_policy *p; + +		if (!helper->policy[j].name[0]) +			break; + +		p = nfct_helper_policy_alloc(); +		if (p == NULL) { +			nfct_perror("OOM"); +			return -1; +		} + +		nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, +					helper->policy[j].name); +		nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, +					helper->policy[j].expect_timeout); +		nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, +					helper->policy[j].expect_max); + +		nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, +					  NLM_F_CREATE | NLM_F_ACK, seq); +	nfct_helper_nlmsg_build_payload(nlh, t); + +	nfct_helper_free(t); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} +	mnl_socket_close(nl); + +	return 0; +} + +int nfct_cmd_helper_delete(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t portid, seq; +	struct nfct_helper *t; +	int ret; + +	if (argc < 4) { +		nfct_perror("missing helper policy name"); +		return -1; +	} else if (argc > 6) { +		nfct_perror("too many arguments"); +		return -1; +	} + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		nfct_perror("OOM"); +		return -1; +	} + +	nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + +	if (argc >= 5) { +		uint16_t l3proto; + +		if (strcmp(argv[4], "inet") == 0) +			l3proto = AF_INET; +		else if (strcmp(argv[4], "inet6") == 0) +			l3proto = AF_INET6; +		else { +			nfct_perror("unknown layer 3 protocol"); +			return -1; +		} +		nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); +	} + +	if (argc == 6) { +		uint8_t l4proto; + +		if (strcmp(argv[5], "tcp") == 0) +			l4proto = IPPROTO_TCP; +		else if (strcmp(argv[5], "udp") == 0) +			l4proto = IPPROTO_UDP; +		else { +			nfct_perror("unsupported layer 4 protocol"); +			return -1; +		} +		nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, +					  NLM_F_ACK, seq); +	nfct_helper_nlmsg_build_payload(nlh, t); + +	nfct_helper_free(t); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} + +	mnl_socket_close(nl); + +	return 0; +} + +int nfct_cmd_helper_get(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t portid, seq; +	struct nfct_helper *t; +	int ret; + +	if (argc < 4) { +		nfct_perror("missing helper policy name"); +		return -1; +	} else if (argc > 6) { +		nfct_perror("too many arguments"); +		return -1; +	} + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		nfct_perror("OOM"); +		return -1; +	} +	nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + +	if (argc >= 5) { +		uint16_t l3proto; + +		if (strcmp(argv[4], "inet") == 0) +			l3proto = AF_INET; +		else if (strcmp(argv[4], "inet6") == 0) +			l3proto = AF_INET6; +		else { +			nfct_perror("unknown layer 3 protocol"); +			return -1; +		} +		nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); +	} + +	if (argc == 6) { +		uint8_t l4proto; + +		if (strcmp(argv[5], "tcp") == 0) +			l4proto = IPPROTO_TCP; +		else if (strcmp(argv[5], "udp") == 0) +			l4proto = IPPROTO_UDP; +		else { +			nfct_perror("unsupported layer 4 protocol"); +			return -1; +		} +		nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, +					  NLM_F_ACK, seq); + +	nfct_helper_nlmsg_build_payload(nlh, t); + +	nfct_helper_free(t); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} +	mnl_socket_close(nl); + +	return 0; +} + +int nfct_cmd_helper_flush(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t portid, seq; +	int ret; + +	if (argc > 3) { +		nfct_perror("too many arguments"); +		return -1; +	} + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, +					   NLM_F_ACK, seq); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} + +	mnl_socket_close(nl); + +	return 0; +} + +int nfct_cmd_helper_disable(int argc, char *argv[]) +{ +	struct mnl_socket *nl; +	char buf[MNL_SOCKET_BUFFER_SIZE]; +	struct nlmsghdr *nlh; +	uint32_t portid, seq; +	struct nfct_helper *t; +	uint16_t l3proto; +	uint8_t l4proto; +	struct ctd_helper *helper; +	int ret; + +	if (argc < 6) { +		nfct_perror("missing parameters\n" +			    "syntax: nfct helper add name " +			    "family protocol"); +		return -1; +	} + +	if (strcmp(argv[4], "inet") == 0) +		l3proto = AF_INET; +	else if (strcmp(argv[4], "inet6") == 0) +		l3proto = AF_INET6; +	else { +		nfct_perror("unknown layer 3 protocol"); +		return -1; +	} + +	if (strcmp(argv[5], "tcp") == 0) +		l4proto = IPPROTO_TCP; +	else if (strcmp(argv[5], "udp") == 0) +		l4proto = IPPROTO_UDP; +	else { +		nfct_perror("unsupported layer 4 protocol"); +		return -1; +	} + +	/* XXX use prefix defined in configure.ac. */ +	helper = helper_find("/usr/lib/conntrack-tools", +				argv[3], l4proto, RTLD_LAZY); +	if (helper == NULL) { +		nfct_perror("that helper is not supported"); +		return -1; +	} + +	t = nfct_helper_alloc(); +	if (t == NULL) { +		nfct_perror("OOM"); +		return -1; +	} +	nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); +	nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); +	nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); +	nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, +					NFCT_HELPER_STATUS_DISABLED); + +	seq = time(NULL); +	nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, +					  NLM_F_CREATE | NLM_F_ACK, seq); +	nfct_helper_nlmsg_build_payload(nlh, t); + +	nfct_helper_free(t); + +	nl = mnl_socket_open(NETLINK_NETFILTER); +	if (nl == NULL) { +		nfct_perror("mnl_socket_open"); +		return -1; +	} + +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { +		nfct_perror("mnl_socket_bind"); +		return -1; +	} +	portid = mnl_socket_get_portid(nl); + +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { +		nfct_perror("mnl_socket_send"); +		return -1; +	} + +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	while (ret > 0) { +		ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); +		if (ret <= 0) +			break; +		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); +	} +	if (ret == -1) { +		nfct_perror("error"); +		return -1; +	} +	mnl_socket_close(nl); + +	return 0; +} + @@ -56,6 +56,8 @@ int main(int argc, char *argv[])  	}  	if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) {  		subsys = NFCT_SUBSYS_TIMEOUT; +	} else if (strncmp(argv[1], "helper", strlen(argv[1])) == 0) { +		subsys = NFCT_SUBSYS_HELPER;  	} else if (strncmp(argv[1], "version", strlen(argv[1])) == 0)  		subsys = NFCT_SUBSYS_VERSION;  	else if (strncmp(argv[1], "help", strlen(argv[1])) == 0) @@ -71,6 +73,9 @@ int main(int argc, char *argv[])  	case NFCT_SUBSYS_TIMEOUT:  		ret = nfct_cmd_timeout_parse_params(argc, argv);  		break; +	case NFCT_SUBSYS_HELPER: +		ret = nfct_cmd_helper_parse_params(argc, argv); +		break;  	case NFCT_SUBSYS_VERSION:  		ret = nfct_cmd_version(argc, argv);  		break; @@ -99,6 +104,7 @@ static const char help_msg[] =  	"nfct v%s: utility for the Netfilter's Connection Tracking System\n"  	"Usage: %s command [parameters]...\n\n"  	"Subsystem:\n" +	"  helper\t\tAllows to configure user-space helper\n"  	"  timeout\t\tAllows definition of fine-grain timeout policies\n"  	"  version\t\tDisplay version and disclaimer\n"  	"  help\t\t\tDisplay this help message\n" diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 01fe4fc..31fa32e 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -142,6 +142,11 @@ notrack		[N|n][O|o][T|t][R|r][A|a][C|c][K|k]  "TCPWindowTracking"		{ return T_TCP_WINDOW_TRACKING; }  "ExpectationSync"		{ return T_EXPECT_SYNC; }  "ErrorQueueLength"		{ return T_ERROR_QUEUE_LENGTH; } +"Helper"			{ return T_HELPER; } +"QueueNum"			{ return T_HELPER_QUEUE_NUM; } +"Policy"			{ return T_HELPER_POLICY; } +"ExpectMax"			{ return T_HELPER_EXPECT_MAX; } +"ExpectTimeout"			{ return T_HELPER_EXPECT_TIMEOUT; }  {is_on}			{ return T_ON; }  {is_off}		{ return T_OFF; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index b22784c..c9235d3 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -28,8 +28,11 @@  #include "conntrackd.h"  #include "bitops.h"  #include "cidr.h" +#include "helper.h" +#include "stack.h"  #include <syslog.h>  #include <sched.h> +#include <dlfcn.h>  #include <libnetfilter_conntrack/libnetfilter_conntrack.h>  #include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h> @@ -48,6 +51,15 @@ static void print_err(int err, const char *msg, ...);  static void __kernel_filter_start(void);  static void __kernel_filter_add_state(int value);  static void __max_dedicated_links_reached(void); + +struct stack symbol_stack; + +enum { +	SYMBOL_HELPER_QUEUE_NUM, +	SYMBOL_HELPER_POLICY_EXPECT_ROOT, +	SYMBOL_HELPER_EXPECT_POLICY_LEAF, +}; +  %}  %union { @@ -74,6 +86,8 @@ static void __max_dedicated_links_reached(void);  %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE  %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH  %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX +%token T_HELPER_EXPECT_TIMEOUT  %token <string> T_IP T_PATH_VAL  %token <val> T_NUMBER @@ -96,6 +110,7 @@ line : ignore_protocol       | general       | sync       | stats +     | helper       ;  logfile_bool : T_LOG T_ON @@ -1561,6 +1576,186 @@ buffer_size: T_STAT_BUFFER_SIZE T_NUMBER  	print_err(CTD_CFG_WARN, "`LogFileBufferSize' is deprecated");  }; +helper: T_HELPER '{' helper_list '}' +{ +	conf.flags |= CTD_HELPER; +}; + +helper_list: +	    | helper_list helper_line +	    ; + +helper_line: helper_type +	    ; + +helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list  '}' +{ +	struct ctd_helper_instance *helper_inst; +	struct ctd_helper *helper; +	struct stack_item *e; +	uint16_t l3proto; +	uint8_t l4proto; + +	if (strcmp($3, "inet") == 0) +		l3proto = AF_INET; +	else if (strcmp($3, "inet6") == 0) +		l3proto = AF_INET6; +	else { +		print_err(CTD_CFG_ERROR, "unknown layer 3 protocol"); +		exit(EXIT_FAILURE); +	} + +	if (strcmp($4, "tcp") == 0) +		l4proto = IPPROTO_TCP; +	else if (strcmp($4, "udp") == 0) +		l4proto = IPPROTO_UDP; +	else { +		print_err(CTD_CFG_ERROR, "unknown layer 4 protocol"); +		exit(EXIT_FAILURE); +	} + +	/* XXX use configure.ac definitions. */ +	helper = helper_find("/usr/lib/conntrack-tools", $2, l4proto, RTLD_NOW); +	if (helper == NULL) { +		print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2); +		exit(EXIT_FAILURE); +	} + +	helper_inst = calloc(1, sizeof(struct ctd_helper_instance)); +	if (helper_inst == NULL) +		break; + +	helper_inst->l3proto = l3proto; +	helper_inst->l4proto = l4proto; +	helper_inst->helper = helper; + +	while ((e = stack_item_pop(&symbol_stack, -1)) != NULL) { + +		switch(e->type) { +		case SYMBOL_HELPER_QUEUE_NUM: { +			int *qnum = (int *) &e->data; + +			helper_inst->queue_num = *qnum; +			stack_item_free(e); +			break; +		} +		case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { +			struct ctd_helper_policy *pol = +				(struct ctd_helper_policy *) &e->data; +			struct ctd_helper_policy *matching = NULL; +			int i; + +			for (i=0; i<CTD_HELPER_POLICY_MAX; i++) { +				if (strcmp(helper->policy[i].name, +					   pol->name) != 0) +					continue; + +				matching = pol; +				break; +			} +			if (matching == NULL) { +				print_err(CTD_CFG_ERROR, +					  "Unknown policy `%s' in helper " +					  "configuration", pol->name); +				exit(EXIT_FAILURE); +			} +			/* FIXME: First set default policy, then change only +			 * tuned fields, not everything. +			 */ +			memcpy(&helper->policy[i], pol, +				sizeof(struct ctd_helper_policy)); + +			stack_item_free(e); +			break; +		} +		default: +			print_err(CTD_CFG_ERROR, +				  "Unexpected symbol parsing helper policy"); +				exit(EXIT_FAILURE); +			break; +		} +	} +	list_add(&helper_inst->head, &CONFIG(cthelper).list); +}; + +helper_type_list: +		| helper_type_list helper_type_line +		; + +helper_type_line: helper_type +		; + +helper_type: T_HELPER_QUEUE_NUM T_NUMBER +{ +	int *qnum; +	struct stack_item *e; + +	e = stack_item_alloc(SYMBOL_HELPER_QUEUE_NUM, sizeof(int)); +	qnum = (int *) e->data; +	*qnum = $2; +	stack_item_push(&symbol_stack, e); +}; + +helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' +{ +	struct stack_item *e; +	struct ctd_helper_policy *policy; + +	e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); +	if (e == NULL) { +		print_err(CTD_CFG_ERROR, +			  "Helper policy configuration empty, fix your " +			  "configuration file, please"); +		exit(EXIT_FAILURE); +		break; +	} + +	policy = (struct ctd_helper_policy *) &e->data; +	strncpy(policy->name, $2, CTD_HELPER_NAME_LEN); +	policy->name[CTD_HELPER_NAME_LEN-1] = '\0'; +	/* Now object is complete. */ +	e->type = SYMBOL_HELPER_POLICY_EXPECT_ROOT; +	stack_item_push(&symbol_stack, e); +}; + +helper_policy_list: +		  | helper_policy_list helper_policy_line +		  ; + +helper_policy_line: helper_policy_expect_max +		  | helper_policy_expect_timeout +		  ; + +helper_policy_expect_max: T_HELPER_EXPECT_MAX T_NUMBER +{ +	struct stack_item *e; +	struct ctd_helper_policy *policy; + +	e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); +	if (e == NULL) { +		e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, +				     sizeof(struct ctd_helper_policy)); +	} +	policy = (struct ctd_helper_policy *) &e->data; +	policy->expect_max = $2; +	stack_item_push(&symbol_stack, e); +}; + +helper_policy_expect_timeout: T_HELPER_EXPECT_TIMEOUT T_NUMBER +{ +	struct stack_item *e; +	struct ctd_helper_policy *policy; + +	e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); +	if (e == NULL) { +		e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, +				     sizeof(struct ctd_helper_policy)); +	} +	policy = (struct ctd_helper_policy *) &e->data; +	policy->expect_timeout = $2; +	stack_item_push(&symbol_stack, e); +}; +  %%  int __attribute__((noreturn)) @@ -1640,6 +1835,11 @@ init_config(char *filename)  	CONFIG(stats).syslog_facility = -1;  	CONFIG(netlink).subsys_id = -1; +	/* Initialize list of user-space helpers */ +	INIT_LIST_HEAD(&CONFIG(cthelper).list); + +	stack_init(&symbol_stack); +  	yyrestart(fp);  	yyparse();  	fclose(fp); @@ -50,6 +50,9 @@ void killer(int foo)  	if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))  		ctnl_kill(); +	if (CONFIG(flags) & CTD_HELPER) +		cthelper_kill(); +  	destroy_fds(STATE(fds));  	unlink(CONFIG(lockfile));  	dlog(LOG_NOTICE, "---- shutdown received ----"); @@ -199,6 +202,9 @@ static int local_handler(int fd, void *data)  	if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))  		return ctnl_local(fd, type, data); +	if (CONFIG(flags) & CTD_HELPER) +		return cthelper_local(fd, type, data); +  	return ret;  } @@ -250,6 +256,11 @@ init(void)  		if (ctnl_init() < 0)  			return -1; +	if (CONFIG(flags) & CTD_HELPER) { +		if (cthelper_init() < 0) +			return -1; +	} +  	time(&STATE(stats).daemon_start_time);  	dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..104b7ba --- /dev/null +++ b/src/stack.c @@ -0,0 +1,56 @@ +/* + * (C) 2005-2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "stack.h" + +struct stack_item * +stack_item_alloc(int type, size_t data_len) +{ +	struct stack_item *e; + +	e = calloc(1, sizeof(struct stack_item) + data_len); +	if (e == NULL) +		return NULL; + +	e->data_len = data_len; +	e->type = type; + +	return e; +} + +void stack_item_free(struct stack_item *e) +{ +	free(e); +} + +void stack_item_push(struct stack *s, struct stack_item *e) +{ +	list_add(&e->head, &s->list); +} + +struct stack_item *stack_item_pop(struct stack *s, int type) +{ +	struct stack_item *cur, *tmp, *found = NULL; + +	list_for_each_entry_safe(cur, tmp, &s->list, head) { +		if (cur->type != type && type != -1) +			continue; + +		list_del(&cur->head); +		found = cur; +		break; +	} + +	return found; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..fabec24 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,243 @@ +/* The following code has been extracted from the kenrel sources, if there is + * any problem, blame for mangling it. --pablo */ + +/* + *	Generic address resultion entity + * + *	Authors: + *	net_random Alan Cox + *	net_ratelimit Andi Kleen + *	in{4,6}_pton YOSHIFUJI Hideaki, Copyright (C)2006 USAGI/WIDE Project + * + *	Created by Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> + * + *	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. + */ + +/* + * lib/hexdump.c + * + * 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. See README and COPYING for + * more details. + */ + +#include <stdint.h> +#include <ctype.h> +#include <string.h>	/* for memcpy */ + +#include "helper.h" + +static int hex_to_bin(char ch) +{ +	if ((ch >= '0') && (ch <= '9')) +		return ch - '0'; +	ch = tolower(ch); +	if ((ch >= 'a') && (ch <= 'f')) +		return ch - 'a' + 10; +	return -1; +} + +#define IN6PTON_XDIGIT		0x00010000 +#define IN6PTON_DIGIT		0x00020000 +#define IN6PTON_COLON_MASK	0x00700000 +#define IN6PTON_COLON_1		0x00100000	/* single : requested */ +#define IN6PTON_COLON_2		0x00200000	/* second : requested */ +#define IN6PTON_COLON_1_2	0x00400000	/* :: requested */ +#define IN6PTON_DOT		0x00800000	/* . */ +#define IN6PTON_DELIM		0x10000000 +#define IN6PTON_NULL		0x20000000	/* first/tail */ +#define IN6PTON_UNKNOWN		0x40000000 + +static inline int xdigit2bin(char c, int delim) +{ +	int val; + +	if (c == delim || c == '\0') +		return IN6PTON_DELIM; +	if (c == ':') +		return IN6PTON_COLON_MASK; +	if (c == '.') +		return IN6PTON_DOT; + +	val = hex_to_bin(c); +	if (val >= 0) +		return val | IN6PTON_XDIGIT | (val < 10 ? IN6PTON_DIGIT : 0); + +	if (delim == -1) +		return IN6PTON_DELIM; +	return IN6PTON_UNKNOWN; +} + +int in4_pton(const char *src, int srclen, +	     uint8_t *dst, +	     int delim, const char **end) +{ +	const char *s; +	uint8_t *d; +	uint8_t dbuf[4]; +	int ret = 0; +	int i; +	int w = 0; + +	if (srclen < 0) +		srclen = strlen(src); +	s = src; +	d = dbuf; +	i = 0; +	while(1) { +		int c; +		c = xdigit2bin(srclen > 0 ? *s : '\0', delim); +		if (!(c & (IN6PTON_DIGIT | IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK))) { +			goto out; +		} +		if (c & (IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK)) { +			if (w == 0) +				goto out; +			*d++ = w & 0xff; +			w = 0; +			i++; +			if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { +				if (i != 4) +					goto out; +				break; +			} +			goto cont; +		} +		w = (w * 10) + c; +		if ((w & 0xffff) > 255) { +			goto out; +		} +cont: +		if (i >= 4) +			goto out; +		s++; +		srclen--; +	} +	ret = 1; +	memcpy(dst, dbuf, sizeof(dbuf)); +out: +	if (end) +		*end = s; +	return ret; +} + +int in6_pton(const char *src, int srclen, +	     uint8_t *dst, +	     int delim, const char **end) +{ +	const char *s, *tok = NULL; +	uint8_t *d, *dc = NULL; +	uint8_t dbuf[16]; +	int ret = 0; +	int i; +	int state = IN6PTON_COLON_1_2 | IN6PTON_XDIGIT | IN6PTON_NULL; +	int w = 0; + +	memset(dbuf, 0, sizeof(dbuf)); + +	s = src; +	d = dbuf; +	if (srclen < 0) +		srclen = strlen(src); + +	while (1) { +		int c; + +		c = xdigit2bin(srclen > 0 ? *s : '\0', delim); +		if (!(c & state)) +			goto out; +		if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { +			/* process one 16-bit word */ +			if (!(state & IN6PTON_NULL)) { +				*d++ = (w >> 8) & 0xff; +				*d++ = w & 0xff; +			} +			w = 0; +			if (c & IN6PTON_DELIM) { +				/* We've processed last word */ +				break; +			} +			/* +			 * COLON_1 => XDIGIT +			 * COLON_2 => XDIGIT|DELIM +			 * COLON_1_2 => COLON_2 +			 */ +			switch (state & IN6PTON_COLON_MASK) { +			case IN6PTON_COLON_2: +				dc = d; +				state = IN6PTON_XDIGIT | IN6PTON_DELIM; +				if (dc - dbuf >= (int)sizeof(dbuf)) +					state |= IN6PTON_NULL; +				break; +			case IN6PTON_COLON_1|IN6PTON_COLON_1_2: +				state = IN6PTON_XDIGIT | IN6PTON_COLON_2; +				break; +			case IN6PTON_COLON_1: +				state = IN6PTON_XDIGIT; +				break; +			case IN6PTON_COLON_1_2: +				state = IN6PTON_COLON_2; +				break; +			default: +				state = 0; +			} +			tok = s + 1; +			goto cont; +		} + +		if (c & IN6PTON_DOT) { +			ret = in4_pton(tok ? tok : s, srclen + (int)(s - tok), d, delim, &s); +			if (ret > 0) { +				d += 4; +				break; +			} +			goto out; +		} + +		w = (w << 4) | (0xff & c); +		state = IN6PTON_COLON_1 | IN6PTON_DELIM; +		if (!(w & 0xf000)) { +			state |= IN6PTON_XDIGIT; +		} +		if (!dc && d + 2 < dbuf + sizeof(dbuf)) { +			state |= IN6PTON_COLON_1_2; +			state &= ~IN6PTON_DELIM; +		} +		if (d + 2 >= dbuf + sizeof(dbuf)) { +			state &= ~(IN6PTON_COLON_1|IN6PTON_COLON_1_2); +		} +cont: +		if ((dc && d + 4 < dbuf + sizeof(dbuf)) || +		    d + 4 == dbuf + sizeof(dbuf)) { +			state |= IN6PTON_DOT; +		} +		if (d >= dbuf + sizeof(dbuf)) { +			state &= ~(IN6PTON_XDIGIT|IN6PTON_COLON_MASK); +		} +		s++; +		srclen--; +	} + +	i = 15; d--; + +	if (dc) { +		while(d >= dc) +			dst[i--] = *d--; +		while(i >= dc - dbuf) +			dst[i--] = 0; +		while(i >= 0) +			dst[i--] = *d--; +	} else +		memcpy(dst, dbuf, sizeof(dbuf)); + +	ret = 1; +out: +	if (end) +		*end = s; +	return ret; +} | 
