I am all for the firewall understanding a bit about the applications behind it - it makes rule writing much easier.
But thanks for your patch - its always nice to have other options.
Tim
Scott L. Burson wrote:
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.