--------------------- PatchSet 6531 Date: 2008/01/19 00:45:17 Author: amosjeffries Branch: squid3-ipv6 Tag: (none) Log: EPSV support - part 1 Coded and compiles. TODO run-time testing. Members: src/cf.data.pre:1.68.2.52->1.68.2.53 src/ftp.cc:1.26.2.50->1.26.2.51 src/structs.h:1.66.2.40->1.66.2.41 Index: squid3/src/cf.data.pre =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/cf.data.pre,v retrieving revision 1.68.2.52 retrieving revision 1.68.2.53 diff -u -r1.68.2.52 -r1.68.2.53 --- squid3/src/cf.data.pre 16 Jan 2008 06:20:38 -0000 1.68.2.52 +++ squid3/src/cf.data.pre 19 Jan 2008 00:45:17 -0000 1.68.2.53 @@ -1,6 +1,6 @@ # -# $Id: cf.data.pre,v 1.68.2.52 2008/01/16 06:20:38 amosjeffries Exp $ +# $Id: cf.data.pre,v 1.68.2.53 2008/01/19 00:45:17 amosjeffries Exp $ # # SQUID Web Proxy Cache http://www.squid-cache.org/ # ---------------------------------------------------------- @@ -2326,6 +2326,29 @@ DOC_START If your firewall does not allow Squid to use passive connections, turn off this option. + + Use of ftp_epsv_all option requires this to be ON. +DOC_END + +NAME: ftp_epsv_all +TYPE: onoff +DEFAULT: off +LOC: Config.Ftp.epsv_all +DOC_START + FTP Protocol extensions permit the use of a special "EPSV ALL" command. + + NATs may be able to put the connection on a "fast path" through the + translator, as the EPRT command will never be used and therefore, + translation of the data portion of the segments will never be needed. + + When a client only expects to do two-way FTP transfers this may be useful. + If squid finds that it must do a three-way FTP transfer after issuing + an EPSV ALL command, the FTP session will fail. + + If you have any doubts about this option do not use it. + Squid will nicely attempt all other connection methods. + + Requires ftp_passive to be ON (default) DOC_END NAME: ftp_sanitycheck Index: squid3/src/ftp.cc =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/ftp.cc,v retrieving revision 1.26.2.50 retrieving revision 1.26.2.51 diff -u -r1.26.2.50 -r1.26.2.51 --- squid3/src/ftp.cc 16 Jan 2008 10:00:58 -0000 1.26.2.50 +++ squid3/src/ftp.cc 19 Jan 2008 00:45:18 -0000 1.26.2.51 @@ -1,5 +1,5 @@ /* - * $Id: ftp.cc,v 1.26.2.50 2008/01/16 10:00:58 amosjeffries Exp $ + * $Id: ftp.cc,v 1.26.2.51 2008/01/19 00:45:18 amosjeffries Exp $ * * DEBUG: section 9 File Transfer Protocol (FTP) * AUTHOR: Harvest Derived @@ -66,7 +66,9 @@ SENT_SIZE, SENT_EPRT, SENT_PORT, - SENT_EPSV, + SENT_EPSV_ALL, + SENT_EPSV_1, + SENT_EPSV_2, SENT_PASV, SENT_CWD, SENT_LIST, @@ -84,6 +86,7 @@ { bool isdir; bool pasv_supported; + bool epsv_all_sent; bool skip_whitespace; bool rest_supported; bool pasv_only; @@ -281,9 +284,8 @@ static FTPSM ftpReadEPRT; static FTPSM ftpSendPORT; static FTPSM ftpReadPORT; -//static FTPSM ftpSendEPSV; +static FTPSM ftpSendPassive; static FTPSM ftpReadEPSV; -static FTPSM ftpSendPasv; static FTPSM ftpReadPasv; static FTPSM ftpTraverseDirectory; static FTPSM ftpListDir; @@ -335,9 +337,9 @@ Cwd TraverseDirectory / Mkdir GetFile Mdtm Mdtm Size -Size Pasv -ListDir Pasv -Pasv FileOrList +Size Epsv +ListDir Epsv +Epsv FileOrList FileOrList Rest / Retr / Nlst / List / Mkdir (PUT /xxx;type=d) Rest Retr Retr / Nlst / List DataRead* (on datachannel) @@ -361,9 +363,11 @@ ftpReadSize, /* SENT_SIZE */ ftpReadEPRT, /* SENT_EPRT */ ftpReadPORT, /* SENT_PORT */ - ftpReadEPSV, /* SENT_EPSV */ + ftpReadEPSV, /* SENT_EPSV_ALL */ + ftpReadEPSV, /* SENT_EPSV_1 */ + ftpReadEPSV, /* SENT_EPSV_2 */ ftpReadPasv, /* SENT_PASV */ - ftpReadCwd, /* SENT_CWD */ + ftpReadCwd, /* SENT_CWD */ ftpReadList, /* SENT_LIST */ ftpReadList, /* SENT_NLST */ ftpReadRest, /* SENT_REST */ @@ -2172,7 +2176,7 @@ ftpState->flags.isdir = 1; } - ftpSendPasv(ftpState); + ftpSendPassive(ftpState); } static void @@ -2209,7 +2213,7 @@ ftpSendSize(FtpStateData * ftpState) { /* check the server control channel is still available */ - if(!ftpState || !ftpState->haveControlChannel("ftpSendPasv")) + if(!ftpState || !ftpState->haveControlChannel("ftpSendSize")) return; /* Only send SIZE for binary transfers. The returned size @@ -2223,7 +2227,7 @@ ftpState->state = SENT_SIZE; } else /* Skip to next state no non-binary transfers */ - ftpSendPasv(ftpState); + ftpSendPassive(ftpState); } static void @@ -2247,59 +2251,168 @@ return; } - ftpSendPasv(ftpState); + ftpSendPassive(ftpState); } -/* FIXME INET6 : EPASV command not yet coded. */ -/* static void -ftpSendEPSV(FtpStateData* ftpstate) +ftpReadEPSV(FtpStateData* ftpState) { - // TODO. -} -*/ + int code = ftpState->ctrl.replycode; + char h1, h2, h3, h4; + int n; + u_short port; + IPAddress ipa_remote; + int fd = ftpState->data.fd; + char *buf; + debugs(9, 3, HERE); -static void -ftpReadEPSV(FtpStateData* ftpstate) -{ - /* FIXME INET6 : EPASV command not yet coded. */ + if (code != 229 && code != 522) { + debugs(9, 2, "EPSV not supported by remote end"); + ftpState->state = SENT_EPSV_1; /* simulate having failed EPSV 1 (last EPSV to try before shifting to PASV) */ + ftpSendPassive(ftpState); + return; + } - /* Failover to PASV */ - ftpReadPasv(ftpstate); -} + if(code == 522) { + /* server response with list of supported methods */ + /* 522 Network protocol not supported, use (1) */ + /* 522 Network protocol not supported, use (1,2) */ + debugs(9, 5, "scanning: " << ftpState->ctrl.last_reply); + + buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "(1,2)"); +/*AYJ*/debugs(9,5, HERE << "located protocol method info as '" << buf << "'"); + if(strcmp(buf, "(1)") == 0) { + ftpState->state = SENT_EPSV_2; /* simulate having sent and failed EPSV 2 */ + ftpSendPassive(ftpState); + } + else if(strcmp(buf, "(2)") == 0) { +#if USE_IPV6 + /* If server only supports EPSV 2 and we have already tried that. Go straight to EPRT */ + if(ftpState->state == SENT_EPSV_2) { + ftpSendEPRT(ftpState); + } + else { + /* or try the next Passive mode down the chain. */ + ftpPassive(ftpState); + } +#else + /* We do not support IPv6. Remote server requires it. + So we must simulate having failed all EPSV methods. */ + ftpState->state = SENT_EPSV_1; + ftpSendPassive(ftpState); +#endif + } + return; + } + + /* 229 Entering Extended Passive Mode (|||port|) */ + /* ANSI sez [^0-9] is undefined, it breaks on Watcom cc */ + debugs(9, 5, "scanning: " << ftpState->ctrl.last_reply); + + buf = ftpState->ctrl.last_reply + strcspn(ftpState->ctrl.last_reply, "("); +/*AYJ*/debugs(9,5, HERE << "located EPSV port info as '" << buf << "'"); + + n = sscanf(buf, "(%c%c%c%hu%c)", &h1, &h2, &h3, &port, &h4); + if (h1 != h2 || h1 != h3 || h1 != h4) { + debugs(9, DBG_IMPORTANT, "Invalid EPSV reply from " << + fd_table[ftpState->ctrl.fd].ipaddr << ": " << + ftpState->ctrl.last_reply); + + ftpSendPassive(ftpState); + return; + } + + if (0 == port) { + debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " << + fd_table[ftpState->ctrl.fd].ipaddr << ": " << + ftpState->ctrl.last_reply); + + ftpSendPassive(ftpState); + return; + } + + if (Config.Ftp.sanitycheck) { + if (port < 1024) { + debugs(9, DBG_IMPORTANT, "Unsafe EPSV reply from " << + fd_table[ftpState->ctrl.fd].ipaddr << ": " << + ftpState->ctrl.last_reply); + + ftpSendPassive(ftpState); + return; + } + } + + ftpState->data.port = port; + + ftpState->data.host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr); + + safe_free(ftpState->ctrl.last_command); + safe_free(ftpState->ctrl.last_reply); + + ftpState->ctrl.last_command = xstrdup("Connect to server data port"); + + debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port); + + commConnectStart(fd, ftpState->data.host, port, FtpStateData::ftpPasvCallback, ftpState); +} + +/** + * Send Passive connection request. + * Default method is to use modern EPSV request. + * The failover mechanism should check for previous state and re-call with alternates on failure. + */ static void -ftpSendPasv(FtpStateData * ftpState) +ftpSendPassive(FtpStateData * ftpState) { IPAddress addr; struct addrinfo *AI = NULL; - /* check the server control channel is still available */ - if(!ftpState || !ftpState->haveControlChannel("ftpSendPasv")) + /** Checks the server control channel is still available before running. */ + if(!ftpState || !ftpState->haveControlChannel("ftpSendPassive")) return; debugs(9, 3, HERE); + /** \par + * Checks for EPSV ALL special conditions: + * If enabled to be sent, squid MUST NOT send any other connect method. + * If 'ALL' is sent and fails the entire FTP Session fails. */ + if(Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent && (ftpState->state == SENT_EPSV_1 || ftpState->state == SENT_EPSV_2) ) { + debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent."); + ftpFail(ftpState); + return; + } + + /** \par + * Checks for 'HEAD' method request and passes off for special handling by FtpStateData::processHeadResponse(). */ if (ftpState->request->method == METHOD_HEAD && (ftpState->flags.isdir || ftpState->theSize != -1)) { ftpState->processHeadResponse(); // may call serverComplete return; } + /** \par + * Closes any old FTP-Data connection which may exist. */ if (ftpState->data.fd >= 0) { - /* Close old connection */ comm_close(ftpState->data.fd); ftpState->data.fd = -1; } + /** \par + * Checks for previous EPSV/PASV failures on this server/session. + * Diverts to EPRT immediately if they are not working. */ if (!ftpState->flags.pasv_supported) { ftpSendEPRT(ftpState); return; } + /** \par + * Locates the Address of the remote server. */ addr.InitAddrInfo(AI); if (getsockname(ftpState->ctrl.fd, AI->ai_addr, &AI->ai_addrlen)) { + /** If it cannot be located the FTP Session is killed. */ addr.FreeAddrInfo(AI); debugs(9, DBG_CRITICAL, HERE << "getsockname(" << ftpState->ctrl.fd << ",'" << addr << "',...): " << xstrerror()); ftpFail(ftpState); @@ -2310,7 +2423,7 @@ addr.FreeAddrInfo(AI); - /* Open data channel with the same local address as control channel BUT not the port! */ + /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */ addr.SetPort(0); int fd = comm_open(SOCK_STREAM, IPPROTO_TCP, @@ -2337,11 +2450,43 @@ */ ftpState->data.fd = fd; - snprintf(cbuf, 1024, "PASV\r\n"); + /** \par + * Send EPSV (ALL,2,1) or PASV on the control channel. + * + \item EPSV ALL is used if enabled. + \item EPSV 2 is used if ALL is disabled and IPv6 is available. + \item EPSV 1 is used if EPSV 2 (IPv6) fails or is not available. + \item PASV is used if EPSV 1 fails. + */ + switch(ftpState->state) { + case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */ + snprintf(cbuf, 1024, "PASV\r\n"); + ftpState->state = SENT_PASV; + break; - ftpState->writeCommand(cbuf); + case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */ + snprintf(cbuf, 1024, "EPSV 1\r\n"); + ftpState->state = SENT_EPSV_1; + break; - ftpState->state = SENT_PASV; + default: + if(Config.Ftp.epsv_all) { + snprintf(cbuf, 1024, "EPSV ALL\r\n"); + ftpState->state = SENT_EPSV_ALL; + } + else { +#if USE_IPV6 + snprintf(cbuf, 1024, "EPSV 2\r\n"); + ftpState->state = SENT_EPSV_2; +#else + snprintf(cbuf, 1024, "EPSV 1\r\n"); + ftpState->state = SENT_EPSV_1; +#endif + } + break; + } + + ftpState->writeCommand(cbuf); /* * ugly hack for ftp servers like ftp.netscape.com that sometimes @@ -2369,7 +2514,7 @@ #if ICAP_CLIENT if (icapAccessCheckPending) { - debugs(9,3, HERE << "returning from ftpSendPasv due to icapAccessCheckPending"); + debugs(9,3, HERE << "returning due to icapAccessCheckPending"); return; } #endif @@ -2475,7 +2620,7 @@ debugs(9, 3, HERE); if (status != COMM_OK) { - debugs(9, 3, HERE << "failed to connect. Retrying without PASV."); + debugs(9, 2, HERE << "Failed to connect. Retrying without PASV."); ftpState->fwd->dontRetry(false); /* this is a retryable error */ ftpState->fwd->ftpPasvFailed(true); ftpState->failed(ERR_NONE, 0); @@ -2572,6 +2717,11 @@ if(!ftpState || !ftpState->haveControlChannel("ftpSendPort")) return; + if(Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) { + debugs(9, DBG_IMPORTANT, "FTP does not allow PORT method after 'EPSV ALL' has been sent."); + return; + } + debugs(9, 3, HERE); ftpState->flags.pasv_supported = 0; fd = ftpOpenListenSocket(ftpState, 0); @@ -2631,6 +2781,11 @@ struct addrinfo *AI = NULL; char buf[MAX_IPSTRLEN]; + if(Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) { + debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent."); + return; + } + debugs(9, 3, HERE); ftpState->flags.pasv_supported = 0; fd = ftpOpenListenSocket(ftpState, 0); @@ -2705,13 +2860,6 @@ return; } - if (flag != COMM_OK) { - debugs(9, 1, HERE << "comm_accept(" << newfd << "): " << xstrerr(xerrno)); - /** \todo XXX Need to set error message */ - ftpFail(ftpState); - return; - } - /** \par * When squid.conf ftp_sanitycheck is enabled, check the new connection is actually being * made by the remote client which is connected to the FTP control socket. @@ -2730,10 +2878,15 @@ } } + if (flag != COMM_OK) { + debugs(9, DBG_IMPORTANT, HERE << "Comm Error for FD " << newfd << ": " << xstrerr(xerrno)); + /** \todo XXX Need to set error message */ + ftpFail(ftpState); + return; + } + /**\par * Replace the Listen socket with the accepted data socket */ -/* only if they are different sockets. on pre-opened data sockets they may be identical */ -/* the close will in those cases destroy all the newly found state for the connection */ debugs(9, 3, HERE << "Connected data socket on FD " << newfd); /* remember that details is state for fd, it will be erased by the following comm_close() */ Index: squid3/src/structs.h =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/structs.h,v retrieving revision 1.66.2.40 retrieving revision 1.66.2.41 diff -u -r1.66.2.40 -r1.66.2.41 --- squid3/src/structs.h 8 Jan 2008 21:56:29 -0000 1.66.2.40 +++ squid3/src/structs.h 19 Jan 2008 00:45:19 -0000 1.66.2.41 @@ -1,6 +1,6 @@ /* - * $Id: structs.h,v 1.66.2.40 2008/01/08 21:56:29 amosjeffries Exp $ + * $Id: structs.h,v 1.66.2.41 2008/01/19 00:45:19 amosjeffries Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -598,6 +598,7 @@ int list_wrap; char *anon_user; int passive; + int epsv_all; int sanitycheck; int telnet; }