Hi,

I also wanted a reverse FTP proxy.  Not being aware of the existing patch to 
`ftp-proxy', I developed my own.  I think it is better in two ways:

(1) It supports EPSV in reverse mode (though I haven't tested it with v6
addresses).

(2) It is better documented.

I hope we can get one of these patches integrated into the official sources.
It's kindof a drag finding out I didn't need to do this in the first place.
Well, I sent my patch to the OpenBSD gods -- we'll see if they accept it.

-- Scott

*** ftp-proxy.c.~1~     2003-08-22 14:50:34.000000000 -0700
--- ftp-proxy.c 2003-12-29 14:35:18.000000000 -0800
***************
*** 128,133 ****
--- 128,134 ----
  struct sockaddr_in real_server_sa;
  struct sockaddr_in client_listen_sa;
  struct sockaddr_in server_listen_sa;
+ struct sockaddr_in proxy_sa;
  
  int client_listen_socket = -1;        /* Only used in PASV mode */
  int client_data_socket = -1;  /* Connected socket to real client */
***************
*** 138,147 ****
  int AnonFtpOnly;
  int Verbose;
  int NatMode;
  
  char ClientName[NI_MAXHOST];
  char RealServerName[NI_MAXHOST];
! char OurName[NI_MAXHOST];
  
  char *User = "proxy";
  char *Group;
--- 139,151 ----
  int AnonFtpOnly;
  int Verbose;
  int NatMode;
+ int Reverse;
+ char *RevServer;
  
  char ClientName[NI_MAXHOST];
  char RealServerName[NI_MAXHOST];
! char OurNameForServer[NI_MAXHOST];
! char OurNameForClient[NI_MAXHOST];
  
  char *User = "proxy";
  char *Group;
***************
*** 573,579 ****
--- 577,587 ----
  
        if (connect(client_data_socket, (struct sockaddr *) &client_listen_sa,
            sizeof(client_listen_sa)) != 0) {
+               unsigned a = ntohl(client_listen_sa.sin_addr.s_addr);
                syslog(LOG_INFO, "cannot connect data channel (%m)");
+               debuglog(1, "connect failed to %d.%d.%d.%d:%d",
+                        a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
+                        ntohs(client_listen_sa.sin_port));
                exit(EX_NOHOST);
        }
  
***************
*** 727,737 ****
                                        j += rv;
                        } while (j >= 0 && j < i);
                }
!       } else if (!NatMode && (strncasecmp((char *)client->line_buffer,
!           "epsv", strlen("epsv")) == 0)) {
  
                /*
!                * If we aren't in NAT mode, deal with EPSV.
                 * EPSV is a problem - Unlike PASV, the reply from the
                 * server contains *only* a port, we can't modify the reply
                 * to the client and get the client to connect to us without
--- 735,746 ----
                                        j += rv;
                        } while (j >= 0 && j < i);
                }
!       } else if (!NatMode && !Reverse &&
!                  (strncasecmp((char *)client->line_buffer,
!                               "epsv", strlen("epsv")) == 0)) {
  
                /*
!                * If we aren't in NAT or reverse mode, deal with EPSV.
                 * EPSV is a problem - Unlike PASV, the reply from the
                 * server contains *only* a port, we can't modify the reply
                 * to the client and get the client to connect to us without
***************
*** 740,745 ****
--- 749,757 ----
                 * so this will wait until we have the right solution for rule
                 * additions/deletions in pf.
                 *
+                * EPSV is not a problem in reverse mode, since the address
+                * the client has for the server is that of the proxy.
+                *
                 * in the meantime we just tell the client we don't do it,
                 * and most clients should fall back to using PASV.
                 */
***************
*** 925,934 ****
  
                new_dataconn(0);
                connection_mode = PASV_MODE;
!               iap = &(server->sa.sin_addr);
  
                debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
!                   htons(client_listen_sa.sin_port));
  
                snprintf(tbuf, sizeof(tbuf),
                    "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n",
--- 937,946 ----
  
                new_dataconn(0);
                connection_mode = PASV_MODE;
!               iap = &(proxy_sa.sin_addr);
  
                debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
!                   ntohs(client_listen_sa.sin_port));
  
                snprintf(tbuf, sizeof(tbuf),
                    "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n",
***************
*** 938,943 ****
--- 950,995 ----
                    ((u_char *)&client_listen_sa.sin_port)[1]);
                debuglog(1, "to client (modified): %s", tbuf);
                sendbuf = tbuf;
+       } else if (Reverse && code == 229) {
+               char *tailptr, delim[4];
+               int port;
+ 
+               debuglog(1, "Got an EPSV reply");
+               debuglog(1, "{%s}", (char *)server->line_buffer);
+ 
+               tailptr = (char *)strchr((char *)server->line_buffer, '(');
+               if (tailptr == NULL) {
+                       syslog(LOG_NOTICE, "malformed 227 reply");
+                       exit(EX_DATAERR);
+               }
+ 
+               tailptr++; /* skip past space or ( */
+               if (sscanf(tailptr, "%c%c%c%d%c", &delim[0], &delim[1],
+                          &delim[2], &port, &delim[3]) != 5 ||
+                   delim[0] != delim[1] || delim[0] != delim[2] ||
+                   delim[0] != delim[3]) {
+                       syslog(LOG_INFO, "malformed EPSV reply (%s)",
+                              client->line_buffer);
+                       exit(EX_DATAERR);
+               }
+ 
+               server_listen_sa = real_server_sa;
+               server_listen_sa.sin_port = htons(port);
+ 
+               debuglog(1, "server wants us to use port %u", port);
+ 
+               new_dataconn(0);
+               connection_mode = PASV_MODE;
+               iap = &(proxy_sa.sin_addr);
+ 
+               debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
+                   ntohs(client_listen_sa.sin_port));
+ 
+               snprintf(tbuf, sizeof(tbuf),
+                        "229 Entering Extended Passive Mode (|||%u|)\r\n",
+                        ntohs(client_listen_sa.sin_port));
+               debuglog(1, "to client (modified): %s", tbuf);
+               sendbuf = tbuf;
        } else {
   sendit:
                sendbuf = (char *)server->line_buffer;
***************
*** 973,979 ****
        int use_tcpwrapper = 0;
  #endif /* LIBWRAP */
  
!       while ((ch = getopt(argc, argv, "D:g:m:M:t:u:AnVwr")) != -1) {
                char *p;
                switch (ch) {
                case 'A':
--- 1025,1031 ----
        int use_tcpwrapper = 0;
  #endif /* LIBWRAP */
  
!       while ((ch = getopt(argc, argv, "D:g:m:M:t:u:R:AnVwr")) != -1) {
                char *p;
                switch (ch) {
                case 'A':
***************
*** 1007,1012 ****
--- 1059,1068 ----
                case 'r':
                        Use_Rdns = 1; /* look up hostnames */
                        break;
+               case 'R':
+                       Reverse = 1;
+                       RevServer = optarg;
+                       break;
                case 't':
                        timeout_seconds = strtol(optarg, &p, 10);
                        if (!*optarg || *p)
***************
*** 1042,1049 ****
        memset(&client_iob, 0, sizeof(client_iob));
        memset(&server_iob, 0, sizeof(server_iob));
  
!       if (get_proxy_env(0, &real_server_sa, &client_iob.sa) == -1)
!               exit(EX_PROTOCOL);
  
        /*
         * We may now drop root privs, as we have done our ioctl for
--- 1098,1125 ----
        memset(&client_iob, 0, sizeof(client_iob));
        memset(&server_iob, 0, sizeof(server_iob));
  
!       if (!Reverse) {
!               if (get_proxy_env(0, &real_server_sa, &client_iob.sa) == -1)
!                       exit(EX_PROTOCOL);
!       } else {
!               struct addrinfo hints, *res = NULL;
!               int slen = sizeof(client_iob.sa);
!               memset(&hints, 0, sizeof(hints));
!               hints.ai_family = AF_INET;
!               hints.ai_socktype = SOCK_STREAM;
!               i = getaddrinfo(RevServer, "ftp", &hints, &res);
!               if (i == 0) memcpy(&real_server_sa, res->ai_addr,
!                                  res->ai_addrlen);
!               else {
!                       syslog(LOG_ERR, "Unknown `-R' host `%s'", RevServer);
!                       exit(EX_NOHOST);
!               }
!               if (getpeername(0, (struct sockaddr *)&client_iob.sa,
!                               &slen) != 0) {
!                       syslog(LOG_ERR, "getpeername() failed (%m)");
!                       exit(EX_PROTOCOL);
!               }
!       }
  
        /*
         * We may now drop root privs, as we have done our ioctl for
***************
*** 1114,1129 ****
        getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen);
  
        i = getnameinfo((struct sockaddr *)&server_iob.sa,
!           sizeof(server_iob.sa), OurName, sizeof(OurName), NULL, 0, flags);
  
        if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
                debuglog(2, "name resolution failure (local)");
                exit(EX_OSERR);
        }
  
!       debuglog(1, "local socket is %s:%u", OurName,
            ntohs(server_iob.sa.sin_port));
  
        /* ignore SIGPIPE */
        bzero(&new_sa, sizeof(new_sa));
        new_sa.sa_handler = SIG_IGN;
--- 1190,1222 ----
        getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen);
  
        i = getnameinfo((struct sockaddr *)&server_iob.sa,
!           sizeof(server_iob.sa), OurNameForServer, sizeof(OurNameForServer),
!           NULL, 0, flags);
  
        if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
                debuglog(2, "name resolution failure (local)");
                exit(EX_OSERR);
        }
  
!       debuglog(1, "local socket to server is %s:%u", OurNameForServer,
            ntohs(server_iob.sa.sin_port));
  
+       /* ... and similarly for the client. */
+       salen = sizeof(proxy_sa);
+       getsockname(client_iob.fd, (struct sockaddr *)&proxy_sa, &salen);
+ 
+       i = getnameinfo((struct sockaddr *)&proxy_sa,
+           sizeof(server_iob.sa), OurNameForClient, sizeof(OurNameForClient),
+           NULL, 0, flags);
+ 
+       if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
+               debuglog(2, "name resolution failure (local)");
+               exit(EX_OSERR);
+       }
+ 
+       debuglog(1, "local socket to client is %s:%u", OurNameForClient,
+           ntohs(proxy_sa.sin_port));
+ 
        /* ignore SIGPIPE */
        bzero(&new_sa, sizeof(new_sa));
        new_sa.sa_handler = SIG_IGN;
*** ftp-proxy.8.~1~     2003-09-05 05:27:47.000000000 -0700
--- ftp-proxy.8 2003-12-29 15:17:07.000000000 -0800
***************
*** 45,51 ****
  .Sh DESCRIPTION
  .Nm
  is a proxy for the Internet File Transfer Protocol.
! The proxy uses
  .Xr pf 4
  and expects to have the FTP control connection as described in
  .Xr services 5
--- 45,56 ----
  .Sh DESCRIPTION
  .Nm
  is a proxy for the Internet File Transfer Protocol.
! .Pp
! The proxy can function either as a regular "forward" (outbound) proxy, or as
! a "reverse" (inbound) proxy.  Reverse mode allows redirection of incoming
! FTP requests through a firewall to a host with a private IP address.
! .Pp
! In forward mode, the proxy uses
  .Xr pf 4
  and expects to have the FTP control connection as described in
  .Xr services 5
***************
*** 55,60 ****
--- 60,70 ----
  command.
  An example of how to do that is further down in this document.
  .Pp
+ In reverse mode, the proxy is invoked directly by
+ .Xr inetd 8
+ in response to a connection attempt received by the firewall at its own
+ address.
+ .Pp
  The options are as follows:
  .Bl -tag -width Ds
  .It Fl A
***************
*** 122,127 ****
--- 132,141 ----
  lookups for logging and libwrap use.
  By default,
  the proxy does not look up hostnames for libwrap or logging purposes.
+ .It Fl R Ar host
+ Specifies that the proxy is to run in reverse mode, redirecting all requests
+ to
+ .Em host .
  .It Fl t Ar timeout
  Specifies a timeout, in seconds.
  The proxy will exit and close open connections if it sees no data
***************
*** 163,168 ****
--- 177,183 ----
  be written based on the destination as well as the source of FTP connections.
  .El
  .Pp
+ In forward mode,
  .Nm ftp-proxy
  is run from
  .Xr inetd 8
***************
*** 227,232 ****
--- 242,267 ----
  foreign FTP server.
  If one does not pass outgoing connections by default additional rules
  are needed.
+ .Pp
+ In reverse mode,
+ .Nm ftp-proxy
+ is run directly from
+ .Xr inetd 8
+ on the firewall.  A sample
+ .Xr inetd.conf 5
+ line is:
+ .Bd -literal -offset 2n
+ ftp stream tcp nowait root /usr/libexec/ftp-proxy ftp-proxy -R ftp-server
+ .Ed
+ .Pp
+ where
+ .Ar ftp-server
+ is the name or address of the actual FTP server host.
+ .Pp
+ As with forward mode, it is necessary for the
+ .Xr pf.conf 5
+ rules to allow incoming connections on the external interface to the proxy
+ data ports.  See the examples above.
  .Sh SEE ALSO
  .Xr ftp 1 ,
  .Xr pf 4 ,
***************
*** 240,253 ****
  .Sh BUGS
  Extended Passive mode
  .Pq EPSV
! is not supported by the proxy and will not work unless the proxy is run
! in network address translation mode.
! When not in network address translation mode, the proxy returns an error
! to the client, hopefully forcing the client to revert to passive mode
  .Pq PASV
  which is supported.
  EPSV will work in network address translation mode, assuming a
  .Xr pf.conf 5
  setup which allows the EPSV connections through to their destinations.
  .Pp
  IPv6 is not yet supported.
--- 275,289 ----
  .Sh BUGS
  Extended Passive mode
  .Pq EPSV
! is not supported by the proxy in forward mode and will not work unless the
! proxy is run in network address translation mode.  When not in network
! address translation mode, the proxy returns an error to the client,
! hopefully forcing the client to revert to passive mode
  .Pq PASV
  which is supported.
  EPSV will work in network address translation mode, assuming a
  .Xr pf.conf 5
  setup which allows the EPSV connections through to their destinations.
+ EPSV also works in reverse mode.
  .Pp
  IPv6 is not yet supported.

Reply via email to