1/* FTP extension for TCP NAT alteration. */
2#include <linux/module.h>
3#include <linux/netfilter_ipv4.h>
4#include <linux/ip.h>
5#include <linux/tcp.h>
6#include <net/tcp.h>
7#include <linux/netfilter_ipv4/ip_nat.h>
8#include <linux/netfilter_ipv4/ip_nat_helper.h>
9#include <linux/netfilter_ipv4/ip_nat_rule.h>
10#include <linux/netfilter_ipv4/ip_conntrack_ftp.h>
11#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
12
13#define DEBUGP(format, args...)
14
15#define MAX_PORTS 8
16static int ports[MAX_PORTS];
17static int ports_c = 0;
18
19#ifdef MODULE_PARM
20MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
21#endif
22
23DECLARE_LOCK_EXTERN(ip_ftp_lock);
24
25
26static unsigned int
27ftp_nat_expected(struct sk_buff **pskb,
28		 unsigned int hooknum,
29		 struct ip_conntrack *ct,
30		 struct ip_nat_info *info)
31{
32	struct ip_nat_multi_range mr;
33	u_int32_t newdstip, newsrcip, newip;
34	struct ip_ct_ftp_expect *exp_ftp_info;
35
36	struct ip_conntrack *master = master_ct(ct);
37
38	IP_NF_ASSERT(info);
39	IP_NF_ASSERT(master);
40
41	IP_NF_ASSERT(!(info->initialized & (1<<HOOK2MANIP(hooknum))));
42
43	DEBUGP("nat_expected: We have a connection!\n");
44	exp_ftp_info = &ct->master->help.exp_ftp_info;
45
46	LOCK_BH(&ip_ftp_lock);
47
48	if (exp_ftp_info->ftptype == IP_CT_FTP_PORT
49	    || exp_ftp_info->ftptype == IP_CT_FTP_EPRT) {
50		/* PORT command: make connection go to the client. */
51		newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
52		newsrcip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
53		DEBUGP("nat_expected: PORT cmd. %u.%u.%u.%u->%u.%u.%u.%u\n",
54		       NIPQUAD(newsrcip), NIPQUAD(newdstip));
55	} else {
56		/* PASV command: make the connection go to the server */
57		newdstip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
58		newsrcip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
59		DEBUGP("nat_expected: PASV cmd. %u.%u.%u.%u->%u.%u.%u.%u\n",
60		       NIPQUAD(newsrcip), NIPQUAD(newdstip));
61	}
62	UNLOCK_BH(&ip_ftp_lock);
63
64	if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
65		newip = newsrcip;
66	else
67		newip = newdstip;
68
69	DEBUGP("nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip));
70
71	mr.rangesize = 1;
72	/* We don't want to manip the per-protocol, just the IPs... */
73	mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
74	mr.range[0].min_ip = mr.range[0].max_ip = newip;
75
76	/* ... unless we're doing a MANIP_DST, in which case, make
77	   sure we map to the correct port */
78	if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
79		mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
80		mr.range[0].min = mr.range[0].max
81			= ((union ip_conntrack_manip_proto)
82				{ htons(exp_ftp_info->port) });
83	}
84	return ip_nat_setup_info(ct, &mr, hooknum);
85}
86
87static int
88mangle_rfc959_packet(struct sk_buff **pskb,
89		     u_int32_t newip,
90		     u_int16_t port,
91		     unsigned int matchoff,
92		     unsigned int matchlen,
93		     struct ip_conntrack *ct,
94		     enum ip_conntrack_info ctinfo)
95{
96	char buffer[sizeof("nnn,nnn,nnn,nnn,nnn,nnn")];
97
98	MUST_BE_LOCKED(&ip_ftp_lock);
99
100	sprintf(buffer, "%u,%u,%u,%u,%u,%u",
101		NIPQUAD(newip), port>>8, port&0xFF);
102
103	DEBUGP("calling ip_nat_mangle_tcp_packet\n");
104
105	return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
106					matchlen, buffer, strlen(buffer));
107}
108
109/* |1|132.235.1.2|6275| */
110static int
111mangle_eprt_packet(struct sk_buff **pskb,
112		   u_int32_t newip,
113		   u_int16_t port,
114		   unsigned int matchoff,
115		   unsigned int matchlen,
116		   struct ip_conntrack *ct,
117		   enum ip_conntrack_info ctinfo)
118{
119	char buffer[sizeof("|1|255.255.255.255|65535|")];
120
121	MUST_BE_LOCKED(&ip_ftp_lock);
122
123	sprintf(buffer, "|1|%u.%u.%u.%u|%u|", NIPQUAD(newip), port);
124
125	DEBUGP("calling ip_nat_mangle_tcp_packet\n");
126
127	return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
128					matchlen, buffer, strlen(buffer));
129}
130
131/* |1|132.235.1.2|6275| */
132static int
133mangle_epsv_packet(struct sk_buff **pskb,
134		   u_int32_t newip,
135		   u_int16_t port,
136		   unsigned int matchoff,
137		   unsigned int matchlen,
138		   struct ip_conntrack *ct,
139		   enum ip_conntrack_info ctinfo)
140{
141	char buffer[sizeof("|||65535|")];
142
143	MUST_BE_LOCKED(&ip_ftp_lock);
144
145	sprintf(buffer, "|||%u|", port);
146
147	DEBUGP("calling ip_nat_mangle_tcp_packet\n");
148
149	return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
150					matchlen, buffer, strlen(buffer));
151}
152
153static int (*mangle[])(struct sk_buff **, u_int32_t, u_int16_t,
154		     unsigned int,
155		     unsigned int,
156		     struct ip_conntrack *,
157		     enum ip_conntrack_info)
158= { [IP_CT_FTP_PORT] mangle_rfc959_packet,
159    [IP_CT_FTP_PASV] mangle_rfc959_packet,
160    [IP_CT_FTP_EPRT] mangle_eprt_packet,
161    [IP_CT_FTP_EPSV] mangle_epsv_packet
162};
163
164static int ftp_data_fixup(const struct ip_ct_ftp_expect *ct_ftp_info,
165			  struct ip_conntrack *ct,
166			  struct sk_buff **pskb,
167			  enum ip_conntrack_info ctinfo,
168			  struct ip_conntrack_expect *expect)
169{
170	u_int32_t newip;
171	struct iphdr *iph = (*pskb)->nh.iph;
172	struct tcphdr *tcph = (void *)iph + iph->ihl*4;
173	u_int16_t port;
174	struct ip_conntrack_tuple newtuple;
175
176	MUST_BE_LOCKED(&ip_ftp_lock);
177	DEBUGP("FTP_NAT: seq %u + %u in %u\n",
178	       expect->seq, ct_ftp_info->len,
179	       ntohl(tcph->seq));
180
181	/* Change address inside packet to match way we're mapping
182	   this connection. */
183	if (ct_ftp_info->ftptype == IP_CT_FTP_PASV
184	    || ct_ftp_info->ftptype == IP_CT_FTP_EPSV) {
185		/* PASV/EPSV response: must be where client thinks server
186		   is */
187		newip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
188		/* Expect something from client->server */
189		newtuple.src.ip =
190			ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
191		newtuple.dst.ip =
192			ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
193	} else {
194		/* PORT command: must be where server thinks client is */
195		newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
196		/* Expect something from server->client */
197		newtuple.src.ip =
198			ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
199		newtuple.dst.ip =
200			ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
201	}
202	newtuple.dst.protonum = IPPROTO_TCP;
203	newtuple.src.u.tcp.port = expect->tuple.src.u.tcp.port;
204
205	/* Try to get same port: if not, try to change it. */
206	for (port = ct_ftp_info->port; port != 0; port++) {
207		newtuple.dst.u.tcp.port = htons(port);
208
209		if (ip_conntrack_change_expect(expect, &newtuple) == 0)
210			break;
211	}
212	if (port == 0)
213		return 0;
214
215	if (!mangle[ct_ftp_info->ftptype](pskb, newip, port,
216					  expect->seq - ntohl(tcph->seq),
217					  ct_ftp_info->len, ct, ctinfo))
218		return 0;
219
220	return 1;
221}
222
223static unsigned int help(struct ip_conntrack *ct,
224			 struct ip_conntrack_expect *exp,
225			 struct ip_nat_info *info,
226			 enum ip_conntrack_info ctinfo,
227			 unsigned int hooknum,
228			 struct sk_buff **pskb)
229{
230	struct iphdr *iph = (*pskb)->nh.iph;
231	struct tcphdr *tcph = (void *)iph + iph->ihl*4;
232	unsigned int datalen;
233	int dir;
234	struct ip_ct_ftp_expect *ct_ftp_info;
235
236	if (!exp)
237		DEBUGP("ip_nat_ftp: no exp!!");
238
239	ct_ftp_info = &exp->help.exp_ftp_info;
240
241	/* Only mangle things once: original direction in POST_ROUTING
242	   and reply direction on PRE_ROUTING. */
243	dir = CTINFO2DIR(ctinfo);
244	if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
245	      || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) {
246		DEBUGP("nat_ftp: Not touching dir %s at hook %s\n",
247		       dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
248		       hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
249		       : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
250		       : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
251		return NF_ACCEPT;
252	}
253
254	datalen = (*pskb)->len - iph->ihl * 4 - tcph->doff * 4;
255	LOCK_BH(&ip_ftp_lock);
256	/* If it's in the right range... */
257	if (between(exp->seq + ct_ftp_info->len,
258		    ntohl(tcph->seq),
259		    ntohl(tcph->seq) + datalen)) {
260		if (!ftp_data_fixup(ct_ftp_info, ct, pskb, ctinfo, exp)) {
261			UNLOCK_BH(&ip_ftp_lock);
262			return NF_DROP;
263		}
264	} else {
265		/* Half a match?  This means a partial retransmisison.
266		   It's a cracker being funky. */
267		if (net_ratelimit()) {
268			printk("FTP_NAT: partial packet %u/%u in %u/%u\n",
269			       exp->seq, ct_ftp_info->len,
270			       ntohl(tcph->seq),
271			       ntohl(tcph->seq) + datalen);
272		}
273		UNLOCK_BH(&ip_ftp_lock);
274		return NF_DROP;
275	}
276	UNLOCK_BH(&ip_ftp_lock);
277
278	return NF_ACCEPT;
279}
280
281static struct ip_nat_helper ftp[MAX_PORTS];
282static char ftp_names[MAX_PORTS][10];
283
284/* Not __exit: called from init() */
285static void fini(void)
286{
287	int i;
288
289	for (i = 0; i < ports_c; i++) {
290		DEBUGP("ip_nat_ftp: unregistering port %d\n", ports[i]);
291		ip_nat_helper_unregister(&ftp[i]);
292	}
293}
294
295static int __init init(void)
296{
297	int i, ret = 0;
298	char *tmpname;
299
300	if (ports[0] == 0)
301		ports[0] = FTP_PORT;
302
303	for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
304
305		memset(&ftp[i], 0, sizeof(struct ip_nat_helper));
306
307		ftp[i].tuple.dst.protonum = IPPROTO_TCP;
308		ftp[i].tuple.src.u.tcp.port = htons(ports[i]);
309		ftp[i].mask.dst.protonum = 0xFFFF;
310		ftp[i].mask.src.u.tcp.port = 0xFFFF;
311		ftp[i].help = help;
312		ftp[i].me = THIS_MODULE;
313		ftp[i].flags = 0;
314		ftp[i].expect = ftp_nat_expected;
315
316		tmpname = &ftp_names[i][0];
317		if (ports[i] == FTP_PORT)
318			sprintf(tmpname, "ftp");
319		else
320			sprintf(tmpname, "ftp-%d", i);
321		ftp[i].name = tmpname;
322
323		DEBUGP("ip_nat_ftp: Trying to register for port %d\n",
324				ports[i]);
325		ret = ip_nat_helper_register(&ftp[i]);
326
327		if (ret) {
328			printk("ip_nat_ftp: error registering "
329			       "helper for port %d\n", ports[i]);
330			fini();
331			return ret;
332		}
333		ports_c++;
334	}
335
336	return ret;
337}
338
339module_init(init);
340module_exit(fini);
341MODULE_LICENSE("GPL");
342