blob: 9e6c4e706d6db9a4bb187ae9804fecc639899aac [file] [log] [blame]
/*
* (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);
}