| /* |
| * (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); |
| } |