2 * Copyright (c) 2001 Charles Mott <cm@linktel.net>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
31 Alias_ftp.c performs special processing for FTP sessions under
32 TCP. Specifically, when a PORT/EPRT command from the client
33 side or 227/229 reply from the server is sent, it is intercepted
34 and modified. The address is changed to the gateway machine
35 and an aliasing port is used.
37 For this routine to work, the message must fit entirely into a
38 single TCP packet. This is typically the case, but exceptions
39 can easily be envisioned under the actual specifications.
41 Probably the most troubling aspect of the approach taken here is
42 that the new message will typically be a different length, and
43 this causes a certain amount of bookkeeping to keep track of the
44 changes of sequence and acknowledgment numbers, since the client
45 machine is totally unaware of the modification to the TCP stream.
48 References: RFC 959, RFC 2428.
50 Initial version: August, 1996 (cjm)
53 Brian Somers and Martin Renters identified an IP checksum
54 error for modified IP packets.
56 Version 1.7: January 9, 1996 (cjm)
57 Differential checksum computation for change
60 Version 2.1: May, 1997 (cjm)
61 Very minor changes to conform with
62 local/global/function naming conventions
63 within the packet aliasing module.
65 Version 3.1: May, 2000 (eds)
66 Add support for passive mode, alias the 227 replies.
68 See HISTORY file for record of revisions.
73 #include <sys/param.h>
74 #include <sys/ctype.h>
75 #include <sys/systm.h>
76 #include <sys/kernel.h>
77 #include <sys/module.h>
81 #include <sys/types.h>
86 #include <netinet/in_systm.h>
87 #include <netinet/in.h>
88 #include <netinet/ip.h>
89 #include <netinet/tcp.h>
92 #include <netinet/libalias/alias.h>
93 #include <netinet/libalias/alias_local.h>
94 #include <netinet/libalias/alias_mod.h>
96 #include "alias_local.h"
97 #include "alias_mod.h"
100 #define FTP_CONTROL_PORT_NUMBER 21
103 AliasHandleFtpOut(struct libalias *, struct ip *, struct alias_link *,
106 AliasHandleFtpIn(struct libalias *, struct ip *, struct alias_link *);
109 fingerprint_out(struct libalias *la, struct alias_data *ah)
112 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
115 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER ||
116 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER)
122 fingerprint_in(struct libalias *la, struct alias_data *ah)
125 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL)
127 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER ||
128 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER)
134 protohandler_out(struct libalias *la, struct ip *pip, struct alias_data *ah)
137 AliasHandleFtpOut(la, pip, ah->lnk, ah->maxpktsize);
143 protohandler_in(struct libalias *la, struct ip *pip, struct alias_data *ah)
146 AliasHandleFtpIn(la, pip, ah->lnk);
150 struct proto_handler handlers[] = {
155 .fingerprint = &fingerprint_out,
156 .protohandler = &protohandler_out
162 .fingerprint = &fingerprint_in,
163 .protohandler = &protohandler_in
169 mod_handler(module_t mod, int type, void *data)
176 LibAliasAttachHandlers(handlers);
180 LibAliasDetachHandlers(handlers);
191 moduledata_t alias_mod = {
192 "alias_ftp", mod_handler, NULL
196 DECLARE_MODULE(alias_ftp, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
197 MODULE_VERSION(alias_ftp, 1);
198 MODULE_DEPEND(alias_ftp, libalias, 1, 1, 1);
201 #define FTP_CONTROL_PORT_NUMBER 21
202 #define MAX_MESSAGE_SIZE 128
204 /* FTP protocol flags. */
205 #define WAIT_CRLF 0x01
207 enum ftp_message_type {
215 static int ParseFtpPortCommand(struct libalias *la, char *, int);
216 static int ParseFtpEprtCommand(struct libalias *la, char *, int);
217 static int ParseFtp227Reply(struct libalias *la, char *, int);
218 static int ParseFtp229Reply(struct libalias *la, char *, int);
219 static void NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int);
224 struct ip *pip, /* IP packet to examine/patch */
225 struct alias_link *lnk, /* The link to go through (aliased port) */
226 int maxpacketsize /* The maximum size this packet can grow to
227 (including headers) */ )
229 int hlen, tlen, dlen, pflags;
232 int ftp_message_type;
234 /* Calculate data length of TCP packet */
235 tc = (struct tcphdr *)ip_next(pip);
236 hlen = (pip->ip_hl + tc->th_off) << 2;
237 tlen = ntohs(pip->ip_len);
240 /* Place string pointer and beginning of data */
245 * Check that data length is not too long and previous message was
246 * properly terminated with CRLF.
248 pflags = GetProtocolFlags(lnk);
249 if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
250 ftp_message_type = FTP_UNKNOWN_MESSAGE;
252 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
254 * When aliasing a client, check for the PORT/EPRT command.
256 if (ParseFtpPortCommand(la, sptr, dlen))
257 ftp_message_type = FTP_PORT_COMMAND;
258 else if (ParseFtpEprtCommand(la, sptr, dlen))
259 ftp_message_type = FTP_EPRT_COMMAND;
262 * When aliasing a server, check for the 227/229 reply.
264 if (ParseFtp227Reply(la, sptr, dlen))
265 ftp_message_type = FTP_227_REPLY;
266 else if (ParseFtp229Reply(la, sptr, dlen)) {
267 ftp_message_type = FTP_229_REPLY;
268 la->true_addr.s_addr = pip->ip_src.s_addr;
272 if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
273 NewFtpMessage(la, pip, lnk, maxpacketsize, ftp_message_type);
275 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
277 if (dlen) { /* only if there's data */
278 sptr = (char *)pip; /* start over at beginning */
279 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may
281 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
282 pflags &= ~WAIT_CRLF;
285 SetProtocolFlags(lnk, pflags);
290 AliasHandleFtpIn(struct libalias *la,
291 struct ip *pip, /* IP packet to examine/patch */
292 struct alias_link *lnk) /* The link to go through (aliased port) */
294 int hlen, tlen, dlen, pflags;
298 /* Calculate data length of TCP packet */
299 tc = (struct tcphdr *)ip_next(pip);
300 hlen = (pip->ip_hl + tc->th_off) << 2;
301 tlen = ntohs(pip->ip_len);
304 /* Place string pointer and beginning of data */
309 * Check that data length is not too long and previous message was
310 * properly terminated with CRLF.
312 pflags = GetProtocolFlags(lnk);
313 if (dlen <= MAX_MESSAGE_SIZE && (pflags & WAIT_CRLF) == 0 &&
314 ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER &&
315 (ParseFtpPortCommand(la, sptr, dlen) != 0 ||
316 ParseFtpEprtCommand(la, sptr, dlen) != 0)) {
318 * Alias active mode client requesting data from server
319 * behind NAT. We need to alias server->client connection
320 * to external address client is connecting to.
322 AddLink(la, GetOriginalAddress(lnk), la->true_addr,
323 GetAliasAddress(lnk), htons(FTP_CONTROL_PORT_NUMBER - 1),
324 htons(la->true_port), GET_ALIAS_PORT, IPPROTO_TCP);
326 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
328 sptr = (char *)pip; /* start over at beginning */
329 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may
332 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
333 pflags &= ~WAIT_CRLF;
336 SetProtocolFlags(lnk, pflags);
341 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen)
349 /* Format: "PORT A,D,D,R,PO,RT". */
351 /* Return if data length is too short. */
355 if (strncasecmp("PORT ", sptr, 5))
358 addr = port = octet = 0;
360 for (i = 5; i < dlen; i++) {
385 octet = 10 * octet + ch - '0';
386 else if (ch == ',') {
387 addr = (addr << 8) + octet;
395 octet = 10 * octet + ch - '0';
396 else if (ch == ',' || state == 12) {
397 port = (port << 8) + octet;
406 la->true_addr.s_addr = htonl(addr);
407 la->true_port = port;
414 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen)
422 /* Format: "EPRT |1|A.D.D.R|PORT|". */
424 /* Return if data length is too short. */
428 if (strncasecmp("EPRT ", sptr, 5))
431 addr = port = octet = 0;
432 delim = '|'; /* XXX gcc -Wuninitialized */
434 for (i = 5; i < dlen; i++) {
444 if (ch == '1') /* IPv4 address */
470 octet = 10 * octet + ch - '0';
471 else if (ch == '.' || state == 10) {
472 addr = (addr << 8) + octet;
486 port = 10 * port + ch - '0';
487 else if (ch == delim)
496 la->true_addr.s_addr = htonl(addr);
497 la->true_port = port;
504 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen)
512 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
514 /* Return if data length is too short. */
518 if (strncmp("227 ", sptr, 4))
521 addr = port = octet = 0;
524 for (i = 4; i < dlen; i++) {
548 octet = 10 * octet + ch - '0';
549 else if (ch == ',') {
550 addr = (addr << 8) + octet;
558 octet = 10 * octet + ch - '0';
559 else if (ch == ',' || (state == 12 && ch == ')')) {
560 port = (port << 8) + octet;
569 la->true_port = port;
570 la->true_addr.s_addr = htonl(addr);
577 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen)
583 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
585 /* Return if data length is too short. */
589 if (strncmp("229 ", sptr, 4))
593 delim = '|'; /* XXX gcc -Wuninitialized */
596 for (i = 4; i < dlen; i++) {
623 port = 10 * port + ch - '0';
624 else if (ch == delim)
639 la->true_port = port;
646 NewFtpMessage(struct libalias *la, struct ip *pip,
647 struct alias_link *lnk,
649 int ftp_message_type)
651 struct alias_link *ftp_lnk;
653 /* Security checks. */
654 if (pip->ip_src.s_addr != la->true_addr.s_addr)
657 if (la->true_port < IPPORT_RESERVED)
660 /* Establish link to address and port found in FTP control message. */
661 ftp_lnk = AddLink(la, la->true_addr, GetDestAddress(lnk),
662 GetAliasAddress(lnk), htons(la->true_port), 0, GET_ALIAS_PORT,
665 if (ftp_lnk != NULL) {
666 int slen, hlen, tlen, dlen;
670 /* Punch hole in firewall */
671 PunchFWHole(ftp_lnk);
674 /* Calculate data length of TCP packet */
675 tc = (struct tcphdr *)ip_next(pip);
676 hlen = (pip->ip_hl + tc->th_off) << 2;
677 tlen = ntohs(pip->ip_len);
680 /* Create new FTP message. */
682 char stemp[MAX_MESSAGE_SIZE + 1];
686 int a1, a2, a3, a4, p1, p2;
687 struct in_addr alias_address;
689 /* Decompose alias address into quad format */
690 alias_address = GetAliasAddress(lnk);
691 ptr = (u_char *) & alias_address.s_addr;
697 alias_port = GetAliasPort(ftp_lnk);
699 /* Prepare new command */
700 switch (ftp_message_type) {
701 case FTP_PORT_COMMAND:
703 /* Decompose alias port into pair format. */
704 ptr = (char *)&alias_port;
708 if (ftp_message_type == FTP_PORT_COMMAND) {
709 /* Generate PORT command string. */
710 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
711 a1, a2, a3, a4, p1, p2);
713 /* Generate 227 reply string. */
715 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
716 a1, a2, a3, a4, p1, p2);
719 case FTP_EPRT_COMMAND:
720 /* Generate EPRT command string. */
721 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
722 a1, a2, a3, a4, ntohs(alias_port));
725 /* Generate 229 reply string. */
726 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
731 /* Save string length for IP header modification */
732 slen = strlen(stemp);
734 /* Copy modified buffer into IP packet. */
737 strncpy(sptr, stemp, maxpacketsize - hlen);
740 /* Save information regarding modified seq and ack numbers */
745 tc = (struct tcphdr *)ip_next(pip);
746 delta = GetDeltaSeqOut(tc->th_seq, lnk);
747 AddSeq(lnk, delta + slen - dlen, pip->ip_hl,
748 pip->ip_len, tc->th_seq, tc->th_off);
751 /* Revise IP header */
755 new_len = htons(hlen + slen);
756 DifferentialChecksum(&pip->ip_sum,
760 pip->ip_len = new_len;
763 /* Compute TCP checksum for revised packet */
768 tc->th_sum = TcpChecksum(pip);
771 #ifdef LIBALIAS_DEBUG
773 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");