+/*
+ * Copyright (c) 2000-2002 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * The contents of this file constitute Original Code as defined in and
+ * are subject to the Apple Public Source License Version 1.1 (the
+ * "License"). You may not use this file except in compliance with the
+ * License. Please obtain a copy of the License at
+ * http://www.apple.com/publicsource and read it before using this file.
+ *
+ * This Original Code and all software distributed under the License are
+ * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
+ * License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+/*-
+ * Copyright (c) 2001 Charles Mott <cmott@scientech.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Based upon:
+ * $FreeBSD: src/lib/libalias/alias_ftp.c,v 1.5.2.4 2001/08/21 03:50:25 brian Exp $
+ */
+
/*
Alias_ftp.c performs special processing for FTP sessions under
- TCP. Specifically, when a PORT command from the client side
- is sent, it is intercepted and modified. The address is changed
- to the gateway machine and an aliasing port is used.
+ TCP. Specifically, when a PORT/EPRT command from the client
+ side or 227/229 reply from the server is sent, it is intercepted
+ and modified. The address is changed to the gateway machine
+ and an aliasing port is used.
- For this routine to work, the PORT command must fit entirely
- into a single TCP packet. This is typically the case, but exceptions
+ For this routine to work, the message must fit entirely into a
+ single TCP packet. This is typically the case, but exceptions
can easily be envisioned under the actual specifications.
Probably the most troubling aspect of the approach taken here is
- that the new PORT command will typically be a different length, and
+ that the new message will typically be a different length, and
this causes a certain amount of bookkeeping to keep track of the
changes of sequence and acknowledgment numbers, since the client
machine is totally unaware of the modification to the TCP stream.
- This software is placed into the public domain with no restrictions
- on its distribution.
+ References: RFC 959, RFC 2428.
Initial version: August, 1996 (cjm)
error for modified IP packets.
Version 1.7: January 9, 1996 (cjm)
- Differental checksum computation for change
+ Differential checksum computation for change
in IP packet length.
-
+
Version 2.1: May, 1997 (cjm)
Very minor changes to conform with
local/global/function naming conventions
- withing the packet alising module.
+ within the packet aliasing module.
+
+ Version 3.1: May, 2000 (eds)
+ Add support for passive mode, alias the 227 replies.
See HISTORY file for record of revisions.
*/
/* Includes */
#include <ctype.h>
-#include <stdio.h>
+#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in_systm.h>
#include "alias_local.h"
-static void NewFtpPortCommand(struct ip *, struct alias_link *, struct in_addr, u_short, int);
+#define FTP_CONTROL_PORT_NUMBER 21
+#define MAX_MESSAGE_SIZE 128
+
+enum ftp_message_type {
+ FTP_PORT_COMMAND,
+ FTP_EPRT_COMMAND,
+ FTP_227_REPLY,
+ FTP_229_REPLY,
+ FTP_UNKNOWN_MESSAGE
+};
+static int ParseFtpPortCommand(char *, int);
+static int ParseFtpEprtCommand(char *, int);
+static int ParseFtp227Reply(char *, int);
+static int ParseFtp229Reply(char *, int);
+static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
+static struct in_addr true_addr; /* in network byte order. */
+static u_short true_port; /* in host byte order. */
void
AliasHandleFtpOut(
int maxpacketsize /* The maximum size this packet can grow to (including headers) */)
{
int hlen, tlen, dlen;
- struct in_addr true_addr;
- u_short true_port;
char *sptr;
struct tcphdr *tc;
-
+ int ftp_message_type;
+
/* Calculate data length of TCP packet */
tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
hlen = (pip->ip_hl + tc->th_off) << 2;
tlen = ntohs(pip->ip_len);
dlen = tlen - hlen;
-/* Return is data length is too long or too short */
- if (dlen<10 || dlen>80)
- return;
-
/* Place string pointer and beginning of data */
- sptr = (char *) pip;
+ sptr = (char *) pip;
sptr += hlen;
-/* Parse through string using state diagram method */
- {
- char ch, zero;
- int i, state;
- u_long a1, a2, a3, a4;
- u_short p1, p2;
-
- a1=0; a2=0; a3=0; a4=0; p1=0; p2=0;
- zero = '0';
- state=-4;
- for (i=0; i<dlen; i++)
- {
- ch = sptr[i];
- switch (state)
- {
- case -4: if (ch == 'P') state=-3; else return; break;
- case -3: if (ch == 'O') state=-2; else return; break;
- case -2: if (ch == 'R') state=-1; else return; break;
- case -1: if (ch == 'T') state= 0; else return; break;
-
- case 0 :
- if (isdigit(ch)) {a1=ch-zero; state=1 ;} break;
- case 1 :
- if (isdigit(ch)) a1=10*a1+ch-zero; else state=2 ; break;
- case 2 :
- if (isdigit(ch)) {a2=ch-zero; state=3 ;} break;
- case 3 :
- if (isdigit(ch)) a2=10*a2+ch-zero; else state=4 ; break;
- case 4 :
- if (isdigit(ch)) {a3=ch-zero; state=5 ;} break;
- case 5 :
- if (isdigit(ch)) a3=10*a3+ch-zero; else state=6 ; break;
- case 6 :
- if (isdigit(ch)) {a4=ch-zero; state=7 ;} break;
- case 7 :
- if (isdigit(ch)) a4=10*a4+ch-zero; else state=8 ; break;
- case 8 :
- if (isdigit(ch)) {p1=ch-zero; state=9 ;} break;
- case 9 :
- if (isdigit(ch)) p1=10*p1+ch-zero; else state=10; break;
- case 10:
- if (isdigit(ch)) {p2=ch-zero; state=11;} break;
- case 11:
- if (isdigit(ch)) p2=10*p2+ch-zero; break;
- }
- }
+/*
+ * Check that data length is not too long and previous message was
+ * properly terminated with CRLF.
+ */
+ if (dlen <= MAX_MESSAGE_SIZE && GetLastLineCrlfTermed(link)) {
+ ftp_message_type = FTP_UNKNOWN_MESSAGE;
+
+ if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
+/*
+ * When aliasing a client, check for the PORT/EPRT command.
+ */
+ if (ParseFtpPortCommand(sptr, dlen))
+ ftp_message_type = FTP_PORT_COMMAND;
+ else if (ParseFtpEprtCommand(sptr, dlen))
+ ftp_message_type = FTP_EPRT_COMMAND;
+ } else {
+/*
+ * When aliasing a server, check for the 227/229 reply.
+ */
+ if (ParseFtp227Reply(sptr, dlen))
+ ftp_message_type = FTP_227_REPLY;
+ else if (ParseFtp229Reply(sptr, dlen))
+ ftp_message_type = FTP_229_REPLY;
+ }
+
+ if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
+ NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
+ }
+
+/* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
+
+ if (dlen) { /* only if there's data */
+ sptr = (char *) pip; /* start over at beginning */
+ tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
+ SetLastLineCrlfTermed(link,
+ (sptr[tlen-2] == '\r') && (sptr[tlen-1] == '\n'));
+ }
+}
+
+static int
+ParseFtpPortCommand(char *sptr, int dlen)
+{
+ char ch;
+ int i, state;
+ u_int32_t addr;
+ u_short port;
+ u_int8_t octet;
+
+ /* Format: "PORT A,D,D,R,PO,RT". */
+
+ /* Return if data length is too short. */
+ if (dlen < 18)
+ return 0;
+
+ addr = port = octet = 0;
+ state = -4;
+ for (i = 0; i < dlen; i++) {
+ ch = sptr[i];
+ switch (state) {
+ case -4: if (ch == 'P') state++; else return 0; break;
+ case -3: if (ch == 'O') state++; else return 0; break;
+ case -2: if (ch == 'R') state++; else return 0; break;
+ case -1: if (ch == 'T') state++; else return 0; break;
+
+ case 0:
+ if (isspace(ch))
+ break;
+ else
+ state++;
+ case 1: case 3: case 5: case 7: case 9: case 11:
+ if (isdigit(ch)) {
+ octet = ch - '0';
+ state++;
+ } else
+ return 0;
+ break;
+ case 2: case 4: case 6: case 8:
+ if (isdigit(ch))
+ octet = 10 * octet + ch - '0';
+ else if (ch == ',') {
+ addr = (addr << 8) + octet;
+ state++;
+ } else
+ return 0;
+ break;
+ case 10: case 12:
+ if (isdigit(ch))
+ octet = 10 * octet + ch - '0';
+ else if (ch == ',' || state == 12) {
+ port = (port << 8) + octet;
+ state++;
+ } else
+ return 0;
+ break;
+ }
+ }
+
+ if (state == 13) {
+ true_addr.s_addr = htonl(addr);
+ true_port = port;
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+ParseFtpEprtCommand(char *sptr, int dlen)
+{
+ char ch, delim;
+ int i, state;
+ u_int32_t addr;
+ u_short port;
+ u_int8_t octet;
+
+ /* Format: "EPRT |1|A.D.D.R|PORT|". */
+
+ /* Return if data length is too short. */
+ if (dlen < 18)
+ return 0;
+
+ addr = port = octet = 0;
+ delim = '|'; /* XXX gcc -Wuninitialized */
+ state = -4;
+ for (i = 0; i < dlen; i++) {
+ ch = sptr[i];
+ switch (state)
+ {
+ case -4: if (ch == 'E') state++; else return 0; break;
+ case -3: if (ch == 'P') state++; else return 0; break;
+ case -2: if (ch == 'R') state++; else return 0; break;
+ case -1: if (ch == 'T') state++; else return 0; break;
+
+ case 0:
+ if (!isspace(ch)) {
+ delim = ch;
+ state++;
+ }
+ break;
+ case 1:
+ if (ch == '1') /* IPv4 address */
+ state++;
+ else
+ return 0;
+ break;
+ case 2:
+ if (ch == delim)
+ state++;
+ else
+ return 0;
+ break;
+ case 3: case 5: case 7: case 9:
+ if (isdigit(ch)) {
+ octet = ch - '0';
+ state++;
+ } else
+ return 0;
+ break;
+ case 4: case 6: case 8: case 10:
+ if (isdigit(ch))
+ octet = 10 * octet + ch - '0';
+ else if (ch == '.' || state == 10) {
+ addr = (addr << 8) + octet;
+ state++;
+ } else
+ return 0;
+ break;
+ case 11:
+ if (isdigit(ch)) {
+ port = ch - '0';
+ state++;
+ } else
+ return 0;
+ break;
+ case 12:
+ if (isdigit(ch))
+ port = 10 * port + ch - '0';
+ else if (ch == delim)
+ state++;
+ else
+ return 0;
+ break;
+ }
+ }
+
+ if (state == 13) {
+ true_addr.s_addr = htonl(addr);
+ true_port = port;
+ return 1;
+ } else
+ return 0;
+}
- if (state == 11)
+static int
+ParseFtp227Reply(char *sptr, int dlen)
+{
+ char ch;
+ int i, state;
+ u_int32_t addr;
+ u_short port;
+ u_int8_t octet;
+
+ /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
+
+ /* Return if data length is too short. */
+ if (dlen < 17)
+ return 0;
+
+ addr = port = octet = 0;
+
+ state = -3;
+ for (i = 0; i < dlen; i++) {
+ ch = sptr[i];
+ switch (state)
{
- true_port = htons((p1<<8) + p2);
- true_addr.s_addr = htonl((a1<<24) + (a2<<16) +(a3<<8) + a4);
- NewFtpPortCommand(pip, link, true_addr, true_port, maxpacketsize);
- }
+ case -3: if (ch == '2') state++; else return 0; break;
+ case -2: if (ch == '2') state++; else return 0; break;
+ case -1: if (ch == '7') state++; else return 0; break;
+
+ case 0:
+ if (ch == '(')
+ state++;
+ break;
+ case 1: case 3: case 5: case 7: case 9: case 11:
+ if (isdigit(ch)) {
+ octet = ch - '0';
+ state++;
+ } else
+ return 0;
+ break;
+ case 2: case 4: case 6: case 8:
+ if (isdigit(ch))
+ octet = 10 * octet + ch - '0';
+ else if (ch == ',') {
+ addr = (addr << 8) + octet;
+ state++;
+ } else
+ return 0;
+ break;
+ case 10: case 12:
+ if (isdigit(ch))
+ octet = 10 * octet + ch - '0';
+ else if (ch == ',' || (state == 12 && ch == ')')) {
+ port = (port << 8) + octet;
+ state++;
+ } else
+ return 0;
+ break;
+ }
}
+
+ if (state == 13) {
+ true_port = port;
+ true_addr.s_addr = htonl(addr);
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+ParseFtp229Reply(char *sptr, int dlen)
+{
+ char ch, delim;
+ int i, state;
+ u_short port;
+
+ /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
+
+ /* Return if data length is too short. */
+ if (dlen < 11)
+ return 0;
+
+ port = 0;
+ delim = '|'; /* XXX gcc -Wuninitialized */
+
+ state = -3;
+ for (i = 0; i < dlen; i++) {
+ ch = sptr[i];
+ switch (state)
+ {
+ case -3: if (ch == '2') state++; else return 0; break;
+ case -2: if (ch == '2') state++; else return 0; break;
+ case -1: if (ch == '9') state++; else return 0; break;
+
+ case 0:
+ if (ch == '(')
+ state++;
+ break;
+ case 1:
+ delim = ch;
+ state++;
+ break;
+ case 2: case 3:
+ if (ch == delim)
+ state++;
+ else
+ return 0;
+ break;
+ case 4:
+ if (isdigit(ch)) {
+ port = ch - '0';
+ state++;
+ } else
+ return 0;
+ break;
+ case 5:
+ if (isdigit(ch))
+ port = 10 * port + ch - '0';
+ else if (ch == delim)
+ state++;
+ else
+ return 0;
+ break;
+ case 6:
+ if (ch == ')')
+ state++;
+ else
+ return 0;
+ break;
+ }
+ }
+
+ if (state == 7) {
+ true_port = port;
+ return 1;
+ } else
+ return 0;
}
static void
-NewFtpPortCommand(struct ip *pip,
- struct alias_link *link,
- struct in_addr true_addr,
- u_short true_port,
- int maxpacketsize)
-{
+NewFtpMessage(struct ip *pip,
+ struct alias_link *link,
+ int maxpacketsize,
+ int ftp_message_type)
+{
struct alias_link *ftp_link;
-/* Establish link to address and port found in PORT command */
+/* Security checks. */
+ if (ftp_message_type != FTP_229_REPLY &&
+ pip->ip_src.s_addr != true_addr.s_addr)
+ return;
+
+ if (true_port < IPPORT_RESERVED)
+ return;
+
+/* Establish link to address and port found in FTP control message. */
ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
- true_port, 0, IPPROTO_TCP);
+ htons(true_port), 0, IPPROTO_TCP, 1);
if (ftp_link != NULL)
{
struct tcphdr *tc;
#ifndef NO_FW_PUNCH
-/* Punch hole in firewall */
- PunchFWHole(ftp_link);
+ if (ftp_message_type == FTP_PORT_COMMAND ||
+ ftp_message_type == FTP_EPRT_COMMAND) {
+ /* Punch hole in firewall */
+ PunchFWHole(ftp_link);
+ }
#endif
/* Calculate data length of TCP packet */
tlen = ntohs(pip->ip_len);
dlen = tlen - hlen;
-/* Create new PORT command */
+/* Create new FTP message. */
{
- char stemp[80];
+ char stemp[MAX_MESSAGE_SIZE + 1];
char *sptr;
u_short alias_port;
u_char *ptr;
- int a1, a2, a3, a4, p1, p2;
+ int a1, a2, a3, a4, p1, p2;
struct in_addr alias_address;
-
+
/* Decompose alias address into quad format */
alias_address = GetAliasAddress(link);
ptr = (u_char *) &alias_address.s_addr;
a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
-/* Decompose alias port into pair format */
- alias_port = GetAliasPort(ftp_link);
- ptr = (char *) &alias_port;
- p1 = *ptr++; p2=*ptr;
-
-/* Generate command string */
- sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
- a1,a2,a3,a4,p1,p2);
+ alias_port = GetAliasPort(ftp_link);
+
+ switch (ftp_message_type)
+ {
+ case FTP_PORT_COMMAND:
+ case FTP_227_REPLY:
+ /* Decompose alias port into pair format. */
+ ptr = (char *) &alias_port;
+ p1 = *ptr++; p2=*ptr;
+
+ if (ftp_message_type == FTP_PORT_COMMAND) {
+ /* Generate PORT command string. */
+ sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
+ a1,a2,a3,a4,p1,p2);
+ } else {
+ /* Generate 227 reply string. */
+ sprintf(stemp,
+ "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
+ a1,a2,a3,a4,p1,p2);
+ }
+ break;
+ case FTP_EPRT_COMMAND:
+ /* Generate EPRT command string. */
+ sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
+ a1,a2,a3,a4,ntohs(alias_port));
+ break;
+ case FTP_229_REPLY:
+ /* Generate 229 reply string. */
+ sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
+ ntohs(alias_port));
+ break;
+ }
/* Save string length for IP header modification */
slen = strlen(stemp);
-/* Copy into IP packet */
+/* Copy modified buffer into IP packet. */
sptr = (char *) pip; sptr += hlen;
strncpy(sptr, stemp, maxpacketsize-hlen);
}