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.