[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Reverse mode added to `ftp-proxy'



Hi,

The enclosed patch extends `ftp-proxy' (in `src/libexec/ftp-proxy/') to
support reverse proxying.  This allows a public FTP server to have a private
IP address, with incoming requests proxied to it by the firewall (i.e., the
published address of the server is that of the firewall).

-- 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.