]> git.saurik.com Git - apple/network_cmds.git/blobdiff - alias/alias_ftp.c
network_cmds-176.3.3.tar.gz
[apple/network_cmds.git] / alias / alias_ftp.c
index 73769fdc624a9c662b1b1f1cc3a8770dacb1c6cb..7985a95fd3f281818742b857f61489733b96c129 100644 (file)
@@ -1,22 +1,72 @@
+/*
+ * 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(
@@ -59,93 +128,367 @@ struct alias_link *link, /* The link to go through (aliased port) */
 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)
     {
@@ -153,8 +496,11 @@ NewFtpPortCommand(struct ip *pip,
         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 */
@@ -163,33 +509,57 @@ NewFtpPortCommand(struct ip *pip,
         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);
         }