]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
From: Henrik Nordstrom <hno@hem.passagen.se>
authorwessels <>
Wed, 18 Feb 1998 07:38:53 +0000 (07:38 +0000)
committerwessels <>
Wed, 18 Feb 1998 07:38:53 +0000 (07:38 +0000)
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.

src/comm.cc
src/defines.h
src/ftp.cc

index a2522853ed3773a59dce8b5646cd035e2c4b737f..8bf055ff9320ba09f0964f36dbe73cc540f7c673 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: comm.cc,v 1.228 1998/02/17 23:28:22 wessels Exp $
+ * $Id: comm.cc,v 1.229 1998/02/18 00:38:53 wessels Exp $
  *
  * DEBUG: section 5     Socket Functions
  * AUTHOR: Harvest Derived
@@ -260,6 +260,8 @@ comm_open(int sock_type,
     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 ea5cef30335db9114b3377af8aa2d05c00a82eaa..7d9ec63c10f45f09f475a6da99a837e39a6d86e1 100644 (file)
@@ -40,6 +40,7 @@
 
 #define COMM_NONBLOCKING       0x01
 #define COMM_NOCLOEXEC         0x02
+#define COMM_REUSEADDR         0x04
 
 #ifdef HAVE_SYSLOG
 #define debug(SECTION, LEVEL) \
index 6f9569b3febc0d9f1ec8c239d4eb2357e8c59faa..443448059c56da209bb5d3547fa5a425f54d0793 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: ftp.cc,v 1.193 1998/02/13 17:47:38 wessels Exp $
+ * $Id: ftp.cc,v 1.194 1998/02/18 00:38:54 wessels Exp $
  *
  * DEBUG: section 9     File Transfer Protocol (FTP)
  * AUTHOR: Harvest Derived
@@ -1184,6 +1184,9 @@ ftpReadWelcome(FtpStateData * ftpState)
            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 @@ static void
 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 @@ ftpReadPasv(FtpStateData * ftpState)
     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 @@ ftpPasvCallback(int fd, int status, void *data)
     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 @@ ftpReadList(FtpStateData * ftpState)
 {
     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 @@ ftpReadList(FtpStateData * ftpState)
        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 @@ ftpReadRetr(FtpStateData * ftpState)
 {
     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 @@ ftpReadRetr(FtpStateData * ftpState)
         * 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);
     }
 }