This patch add support for ftp_epsv ACLs. The following syntax is supported:
  ftp_epsv on
  ftp_epsv off
  ftp_epsv deny acl1
  ftp_epsv allow acl2 acl3

The action of the first matching line wins. The "ftp_epsv on|off" syntax
is supported for backward compatibility and it is equivalent to ftp_epsv
allow|deny all.
Mixing legacy and new ACLs configuration is not allowed.


I must note that the server side EPRT and PORT ftp commands, when they
are used causes crashes to squid (eg when setting the "ftp_passive
off"). This patch does not try to support ACLs for ftp_eprt.

This is a Measurement Factory project
ftp_epsv ACLs

This patch add support for ftp_epsv ACLs. The following syntax is supported:
  ftp_epsv on
  ftp_epsv off
  ftp_epsv deny acl1
  ftp_epsv allow acl2 acl3

The action of the first matching line wins. The "ftp_epsv on|off" syntax is
supported for backward compatibility.

This is a Measurement Factory project

=== modified file 'src/SquidConfig.h'
--- src/SquidConfig.h	2014-01-12 17:51:12 +0000
+++ src/SquidConfig.h	2014-02-14 18:42:29 +0000
@@ -378,50 +378,51 @@
         acl_access *redirector;
         acl_access *store_id;
         acl_access *reply;
         AclAddress *outgoing_address;
 #if USE_HTCP
 
         acl_access *htcp;
         acl_access *htcp_clr;
 #endif
 
 #if USE_SSL
         acl_access *ssl_bump;
 #endif
 #if FOLLOW_X_FORWARDED_FOR
         acl_access *followXFF;
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
         /// spoof_client_ip squid.conf acl.
         /// nil unless configured
         acl_access* spoof_client_ip;
+
+        acl_access *ftp_epsv;
     } accessList;
     AclDenyInfoList *denyInfoList;
 
     struct {
         size_t list_width;
         int list_wrap;
         char *anon_user;
         int passive;
         int epsv_all;
-        int epsv;
         int eprt;
         int sanitycheck;
         int telnet;
     } Ftp;
     RefreshPattern *Refresh;
 
     struct _cacheSwap {
         RefCount<SwapDir> *swapDirs;
         int n_allocated;
         int n_configured;
         /// number of disk processes required to support all cache_dirs
         int n_strands;
     } cacheSwap;
     /*
      * I'm sick of having to keep doing this ..
      */
 #define INDEXSD(i)   (Config.cacheSwap.swapDirs[(i)].getRaw())
 
     struct {
         char *directory;

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2014-02-10 09:59:19 +0000
+++ src/cache_cf.cc	2014-03-17 11:39:50 +0000
@@ -230,40 +230,44 @@
 #endif /* CURRENTLY_UNUSED */
 #endif /* USE_WCCPv2 */
 
 static void parsePortCfg(AnyP::PortCfg **, const char *protocol);
 #define parse_PortCfg(l) parsePortCfg((l), token)
 static void dump_PortCfg(StoreEntry *, const char *, const AnyP::PortCfg *);
 static void free_PortCfg(AnyP::PortCfg **);
 
 #if USE_SSL
 static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign);
 static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign);
 static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign);
 static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt);
 static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt);
 static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt);
 static void parse_sslproxy_ssl_bump(acl_access **ssl_bump);
 static void dump_sslproxy_ssl_bump(StoreEntry *entry, const char *name, acl_access *ssl_bump);
 static void free_sslproxy_ssl_bump(acl_access **ssl_bump);
 #endif /* USE_SSL */
 
+static void parse_ftp_epsv(acl_access **ftp_epsv);
+static void dump_ftp_epsv(StoreEntry *entry, const char *name, acl_access *ftp_epsv);
+static void free_ftp_epsv(acl_access **ftp_epsv);
+
 static void parse_b_size_t(size_t * var);
 static void parse_b_int64_t(int64_t * var);
 
 static bool parseNamedIntList(const char *data, const String &name, Vector<int> &list);
 
 static void parse_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap);
 static void dump_CpuAffinityMap(StoreEntry *const entry, const char *const name, const CpuAffinityMap *const cpuAffinityMap);
 static void free_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap);
 
 static int parseOneConfigFile(const char *file_name, unsigned int depth);
 
 static void parse_configuration_includes_quoted_values(bool *recognizeQuotedValues);
 static void dump_configuration_includes_quoted_values(StoreEntry *const entry, const char *const name, bool recognizeQuotedValues);
 static void free_configuration_includes_quoted_values(bool *recognizeQuotedValues);
 
 /*
  * LegacyParser is a parser for legacy code that uses the global
  * approach.  This is static so that it is only exposed to cache_cf.
  * Other modules needing access to a ConfigParser should have it
  * provided to them in their parserFOO methods.
@@ -4790,40 +4794,110 @@
     delete *header;
     *header = NULL;
 }
 
 static void parse_note(Notes *notes)
 {
     assert(notes);
     notes->parse(LegacyParser);
 }
 
 static void dump_note(StoreEntry *entry, const char *name, Notes &notes)
 {
     notes.dump(entry, name);
 }
 
 static void free_note(Notes *notes)
 {
     notes->clean();
 }
 
+static bool FtpEspvDeprecated = false;
+static void parse_ftp_epsv(acl_access **ftp_epsv)
+{
+    allow_t ftpEpsvDeprecatedAction;
+    bool ftpEpsvIsDeprecatedRule = false;
+
+    char *t = ConfigParser::PeekAtToken();
+    if (!t){
+        self_destruct();
+        return;
+    }
+
+    if (!strcmp(t, "off")) {
+        (void)ConfigParser::NextToken();
+        ftpEpsvIsDeprecatedRule = true;
+        ftpEpsvDeprecatedAction = allow_t(ACCESS_DENIED);
+    } else if (!strcmp(t, "on")) {
+        (void)ConfigParser::NextToken();
+        ftpEpsvIsDeprecatedRule = true;
+        ftpEpsvDeprecatedAction = allow_t(ACCESS_ALLOWED);
+    }
+
+    // Check for mixing "ftp_epsv on|off" and "ftp_epsv allow|deny .." rules:
+    //   1) if this line is "ftp_epsv allow|deny ..." and already exist rules of "ftp_epsv on|off"
+    //   2) if this line is "ftp_epsv on|off" and already exist rules of "ftp_epsv allow|deny ..."
+    // then abort
+    if ((!ftpEpsvIsDeprecatedRule && FtpEspvDeprecated) ||
+        (ftpEpsvIsDeprecatedRule && !FtpEspvDeprecated && *ftp_epsv != NULL)) {
+        debugs(3, DBG_CRITICAL, "FATAL: do not mix \"ftp_epsv on|off\" cfg lines with \"ftp_epsv allow|deny ...\" cfg lines. Update your ftp_epsv rules.");
+        self_destruct();
+    }
+
+    if (ftpEpsvIsDeprecatedRule) {
+        // overwrite previous ftp_epsv lines
+        delete *ftp_epsv;
+        if (ftpEpsvDeprecatedAction == allow_t(ACCESS_DENIED)) {
+            Acl::AndNode *ftpEpsvRule = new Acl::AndNode;
+            ftpEpsvRule->context("(ftp_epsv rule)", config_input_line);
+            ACL *a = ACL::FindByName("all");
+            if (!a) {
+                self_destruct();
+                return;
+            }
+            ftpEpsvRule->add(a);
+            *ftp_epsv = new Acl::Tree;
+            (*ftp_epsv)->context("(ftp_epsv rules)", config_input_line);
+            (*ftp_epsv)->add(ftpEpsvRule, ftpEpsvDeprecatedAction);
+        } else
+            *ftp_epsv = NULL;
+        FtpEspvDeprecated = true;
+    } else {
+        aclParseAccessLine(cfg_directive, LegacyParser, ftp_epsv);
+    }
+}
+
+static void dump_ftp_epsv(StoreEntry *entry, const char *name, acl_access *ftp_epsv)
+{
+    if (ftp_epsv) {
+        wordlist *lines = ftp_epsv->treeDump(name, NULL);
+        dump_wordlist(entry, lines);
+        wordlistDestroy(&lines);
+    }
+}
+
+static void free_ftp_epsv(acl_access **ftp_epsv)
+{
+    free_acl_access(ftp_epsv);
+    FtpEspvDeprecated = false;
+}
+
 static void
 parse_configuration_includes_quoted_values(bool *recognizeQuotedValues)
 {
     int val = 0;
     parse_onoff(&val);
 
     // If quoted values is set to on then enable new strict mode parsing
     if (val) {
         ConfigParser::RecognizeQuotedValues = true;
         ConfigParser::StrictMode = true;
     } else {
         ConfigParser::RecognizeQuotedValues = false;
         ConfigParser::StrictMode = false;
     }
 }
 
 static void
 dump_configuration_includes_quoted_values(StoreEntry *const entry, const char *const name, bool recognizeQuotedValues)
 {
     int val = ConfigParser::RecognizeQuotedValues ? 1 : 0;

=== modified file 'src/cf.data.depend'
--- src/cf.data.depend	2013-08-29 09:21:53 +0000
+++ src/cf.data.depend	2014-02-14 18:42:29 +0000
@@ -57,20 +57,21 @@
 QosConfig
 refreshpattern
 removalpolicy
 size_t
 IpAddress_list
 string
 string
 time_msec
 time_t
 tristate
 uri_whitespace
 u_short
 wccp2_method
 wccp2_amethod
 wccp2_service
 wccp2_service_info
 wordlist
 sslproxy_ssl_bump	acl
 sslproxy_cert_sign	acl
 sslproxy_cert_adapt	acl
+ftp_epsv                acl

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2014-01-30 21:24:44 +0000
+++ src/cf.data.pre	2014-03-17 09:31:57 +0000
@@ -4460,55 +4460,63 @@
 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) for any effect.
 DOC_END
 
 NAME: ftp_epsv
-TYPE: onoff
-DEFAULT: on
-LOC: Config.Ftp.epsv
+TYPE: ftp_epsv
+DEFAULT: none
+LOC: Config.accessList.ftp_epsv
 DOC_START
 	FTP Protocol extensions permit the use of a special "EPSV" command.
 
 	NATs may be able to put the connection on a "fast path" through the
 	translator using EPSV, as the EPRT command will never be used
 	and therefore, translation of the data portion of the segments 
 	will never be needed.
 
-	Turning this OFF will prevent EPSV being attempted.
-	WARNING: Doing so will convert Squid back to the old behavior with all
-	the related problems with external NAT devices/layers.
+	EPSV is often required to interoperate with FTP servers on IPv6
+	networks. On the other hand, it may break some IPv4 servers.
+
+	By default, EPSV may try EPSV with any FTP server. To fine tune
+	that decision, you may restrict EPSV to certain clients or servers
+	using ACLs:
+
+		ftp_epsv allow|deny al1 acl2 ...
+
+	WARNING: Disabling EPSV may cause problems with external NAT and IPv6.
 
+	Only fast ACLs are supported.
 	Requires ftp_passive to be ON (default) for any effect.
 DOC_END
 
 NAME: ftp_eprt
 TYPE: onoff
 DEFAULT: on
 LOC: Config.Ftp.eprt
 DOC_START
 	FTP Protocol extensions permit the use of a special "EPRT" command.
 
 	This extension provides a protocol neutral alternative to the
 	IPv4-only PORT command. When supported it enables active FTP data
 	channels over IPv6 and efficient NAT handling.
 
 	Turning this OFF will prevent EPRT being attempted and will skip
 	straight to using PORT for IPv4 servers.
 
 	Some devices are known to not handle this extension correctly and
 	may result in crashes. Devices which suport EPRT enough to fail
 	cleanly will result in Squid attempting PORT anyway. This directive

=== modified file 'src/ftp.cc'
--- src/ftp.cc	2014-02-07 13:45:20 +0000
+++ src/ftp.cc	2014-03-17 09:30:01 +0000
@@ -14,40 +14,41 @@
  *  incorporates software developed and/or copyrighted by other
  *  sources; see the CREDITS file for full details.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
 #include "squid.h"
+#include "acl/FilledChecklist.h"
 #include "comm.h"
 #include "comm/ConnOpener.h"
 #include "comm/TcpAcceptor.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
 #include "compat/strtoll.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "fde.h"
 #include "FwdState.h"
 #include "html_quote.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeader.h"
 #include "HttpHeaderRange.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ip/tools.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "mime.h"
@@ -2543,63 +2544,69 @@
 
     case SENT_EPSV_2: /* EPSV IPv6 failed. Try EPSV IPv4 */
         if (ftpState->ctrl.conn->local.isIPv4()) {
             debugs(9, 5, HERE << "FTP Channel is IPv4 (" << ftpState->ctrl.conn->remote << ") attempting EPSV 1 after EPSV ALL has failed.");
             snprintf(cbuf, CTRL_BUFLEN, "EPSV 1\r\n");
             ftpState->state = SENT_EPSV_1;
             break;
         } else if (ftpState->flags.epsv_all_sent) {
             debugs(9, DBG_IMPORTANT, "FTP does not allow PASV method after 'EPSV ALL' has been sent.");
             ftpFail(ftpState);
             return;
         }
         // else fall through to skip EPSV 1
 
     case SENT_EPSV_1: /* EPSV options exhausted. Try PASV now. */
         debugs(9, 5, HERE << "FTP Channel (" << ftpState->ctrl.conn->remote << ") rejects EPSV connection attempts. Trying PASV instead.");
         snprintf(cbuf, CTRL_BUFLEN, "PASV\r\n");
         ftpState->state = SENT_PASV;
         break;
 
-    default:
-        if (!Config.Ftp.epsv) {
+    default: {
+        bool doEpsv = true;
+        if (Config.accessList.ftp_epsv) {
+            ACLFilledChecklist checklist(Config.accessList.ftp_epsv, ftpState->fwd->request, NULL);
+            doEpsv = (checklist.fastCheck() == ACCESS_ALLOWED);
+        }
+        if (!doEpsv) {
             debugs(9, 5, HERE << "EPSV support manually disabled. Sending PASV for FTP Channel (" << ftpState->ctrl.conn->remote <<")");
             snprintf(cbuf, CTRL_BUFLEN, "PASV\r\n");
             ftpState->state = SENT_PASV;
         } else if (Config.Ftp.epsv_all) {
             debugs(9, 5, HERE << "EPSV ALL manually enabled. Attempting with FTP Channel (" << ftpState->ctrl.conn->remote <<")");
             snprintf(cbuf, CTRL_BUFLEN, "EPSV ALL\r\n");
             ftpState->state = SENT_EPSV_ALL;
             /* block other non-EPSV connections being attempted */
             ftpState->flags.epsv_all_sent = true;
         } else {
             if (ftpState->ctrl.conn->local.isIPv6()) {
                 debugs(9, 5, HERE << "FTP Channel (" << ftpState->ctrl.conn->remote << "). Sending default EPSV 2");
                 snprintf(cbuf, CTRL_BUFLEN, "EPSV 2\r\n");
                 ftpState->state = SENT_EPSV_2;
             }
             if (ftpState->ctrl.conn->local.isIPv4()) {
                 debugs(9, 5, HERE << "Channel (" << ftpState->ctrl.conn->remote <<"). Sending default EPSV 1");
                 snprintf(cbuf, CTRL_BUFLEN, "EPSV 1\r\n");
                 ftpState->state = SENT_EPSV_1;
             }
         }
+    }
         break;
     }
 
     ftpState->writeCommand(cbuf);
 
     /*
      * ugly hack for ftp servers like ftp.netscape.com that sometimes
      * dont acknowledge PASV commands. Use connect timeout to be faster then read timeout (minutes).
      */
     typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall =  JobCallback(9, 5,
                                       TimeoutDialer, ftpState, FtpStateData::ftpTimeout);
     commSetConnTimeout(ftpState->ctrl.conn, Config.Timeout.connect, timeoutCall);
 }
 
 void
 FtpStateData::processHeadResponse()
 {
     debugs(9, 5, HERE << "handling HEAD response");
     ftpSendQuit(this);

Reply via email to