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 *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 &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 ¬es) { 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 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);