1.2b15: FTP datachannel PORT support

From: Henrik Nordstrom <hno@dont-contact.us>
Date: Sun, 15 Feb 1998 15:54:08 +0100

This is a multi-part message in MIME format.

--------------539DE3F440955F2C24A40D66
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable

Here is a patch that adds support for PORT and default mode FTP data
channel.

1. Try PASV
2. If that fails, try PORT
3. If all fails, fall back on using the default data channel (same local
port as used by the control connection).

Now I think the FTP gateway in Squid is fylly compliant with RFC 1738
(URL) and STD 9 (FTP) for fetching files and directory listings.

FTP PUT support remains to be done. As I said earlier the hard part here
is in client_side.c: to get request bodies down to the protocols in a
clean manner.

---
Henrik Nordstr=F6m
Sparetime Squid Hacker
--------------539DE3F440955F2C24A40D66
Content-Type: text/plain; charset=us-ascii; name="squid-1.2.beta15.ftp_PORT_and_default.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="squid-1.2.beta15.ftp_PORT_and_default.patch"
Index: squid/src/comm.c
diff -u squid/src/comm.c:1.1.1.12 squid/src/comm.c:1.1.1.12.2.1
--- squid/src/comm.c:1.1.1.12	Sat Feb 14 01:00:23 1998
+++ squid/src/comm.c	Sun Feb 15 15:35:01 1998
@@ -259,6 +259,8 @@
     F = &fd_table[new_socket];
     if (!(flags & COMM_NOCLOEXEC))
 	commSetCloseOnExec(new_socket);
+    if ((flags & COMM_REUSEADDR))
+	commSetReuseAddr(new_socket);
     if (port > (u_short) 0) {
 	commSetNoLinger(new_socket);
 	if (opt_reuseaddr)
Index: squid/src/defines.h
diff -u squid/src/defines.h:1.1.1.7 squid/src/defines.h:1.1.1.7.2.1
--- squid/src/defines.h:1.1.1.7	Sat Feb 14 01:00:23 1998
+++ squid/src/defines.h	Sun Feb 15 15:35:02 1998
@@ -40,6 +40,7 @@
 
 #define COMM_NONBLOCKING	0x01
 #define COMM_NOCLOEXEC		0x02
+#define COMM_REUSEADDR		0x04
 
 #ifdef HAVE_SYSLOG
 #define debug(SECTION, LEVEL) \
Index: squid/src/ftp.c
diff -u squid/src/ftp.c:1.1.1.11 squid/src/ftp.c:1.1.1.11.2.1
--- squid/src/ftp.c:1.1.1.11	Sat Feb 14 01:00:25 1998
+++ squid/src/ftp.c	Sun Feb 15 15:35:02 1998
@@ -1184,6 +1184,9 @@
 	    if (strstr(ftpState->ctrl.message->key, "NetWare"))
 		EBIT_SET(ftpState->flags, FTP_SKIP_WHITESPACE);
 	ftpSendUser(ftpState);
+    } else if (code == 120) {
+	debug(9, 3) ("FTP server is busy: %s\n", ftpState->ctrl.message);
+	return;
     } else {
 	ftpFail(ftpState);
     }
@@ -1435,19 +1438,27 @@
 ftpSendPasv(FtpStateData * ftpState)
 {
     int fd;
+    struct sockaddr_in addr;
+    int addr_len;
     if (ftpState->data.fd >= 0) {
 	/* We are already connected, reuse this connection. */
 	ftpRestOrList(ftpState);
 	return;
     }
-    assert(ftpState->data.fd < 0);
     if (!EBIT_TEST(ftpState->flags, FTP_PASV_SUPPORTED)) {
 	ftpSendPort(ftpState);
 	return;
     }
+    addr_len = sizeof (addr);
+    if ( getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
+	debug(9, 0) ("ftpSendPasv: getsockname(%d,..): %s\n",
+	    ftpState->ctrl.fd, xstrerror());
+	addr.sin_addr = Config.Addrs.tcp_outgoing;
+    }
+    /* Open data channel with the same local address as control channel */
     fd = comm_open(SOCK_STREAM,
 	0,
-	Config.Addrs.tcp_outgoing,
+	addr.sin_addr,
 	0,
 	COMM_NONBLOCKING,
 	storeUrl(ftpState->entry));
@@ -1482,7 +1493,8 @@
     debug(9, 3) ("This is ftpReadPasv\n");
     if (code != 227) {
 	debug(9, 3) ("PASV not supported by remote end\n");
-	/* XXX Shouldn't we get rid of the PASV socket? */
+	comm_close(ftpState->data.fd);
+	ftpState->data.fd = -1;
 	ftpSendPort(ftpState);
 	return;
     }
@@ -1535,19 +1547,114 @@
     ftpRestOrList(ftpState);
 }
 
+static int
+ftpOpenListenSocket(FtpStateData *ftpState, int fallback)
+{
+    int fd;
+    struct sockaddr_in addr;
+    int addr_len;
+    int on=1;
+    u_short port=0;
+    /* Set up a listen socket on the same local address as the control connection. */
+    addr_len = sizeof (addr);
+    if ( getsockname(ftpState->ctrl.fd, (struct sockaddr *) &addr, &addr_len)) {
+	debug(9, 0) ("ftpOpenListenSocket: getsockname(%d,..): %s\n",
+	    ftpState->ctrl.fd, xstrerror());
+	return -1;
+    }
+    /* REUSEADDR is needed in fallback mode, since the same port is used for both
+     * control and data
+     */
+    if (fallback) {
+	setsockopt(ftpState->ctrl.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
+	port = ntohs(addr.sin_port);
+    }
+    fd = comm_open(SOCK_STREAM,
+	0,
+	addr.sin_addr,
+	port,
+	COMM_NONBLOCKING | ( fallback ? COMM_REUSEADDR : 0 ),
+	storeUrl(ftpState->entry));
+    if (fd < 0) {
+	debug(9, 0) ("ftpOpenListenSocket: comm_open failed\n");
+	return -1;
+    }
+    if ( comm_listen(fd) <0 ) {
+	comm_close(fd);
+	return -1;
+    }
+    ftpState->data.fd = fd;
+    ftpState->data.port = comm_local_port(fd);;
+    ftpState->data.host = NULL;
+    return fd;
+}
+
 static void
 ftpSendPort(FtpStateData * ftpState)
 {
+    int fd;
+    struct sockaddr_in addr;
+    int addr_len;
+    unsigned char *addrptr;
+    unsigned char *portptr;
     debug(9, 3) ("This is ftpSendPort\n");
     EBIT_CLR(ftpState->flags, FTP_PASV_SUPPORTED);
-    /* XXX Not implemented? ftpFail??? */
+    fd = ftpOpenListenSocket(ftpState, 0);
+    addr_len = sizeof (addr);
+    if ( getsockname(fd, (struct sockaddr *) &addr, &addr_len)) {
+	debug(9, 0) ("ftpSendPort: getsockname(%d,..): %s\n", fd , xstrerror());
+	/* XXX Need to set error message */
+	ftpFail(ftpState);
+	return;
+    }
+    addrptr=(unsigned char *)&addr.sin_addr.s_addr;
+    portptr=(unsigned char *)&addr.sin_port;
+    snprintf(cbuf, 1024, "PORT %d,%d,%d,%d,%d,%d\r\n",
+	addrptr[0],addrptr[1],addrptr[2],addrptr[3],
+	portptr[0],portptr[1]);
+    ftpWriteCommand(cbuf, ftpState);
+    ftpState->state = SENT_PORT;
 }
 
 static void
-ftpReadPort(FtpStateData * ftpStateNotUsed)
+ftpReadPort(FtpStateData * ftpState)
 {
+    int code = ftpState->ctrl.replycode;
     debug(9, 3) ("This is ftpReadPort\n");
-    /* XXX Not implemented? */
+    if (code != 200) {
+	/* Fall back on using the same port as the control connection */
+	debug(9, 3) ("PORT not supported by remote end\n");
+	comm_close(ftpState->data.fd);
+	ftpOpenListenSocket(ftpState, 1);
+    }
+    ftpRestOrList(ftpState);
+}
+
+/* "read" handler to accept data connection */
+static void
+ftpAcceptDataConnection(int fd, void *data)
+{
+    FtpStateData *ftpState = data;
+    struct sockaddr_in peer, me;
+    debug(9, 3) ("ftpAcceptDataConnection\n");
+
+    fd=comm_accept( fd, &peer, &me );
+    if ( fd < 0) {
+	debug(9, 1) ("ftpHandleDataAccept: comm_accept(%d): %s",fd,xstrerror());
+	/* XXX Need to set error message */
+	ftpFail(ftpState);
+	return;
+    }
+    comm_close(ftpState->data.fd); /* Listen socket replaced by data socket */
+    ftpState->data.fd = fd;
+    ftpState->data.port = ntohs(peer.sin_port);
+    ftpState->data.host = xstrdup(inet_ntoa(peer.sin_addr));
+    /* XXX We should have a flag to track connect state...
+     *    host NULL -> not connected, port == local port
+     *    host set  -> connected, port == remote port
+     */
+    /* Restart state (SENT_NLST/LIST/RETR)*/
+    FTP_SM_FUNCS[ftpState->state] (ftpState);
 }
 
 static void
@@ -1623,7 +1730,8 @@
 {
     int code = ftpState->ctrl.replycode;
     debug(9, 3) ("This is ftpReadList\n");
-    if (code == 150 || code == 125) {
+    if ( code == 125 || ( code == 150 && ftpState->data.host )) {
+	/* Begin data transfer */
 	ftpAppendSuccessHeader(ftpState);
 	commSetSelect(ftpState->data.fd,
 	    COMM_SELECT_READ,
@@ -1637,7 +1745,15 @@
 	commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
 	commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
 	return;
-    } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
+    } else if (code == 150) {
+	/* Accept data channel */
+	commSetSelect(ftpState->data.fd,
+	    COMM_SELECT_READ,
+	    ftpAcceptDataConnection,
+	    ftpState,
+	    Config.Timeout.read);
+	return;
+    } else if (!EBIT_TEST(ftpState->flags, FTP_TRIED_NLST) && code > 300 ) {
 	ftpSendNlst(ftpState);
     } else {
 	ftpFail(ftpState);
@@ -1659,7 +1775,8 @@
 {
     int code = ftpState->ctrl.replycode;
     debug(9, 3) ("This is ftpReadRetr\n");
-    if (code >= 100 && code < 200) {
+    if ( code == 125 || ( code == 150 && ftpState->data.host )) {
+	/* Begin data transfer */
 	debug(9, 3) ("ftpReadRetr: reading data channel\n");
 	ftpAppendSuccessHeader(ftpState);
 	commSetSelect(ftpState->data.fd,
@@ -1673,13 +1790,23 @@
 	 * on the data socket */
 	commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
 	commSetTimeout(ftpState->data.fd, Config.Timeout.read, ftpTimeout, ftpState);
-    } else {
+    } else if (code == 150) {
+	/* Accept data channel */
+	commSetSelect(ftpState->data.fd,
+	    COMM_SELECT_READ,
+	    ftpAcceptDataConnection,
+	    ftpState,
+	    Config.Timeout.read);
+	return;
+    } else if (code >= 300) {
 	if (!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
 	    /* Try this as a directory missing trailing slash... */
 	    ftpHackShortcut(ftpState, ftpSendCwd);
 	} else {
 	    ftpFail(ftpState);
 	}
+    } else {
+	ftpFail(ftpState);
     }
 }
 
--------------539DE3F440955F2C24A40D66--
Received on Tue Jul 29 2003 - 13:15:46 MDT

This archive was generated by hypermail pre-2.1.9 : Tue Dec 09 2003 - 16:11:42 MST