2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2001 Charles Mott <cm@linktel.net>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 #include <sys/cdefs.h>
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.
47 References: RFC 959, RFC 2428.
49 Initial version: August, 1996 (cjm)
52 Brian Somers and Martin Renters identified an IP checksum
53 error for modified IP packets.
55 Version 1.7: January 9, 1996 (cjm)
56 Differential checksum computation for change
59 Version 2.1: May, 1997 (cjm)
60 Very minor changes to conform with
61 local/global/function naming conventions
62 within the packet aliasing module.
64 Version 3.1: May, 2000 (eds)
65 Add support for passive mode, alias the 227 replies.
67 See HISTORY file for record of revisions.
72 #include <sys/param.h>
73 #include <sys/ctype.h>
74 #include <sys/systm.h>
75 #include <sys/kernel.h>
76 #include <sys/module.h>
80 #include <sys/types.h>
85 #include <netinet/in_systm.h>
86 #include <netinet/in.h>
87 #include <netinet/ip.h>
88 #include <netinet/tcp.h>
91 #include <netinet/libalias/alias.h>
92 #include <netinet/libalias/alias_local.h>
93 #include <netinet/libalias/alias_mod.h>
95 #include "alias_local.h"
96 #include "alias_mod.h"
99 #define FTP_CONTROL_PORT_NUMBER 21
102 AliasHandleFtpOut(struct libalias *, struct ip *, struct alias_link *,
105 AliasHandleFtpIn(struct libalias *, struct ip *, struct alias_link *);
108 fingerprint_out(struct libalias *la, struct alias_data *ah)
110 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
113 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER ||
114 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER)
120 fingerprint_in(struct libalias *la, struct alias_data *ah)
122 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL)
124 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER ||
125 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER)
131 protohandler_out(struct libalias *la, struct ip *pip, struct alias_data *ah)
133 AliasHandleFtpOut(la, pip, ah->lnk, ah->maxpktsize);
138 protohandler_in(struct libalias *la, struct ip *pip, struct alias_data *ah)
140 AliasHandleFtpIn(la, pip, ah->lnk);
144 struct proto_handler handlers[] = {
149 .fingerprint = &fingerprint_out,
150 .protohandler = &protohandler_out
156 .fingerprint = &fingerprint_in,
157 .protohandler = &protohandler_in
163 mod_handler(module_t mod, int type, void *data)
170 LibAliasAttachHandlers(handlers);
174 LibAliasDetachHandlers(handlers);
185 moduledata_t alias_mod = {
186 "alias_ftp", mod_handler, NULL
190 DECLARE_MODULE(alias_ftp, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
191 MODULE_VERSION(alias_ftp, 1);
192 MODULE_DEPEND(alias_ftp, libalias, 1, 1, 1);
195 #define FTP_CONTROL_PORT_NUMBER 21
196 #define MAX_MESSAGE_SIZE 128
198 /* FTP protocol flags. */
199 #define WAIT_CRLF 0x01
201 enum ftp_message_type {
209 static int ParseFtpPortCommand(struct libalias *la, char *, int);
210 static int ParseFtpEprtCommand(struct libalias *la, char *, int);
211 static int ParseFtp227Reply(struct libalias *la, char *, int);
212 static int ParseFtp229Reply(struct libalias *la, char *, int);
213 static void NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int);
218 struct ip *pip, /* IP packet to examine/patch */
219 struct alias_link *lnk, /* The link to go through (aliased port) */
220 int maxpacketsize /* The maximum size this packet can grow to
221 (including headers) */ )
223 int hlen, tlen, dlen, pflags;
226 int ftp_message_type;
228 /* Calculate data length of TCP packet */
229 tc = (struct tcphdr *)ip_next(pip);
230 hlen = (pip->ip_hl + tc->th_off) << 2;
231 tlen = ntohs(pip->ip_len);
234 /* Place string pointer and beginning of data */
239 * Check that data length is not too long and previous message was
240 * properly terminated with CRLF.
242 pflags = GetProtocolFlags(lnk);
243 if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
244 ftp_message_type = FTP_UNKNOWN_MESSAGE;
246 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
247 /* When aliasing a client, check for the PORT/EPRT command. */
248 if (ParseFtpPortCommand(la, sptr, dlen))
249 ftp_message_type = FTP_PORT_COMMAND;
250 else if (ParseFtpEprtCommand(la, sptr, dlen))
251 ftp_message_type = FTP_EPRT_COMMAND;
253 /* When aliasing a server, check for the 227/229 reply. */
254 if (ParseFtp227Reply(la, sptr, dlen))
255 ftp_message_type = FTP_227_REPLY;
256 else if (ParseFtp229Reply(la, sptr, dlen)) {
257 ftp_message_type = FTP_229_REPLY;
258 la->true_addr.s_addr = pip->ip_src.s_addr;
262 if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
263 NewFtpMessage(la, pip, lnk, maxpacketsize, ftp_message_type);
266 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
267 if (dlen) { /* only if there's data */
268 sptr = (char *)pip; /* start over at beginning */
269 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
270 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
271 pflags &= ~WAIT_CRLF;
274 SetProtocolFlags(lnk, pflags);
279 AliasHandleFtpIn(struct libalias *la,
280 struct ip *pip, /* IP packet to examine/patch */
281 struct alias_link *lnk) /* The link to go through (aliased port) */
283 int hlen, tlen, dlen, pflags;
287 /* Calculate data length of TCP packet */
288 tc = (struct tcphdr *)ip_next(pip);
289 hlen = (pip->ip_hl + tc->th_off) << 2;
290 tlen = ntohs(pip->ip_len);
293 /* Place string pointer and beginning of data */
298 * Check that data length is not too long and previous message was
299 * properly terminated with CRLF.
301 pflags = GetProtocolFlags(lnk);
302 if (dlen <= MAX_MESSAGE_SIZE && (pflags & WAIT_CRLF) == 0 &&
303 ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER &&
304 (ParseFtpPortCommand(la, sptr, dlen) != 0 ||
305 ParseFtpEprtCommand(la, sptr, dlen) != 0)) {
307 * Alias active mode client requesting data from server
308 * behind NAT. We need to alias server->client connection
309 * to external address client is connecting to.
311 AddLink(la, GetOriginalAddress(lnk), la->true_addr,
312 GetAliasAddress(lnk), htons(FTP_CONTROL_PORT_NUMBER - 1),
313 htons(la->true_port), GET_ALIAS_PORT, IPPROTO_TCP);
315 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
317 sptr = (char *)pip; /* start over at beginning */
318 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may
320 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
321 pflags &= ~WAIT_CRLF;
324 SetProtocolFlags(lnk, pflags);
329 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen)
337 /* Format: "PORT A,D,D,R,PO,RT". */
339 /* Return if data length is too short. */
343 if (strncasecmp("PORT ", sptr, 5))
346 addr = port = octet = 0;
348 for (i = 5; i < dlen; i++) {
373 octet = 10 * octet + ch - '0';
374 else if (ch == ',') {
375 addr = (addr << 8) + octet;
383 octet = 10 * octet + ch - '0';
384 else if (ch == ',' || state == 12) {
385 port = (port << 8) + octet;
394 la->true_addr.s_addr = htonl(addr);
395 la->true_port = port;
402 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen)
410 /* Format: "EPRT |1|A.D.D.R|PORT|". */
412 /* Return if data length is too short. */
416 if (strncasecmp("EPRT ", sptr, 5))
419 addr = port = octet = 0;
420 delim = '|'; /* XXX gcc -Wuninitialized */
422 for (i = 5; i < dlen; i++) {
432 if (ch == '1') /* IPv4 address */
458 octet = 10 * octet + ch - '0';
459 else if (ch == '.' || state == 10) {
460 addr = (addr << 8) + octet;
474 port = 10 * port + ch - '0';
475 else if (ch == delim)
484 la->true_addr.s_addr = htonl(addr);
485 la->true_port = port;
492 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen)
500 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
502 /* Return if data length is too short. */
506 if (strncmp("227 ", sptr, 4))
509 addr = port = octet = 0;
512 for (i = 4; i < dlen; i++) {
536 octet = 10 * octet + ch - '0';
537 else if (ch == ',') {
538 addr = (addr << 8) + octet;
546 octet = 10 * octet + ch - '0';
547 else if (ch == ',' || (state == 12 && ch == ')')) {
548 port = (port << 8) + octet;
557 la->true_port = port;
558 la->true_addr.s_addr = htonl(addr);
565 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen)
571 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
573 /* Return if data length is too short. */
577 if (strncmp("229 ", sptr, 4))
581 delim = '|'; /* XXX gcc -Wuninitialized */
584 for (i = 4; i < dlen; i++) {
611 port = 10 * port + ch - '0';
612 else if (ch == delim)
627 la->true_port = port;
634 NewFtpMessage(struct libalias *la, struct ip *pip,
635 struct alias_link *lnk,
637 int ftp_message_type)
639 struct alias_link *ftp_lnk;
641 /* Security checks. */
642 if (pip->ip_src.s_addr != la->true_addr.s_addr)
645 if (la->true_port < IPPORT_RESERVED)
648 /* Establish link to address and port found in FTP control message. */
649 ftp_lnk = AddLink(la, la->true_addr, GetDestAddress(lnk),
650 GetAliasAddress(lnk), htons(la->true_port), 0, GET_ALIAS_PORT,
653 if (ftp_lnk != NULL) {
654 int slen, hlen, tlen, dlen;
658 /* Punch hole in firewall */
659 PunchFWHole(ftp_lnk);
662 /* Calculate data length of TCP packet */
663 tc = (struct tcphdr *)ip_next(pip);
664 hlen = (pip->ip_hl + tc->th_off) << 2;
665 tlen = ntohs(pip->ip_len);
668 /* Create new FTP message. */
670 char stemp[MAX_MESSAGE_SIZE + 1];
674 int a1, a2, a3, a4, p1, p2;
675 struct in_addr alias_address;
677 /* Decompose alias address into quad format */
678 alias_address = GetAliasAddress(lnk);
679 ptr = (u_char *)&alias_address.s_addr;
685 alias_port = GetAliasPort(ftp_lnk);
687 /* Prepare new command */
688 switch (ftp_message_type) {
689 case FTP_PORT_COMMAND:
691 /* Decompose alias port into pair format. */
692 ptr = (char *)&alias_port;
696 if (ftp_message_type == FTP_PORT_COMMAND) {
697 /* Generate PORT command string. */
698 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
699 a1, a2, a3, a4, p1, p2);
701 /* Generate 227 reply string. */
703 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
704 a1, a2, a3, a4, p1, p2);
707 case FTP_EPRT_COMMAND:
708 /* Generate EPRT command string. */
709 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
710 a1, a2, a3, a4, ntohs(alias_port));
713 /* Generate 229 reply string. */
714 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
719 /* Save string length for IP header modification */
720 slen = strlen(stemp);
722 /* Copy modified buffer into IP packet. */
725 strncpy(sptr, stemp, maxpacketsize - hlen);
728 /* Save information regarding modified seq and ack numbers */
733 tc = (struct tcphdr *)ip_next(pip);
734 delta = GetDeltaSeqOut(tc->th_seq, lnk);
735 AddSeq(lnk, delta + slen - dlen, pip->ip_hl,
736 pip->ip_len, tc->th_seq, tc->th_off);
739 /* Revise IP header */
743 new_len = htons(hlen +
744 MIN(slen, maxpacketsize - hlen));
745 DifferentialChecksum(&pip->ip_sum,
749 pip->ip_len = new_len;
752 /* Compute TCP checksum for revised packet */
755 tc->th_x2 = (TH_RES1 >> 8);
757 tc->th_sum = TcpChecksum(pip);
760 #ifdef LIBALIAS_DEBUG
762 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");