]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/external_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / external_acl.cc
index 9a6182251a9c81bebadfdcf602dc6883ff1ec440..85019696c41bd6f1cf167686cca8a7aad7854927 100644 (file)
  */
 
 #include "squid.h"
-#include "mgr/Registration.h"
-#include "ExternalACL.h"
-#include "ExternalACLEntry.h"
-#include "auth/UserRequest.h"
-#include "SquidTime.h"
-#include "Store.h"
-#include "fde.h"
-#include "acl/FilledChecklist.h"
 #include "acl/Acl.h"
-#if USE_IDENT
-#include "ident/AclIdent.h"
-#endif
-#include "ip/tools.h"
+#include "acl/FilledChecklist.h"
 #include "client_side.h"
-#include "HttpRequest.h"
-#include "HttpReply.h"
-#include "auth/Acl.h"
-#include "auth/Gadgets.h"
+#include "comm/Connection.h"
+#include "ExternalACLEntry.h"
+#include "ExternalACL.h"
+#include "fde.h"
 #include "helper.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+#include "ip/tools.h"
 #include "MemBuf.h"
+#include "mgr/Registration.h"
+#include "protos.h"
 #include "rfc1738.h"
+#include "SquidTime.h"
+#include "Store.h"
 #include "URLScheme.h"
 #include "wordlist.h"
+#if USE_SSL
+#include "ssl/support.h"
+#endif
+#if USE_AUTH
+#include "auth/Acl.h"
+#include "auth/Gadgets.h"
+#include "auth/UserRequest.h"
+#endif
+#if USE_IDENT
+#include "ident/AclIdent.h"
+#endif
 
 #ifndef DEFAULT_EXTERNAL_ACL_TTL
 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
@@ -91,8 +98,7 @@ class external_acl
 public:
     external_acl *next;
 
-    void add
-    (ExternalACLEntry *);
+    void add(ExternalACLEntry *);
 
     void trimCache();
 
@@ -122,6 +128,7 @@ public:
 
     dlink_list queue;
 
+#if USE_AUTH
     /**
      * Configuration flag. May only be altered by the configuration parser.
      *
@@ -129,6 +136,7 @@ public:
      * details to be processed. If none are available its a fail match.
      */
     bool require_auth;
+#endif
 
     enum {
         QUOTE_METHOD_SHELL = 1,
@@ -141,7 +149,9 @@ public:
 struct _external_acl_format {
     enum format_type {
         EXT_ACL_UNKNOWN,
+#if USE_AUTH
         EXT_ACL_LOGIN,
+#endif
 #if USE_IDENT
         EXT_ACL_IDENT,
 #endif
@@ -176,7 +186,12 @@ struct _external_acl_format {
         EXT_ACL_USER_CERT_RAW,
         EXT_ACL_USER_CERTCHAIN_RAW,
 #endif
+#if USE_AUTH
         EXT_ACL_EXT_USER,
+#endif
+        EXT_ACL_EXT_LOG,
+        EXT_ACL_TAG,
+        EXT_ACL_PERCENT,
         EXT_ACL_END
     } type;
     external_acl_format *next;
@@ -250,12 +265,15 @@ parse_header_token(external_acl_format *format, char *header, const _external_ac
 
     if (member) {
         /* Split in header and member */
-        *member++ = '\0';
+        *member = '\0';
+        ++member;
 
-        if (!xisalnum(*member))
-            format->separator = *member++;
-        else
+        if (!xisalnum(*member)) {
+            format->separator = *member;
+            ++member;
+        } else {
             format->separator = ',';
+        }
 
         format->member = xstrdup(member);
 
@@ -324,7 +342,7 @@ parse_externalAclHelper(external_acl ** list)
             a->negative_ttl = atoi(token + 13);
         } else if (strncmp(token, "children=", 9) == 0) {
             a->children.n_max = atoi(token + 9);
-            debugs(0, 0, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
+            debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
         } else if (strncmp(token, "children-max=", 13) == 0) {
             a->children.n_max = atoi(token + 13);
         } else if (strncmp(token, "children-startup=", 17) == 0) {
@@ -350,11 +368,11 @@ parse_externalAclHelper(external_acl ** list)
                       bind to IPv4/v6 localhost port. */
         } else if (strcmp(token, "ipv4") == 0) {
             if ( !a->local_addr.SetIPv4() ) {
-                debugs(3, 0, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
+                debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
             }
         } else if (strcmp(token, "ipv6") == 0) {
             if (!Ip::EnableIpv6)
-                debugs(3, 0, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
+                debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
             // else nothing to do.
         } else {
             break;
@@ -397,9 +415,11 @@ parse_externalAclHelper(external_acl ** list)
             parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REQUEST);
         } else if (strncmp(token, "%<{", 3) == 0) {
             parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REPLY);
+#if USE_AUTH
         } else if (strcmp(token, "%LOGIN") == 0) {
             format->type = _external_acl_format::EXT_ACL_LOGIN;
             a->require_auth = true;
+#endif
         }
 
 #if USE_IDENT
@@ -448,10 +468,18 @@ parse_externalAclHelper(external_acl ** list)
             format->header = xstrdup(token + 11);
         }
 #endif
+#if USE_AUTH
         else if (strcmp(token, "%EXT_USER") == 0)
             format->type = _external_acl_format::EXT_ACL_EXT_USER;
+#endif
+        else if (strcmp(token, "%EXT_LOG") == 0)
+            format->type = _external_acl_format::EXT_ACL_EXT_LOG;
+        else if (strcmp(token, "%TAG") == 0)
+            format->type = _external_acl_format::EXT_ACL_TAG;
+        else if (strcmp(token, "%%") == 0)
+            format->type = _external_acl_format::EXT_ACL_PERCENT;
         else {
-            debugs(0,0, "ERROR: Unknown Format token " << token);
+            debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
             self_destruct();
         }
 
@@ -544,8 +572,13 @@ dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl
             case _external_acl_format::EXT_ACL_##a: \
                 storeAppendPrintf(sentry, " %%%s", #a); \
                 break
-
+#define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
+            case _external_acl_format::EXT_ACL_##a: \
+                storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
+                break
+#if USE_AUTH
                 DUMP_EXT_ACL_TYPE(LOGIN);
+#endif
 #if USE_IDENT
 
                 DUMP_EXT_ACL_TYPE(IDENT);
@@ -567,26 +600,17 @@ dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl
                 DUMP_EXT_ACL_TYPE(PATH);
                 DUMP_EXT_ACL_TYPE(METHOD);
 #if USE_SSL
-
-            case _external_acl_format::EXT_ACL_USER_CERT_RAW:
-                storeAppendPrintf(sentry, " %%USER_CERT");
-                break;
-
-            case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
-                storeAppendPrintf(sentry, " %%USER_CERTCHAIN");
-                break;
-
-            case _external_acl_format::EXT_ACL_USER_CERT:
-                storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
-                break;
-
-            case _external_acl_format::EXT_ACL_CA_CERT:
-                storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
-                break;
+                DUMP_EXT_ACL_TYPE_FMT(USER_CERT_RAW, " %%USER_CERT_RAW");
+                DUMP_EXT_ACL_TYPE_FMT(USER_CERTCHAIN_RAW, " %%USER_CERTCHAIN_RAW");
+                DUMP_EXT_ACL_TYPE_FMT(USER_CERT, " %%USER_CERT_%s", format->header);
+                DUMP_EXT_ACL_TYPE_FMT(CA_CERT, " %%CA_CERT_%s", format->header);
 #endif
-
+#if USE_AUTH
                 DUMP_EXT_ACL_TYPE(EXT_USER);
-
+#endif
+                DUMP_EXT_ACL_TYPE(EXT_LOG);
+                DUMP_EXT_ACL_TYPE(TAG);
+                DUMP_EXT_ACL_TYPE_FMT(PERCENT, " %%%%");
             default:
                 fatal("unknown external_acl format error");
                 break;
@@ -625,16 +649,14 @@ find_externalAclHelper(const char *name)
 }
 
 void
-
-external_acl::add
-(ExternalACLEntry *anEntry)
+external_acl::add(ExternalACLEntry *anEntry)
 {
     trimCache();
     assert (anEntry->def == NULL);
     anEntry->def = this;
     hash_join(cache, anEntry);
     dlinkAdd(anEntry, &anEntry->lru, &lru_list);
-    cache_entries++;
+    ++cache_entries;
 }
 
 void
@@ -644,7 +666,6 @@ external_acl::trimCache()
         external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
 }
 
-
 /******************************************************************
  * external acl type
  */
@@ -693,17 +714,19 @@ ACLExternal::parse()
 bool
 ACLExternal::valid () const
 {
+#if USE_AUTH
     if (data->def->require_auth) {
         if (authenticateSchemeCount() == 0) {
-            debugs(28, 0, "Can't use proxy auth because no authentication schemes were compiled.");
+            debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
             return false;
         }
 
         if (authenticateActiveSchemeCount() == 0) {
-            debugs(28, 0, "Can't use proxy auth because no authentication schemes are fully configured.");
+            debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
             return false;
         }
     }
+#endif
 
     return true;
 }
@@ -720,21 +743,53 @@ ACLExternal::~ACLExternal()
     safe_free (class_);
 }
 
-static int
+static void
+copyResultsFromEntry(HttpRequest *req, external_acl_entry *entry)
+{
+    if (req) {
+#if USE_AUTH
+        if (entry->user.size())
+            req->extacl_user = entry->user;
+
+        if (entry->password.size())
+            req->extacl_passwd = entry->password;
+#endif
+        if (!req->tag.size())
+            req->tag = entry->tag;
+
+        if (entry->log.size())
+            req->extacl_log = entry->log;
+
+        if (entry->message.size())
+            req->extacl_message = entry->message;
+    }
+}
+
+static allow_t
 aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
 {
-    int result;
-    external_acl_entry *entry;
     const char *key = "";
-    debugs(82, 9, "aclMatchExternal: acl=\"" << acl->def->name << "\"");
-    entry = ch->extacl_entry;
+    debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
+    external_acl_entry *entry = ch->extacl_entry;
 
     if (entry) {
-        if (cbdataReferenceValid(entry) && entry->def == acl->def &&
-                strcmp((char *)entry->key, key) == 0) {
-            /* Ours, use it.. */
+        if (cbdataReferenceValid(entry) && entry->def == acl->def) {
+            /* Ours, use it.. if the key matches */
+            key = makeExternalAclKey(ch, acl);
+            if (strcmp(key, (char*)entry->key) != 0) {
+                debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
+                // too bad. need a new lookup.
+                cbdataReferenceDone(ch->extacl_entry);
+                entry = NULL;
+            }
         } else {
             /* Not valid, or not ours.. get rid of it */
+            debugs(82, 9, HERE << "entry " << entry << " not valid or not ours. Discarded.");
+            if (entry) {
+                debugs(82, 9, HERE << "entry def=" << entry->def << ", our def=" << acl->def);
+                key = makeExternalAclKey(ch, acl);
+                debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "'");
+            }
             cbdataReferenceDone(ch->extacl_entry);
             entry = NULL;
         }
@@ -743,85 +798,104 @@ aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
     external_acl_message = "MISSING REQUIRED INFORMATION";
 
     if (!entry) {
+        debugs(82, 9, HERE << "No helper entry available");
+#if USE_AUTH
         if (acl->def->require_auth) {
-            int ti;
             /* Make sure the user is authenticated */
-            debugs(82, 3, "aclMatchExternal: " << acl->def->name << " check user authenticated.");
-
-            if ((ti = AuthenticateAcl(ch)) != 1) {
-                debugs(82, 2, "aclMatchExternal: " << acl->def->name << " user not authenticated (" << ti << ")");
+            debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
+            const allow_t ti = AuthenticateAcl(ch);
+            if (ti != ACCESS_ALLOWED) {
+                debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
                 return ti;
             }
-            debugs(82, 3, "aclMatchExternal: " << acl->def->name << " user is authenticated.");
+            debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
         }
-
+#endif
         key = makeExternalAclKey(ch, acl);
 
         if (!key) {
             /* Not sufficient data to process */
-            return -1;
+            return ACCESS_DUNNO;
         }
 
         entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
 
-        if (!entry || external_acl_grace_expired(acl->def, entry)) {
-            debugs(82, 2, "aclMatchExternal: " << acl->def->name << "(\"" << key << "\") = lookup needed");
-            debugs(82, 2, "aclMatchExternal: \"" << key << "\": entry=@" <<
+        external_acl_entry *staleEntry = entry;
+        if (entry && external_acl_entry_expired(acl->def, entry))
+            entry = NULL;
+
+        if (entry && external_acl_grace_expired(acl->def, entry)) {
+            // refresh in the background
+            ExternalACLLookup::Start(ch, acl, true);
+            debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
+                   key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
+        }
+
+        if (!entry) {
+            debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
+            debugs(82, 2, HERE << "\"" << key << "\": entry=@" <<
                    entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
 
             if (acl->def->theHelper->stats.queue_size <= (int)acl->def->theHelper->childs.n_active) {
-                debugs(82, 2, "aclMatchExternal: \"" << key << "\": queueing a call.");
-                ch->changeState (ExternalACLLookup::Instance());
-
-                if (entry == NULL) {
-                    debugs(82, 2, "aclMatchExternal: \"" << key << "\": return -1.");
-                    return -1;
-                }
+                debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
+                ch->changeState(ExternalACLLookup::Instance());
+                debugs(82, 2, HERE << "\"" << key << "\": return -1.");
+                return ACCESS_DUNNO; // expired cached or simply absent entry
             } else {
-                if (!entry) {
-                    debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
+                if (!staleEntry) {
+                    debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
                            "' queue overload. Request rejected '" << key << "'.");
                     external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
-                    return -1;
+                    return ACCESS_DUNNO;
                 } else {
-                    debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
+                    debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
                            "' queue overload. Using stale result. '" << key << "'.");
+                    entry = staleEntry;
                     /* Fall thru to processing below */
                 }
             }
         }
     }
 
+    debugs(82, 4, HERE << "entry = { date=" <<
+           (long unsigned int) entry->date <<
+           ", result=" << entry->result <<
+           " tag=" << entry->tag <<
+           " log=" << entry->log << " }");
+#if USE_AUTH
+    debugs(82, 4, HERE << "entry user=" << entry->user);
+#endif
+
     external_acl_cache_touch(acl->def, entry);
-    result = entry->result;
     external_acl_message = entry->message.termedBuf();
 
-    debugs(82, 2, "aclMatchExternal: " << acl->def->name << " = " << result);
-
-    if (ch->request) {
-        if (entry->user.size())
-            ch->request->extacl_user = entry->user;
-
-        if (entry->password.size())
-            ch->request->extacl_passwd = entry->password;
-
-        if (!ch->request->tag.size())
-            ch->request->tag = entry->tag;
-
-        if (entry->log.size())
-            ch->request->extacl_log = entry->log;
-
-        if (entry->message.size())
-            ch->request->extacl_message = entry->message;
-    }
-
-    return result;
+    debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
+    copyResultsFromEntry(ch->request, entry);
+    return entry->result;
 }
 
 int
 ACLExternal::match(ACLChecklist *checklist)
 {
-    return aclMatchExternal (data, Filled(checklist));
+    allow_t answer = aclMatchExternal(data, Filled(checklist));
+
+    // convert to tri-state ACL match 1,0,-1
+    switch (answer) {
+    case ACCESS_ALLOWED:
+        return 1; // match
+
+    case ACCESS_DENIED:
+        return 0; // non-match
+
+    case ACCESS_DUNNO:
+    case ACCESS_AUTH_REQUIRED:
+    default:
+        // If the answer is not allowed or denied (matches/not matches) and
+        // async authentication is not needed (asyncNeeded), then we are done.
+        if (!checklist->asyncNeeded())
+            checklist->markFinished(answer, "aclMatchExternal exception");
+        return -1; // other
+    }
 }
 
 wordlist *
@@ -850,6 +924,10 @@ ACLExternal::dump() const
 static void
 external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
 {
+    // this must not be done when nothing is being cached.
+    if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
+        return;
+
     dlinkDelete(&entry->lru, &def->lru_list);
     dlinkAdd(entry, &entry->lru, &def->lru_list);
 }
@@ -871,13 +949,18 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
         String sb;
 
         switch (format->type) {
-
+#if USE_AUTH
         case _external_acl_format::EXT_ACL_LOGIN:
-            assert (ch->auth_user_request != NULL);
-            str = ch->auth_user_request->username();
+            // if this ACL line was the cause of credentials fetch
+            // they may not already be in the checklist
+            if (ch->auth_user_request == NULL && ch->request)
+                ch->auth_user_request = ch->request->auth_user_request;
+
+            if (ch->auth_user_request != NULL)
+                str = ch->auth_user_request->username();
             break;
+#endif
 #if USE_IDENT
-
         case _external_acl_format::EXT_ACL_IDENT:
             str = ch->rfc931;
 
@@ -900,12 +983,14 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
 #if USE_SQUID_EUI
         case _external_acl_format::EXT_ACL_SRCEUI48:
-            if (request->client_eui48.encode(buf, sizeof(buf)))
+            if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
+                    request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
                 str = buf;
             break;
 
         case _external_acl_format::EXT_ACL_SRCEUI64:
-            if (request->client_eui64.encode(buf, sizeof(buf)))
+            if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
+                    request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
                 str = buf;
             break;
 #endif
@@ -928,7 +1013,7 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
             break;
 
         case _external_acl_format::EXT_ACL_PROTO:
-            str = ProtocolStr[request->protocol];
+            str = AnyP::ProtocolType_str[request->protocol];
             break;
 
         case _external_acl_format::EXT_ACL_PORT:
@@ -995,8 +1080,8 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
         case _external_acl_format::EXT_ACL_USER_CERT_RAW:
 
-            if (ch->conn() != NULL) {
-                SSL *ssl = fd_table[ch->conn()->fd].ssl;
+            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
+                SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
 
                 if (ssl)
                     str = sslGetUserCertificatePEM(ssl);
@@ -1006,8 +1091,8 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
         case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
 
-            if (ch->conn() != NULL) {
-                SSL *ssl = fd_table[ch->conn()->fd].ssl;
+            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
+                SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
 
                 if (ssl)
                     str = sslGetUserCertificateChainPEM(ssl);
@@ -1017,8 +1102,8 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
         case _external_acl_format::EXT_ACL_USER_CERT:
 
-            if (ch->conn() != NULL) {
-                SSL *ssl = fd_table[ch->conn()->fd].ssl;
+            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
+                SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
 
                 if (ssl)
                     str = sslGetUserAttribute(ssl, format->header);
@@ -1028,8 +1113,8 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
         case _external_acl_format::EXT_ACL_CA_CERT:
 
-            if (ch->conn() != NULL) {
-                SSL *ssl = fd_table[ch->conn()->fd].ssl;
+            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
+                SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
 
                 if (ssl)
                     str = sslGetCAAttribute(ssl, format->header);
@@ -1037,11 +1122,20 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 
             break;
 #endif
-
+#if USE_AUTH
         case _external_acl_format::EXT_ACL_EXT_USER:
             str = request->extacl_user.termedBuf();
             break;
-
+#endif
+        case _external_acl_format::EXT_ACL_EXT_LOG:
+            str = request->extacl_log.termedBuf();
+            break;
+        case _external_acl_format::EXT_ACL_TAG:
+            str = request->tag.termedBuf();
+            break;
+        case _external_acl_format::EXT_ACL_PERCENT:
+            str = "%";
+            break;
         case _external_acl_format::EXT_ACL_UNKNOWN:
 
         case _external_acl_format::EXT_ACL_END:
@@ -1091,6 +1185,9 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 static int
 external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
 {
+    if (def->cache_size <= 0)
+        return 1;
+
     if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
         return 1;
     else
@@ -1100,11 +1197,14 @@ external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
 static int
 external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
 {
+    if (def->cache_size <= 0)
+        return 1;
+
     int ttl;
     ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
     ttl = (ttl * (100 - def->grace)) / 100;
 
-    if (entry->date + ttl < squid_curtime)
+    if (entry->date + ttl <= squid_curtime)
         return 1;
     else
         return 0;
@@ -1113,12 +1213,24 @@ external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
 static external_acl_entry *
 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
 {
-    ExternalACLEntry *entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
+    ExternalACLEntry *entry;
+
+    // do not bother caching this result if TTL is going to expire it immediately
+    if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
+        debugs(82,6, HERE);
+        entry = new ExternalACLEntry;
+        entry->key = xstrdup(key);
+        entry->update(data);
+        entry->def = def;
+        return entry;
+    }
+
+    entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
     debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
 
     if (entry) {
         debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
-        entry->update (data);
+        entry->update(data);
         external_acl_cache_touch(def, entry);
 
         return entry;
@@ -1126,10 +1238,9 @@ external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData
 
     entry = new ExternalACLEntry;
     entry->key = xstrdup(key);
-    entry->update (data);
+    entry->update(data);
 
-    def->add
-    (entry);
+    def->add(entry);
 
     return entry;
 }
@@ -1137,7 +1248,7 @@ external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData
 static void
 external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
 {
-    assert (entry->def == def);
+    assert(def->cache_size > 0 && entry->def == def);
     hash_remove_link(def->cache, entry);
     dlinkDelete(&entry->lru, &def->lru_list);
     def->cache_entries -= 1;
@@ -1201,9 +1312,9 @@ externalAclHandleReply(void *data, char *reply)
     char *status;
     char *token;
     char *value;
-    char *t;
+    char *t = NULL;
     ExternalACLEntryData entryData;
-    entryData.result = 0;
+    entryData.result = ACCESS_DENIED;
     external_acl_entry *entry = NULL;
 
     debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
@@ -1212,20 +1323,19 @@ externalAclHandleReply(void *data, char *reply)
         status = strwordtok(reply, &t);
 
         if (status && strcmp(status, "OK") == 0)
-            entryData.result = 1;
+            entryData.result = ACCESS_ALLOWED;
 
         while ((token = strwordtok(NULL, &t))) {
             value = strchr(token, '=');
 
             if (value) {
-                *value++ = '\0';       /* terminate the token, and move up to the value */
+                *value = '\0'; /* terminate the token, and move up to the value */
+                ++value;
 
                 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
                     rfc1738_unescape(value);
 
-                if (strcmp(token, "user") == 0)
-                    entryData.user = value;
-                else if (strcmp(token, "message") == 0)
+                if (strcmp(token, "message") == 0)
                     entryData.message = value;
                 else if (strcmp(token, "error") == 0)
                     entryData.message = value;
@@ -1233,12 +1343,16 @@ externalAclHandleReply(void *data, char *reply)
                     entryData.tag = value;
                 else if (strcmp(token, "log") == 0)
                     entryData.log = value;
+#if USE_AUTH
+                else if (strcmp(token, "user") == 0)
+                    entryData.user = value;
                 else if (strcmp(token, "password") == 0)
                     entryData.password = value;
                 else if (strcmp(token, "passwd") == 0)
                     entryData.password = value;
                 else if (strcmp(token, "login") == 0)
                     entryData.user = value;
+#endif
             }
         }
     }
@@ -1272,86 +1386,52 @@ externalAclHandleReply(void *data, char *reply)
 }
 
 void
-ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me, EAH * callback, void *callback_data)
+ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
+{
+    ExternalACLLookup::Start(checklist, me->data, false);
+}
+
+void
+ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
 {
-    MemBuf buf;
-    external_acl_data *acl = me->data;
     external_acl *def = acl->def;
-    externalAclState *state;
-    dlink_node *node;
-    externalAclState *oldstate = NULL;
-    bool graceful = 0;
 
     ACLFilledChecklist *ch = Filled(checklist);
-    if (acl->def->require_auth) {
-        int ti;
-        /* Make sure the user is authenticated */
-        debugs(82, 3, "aclMatchExternal: " << acl->def->name << " check user authenticated.");
-
-        if ((ti = AuthenticateAcl(ch)) != 1) {
-            debugs(82, 1, "externalAclLookup: " << acl->def->name <<
-                   " user authentication failure (" << ti << ", ch=" << ch << ")");
-            callback(callback_data, NULL);
-            return;
-        }
-        debugs(82, 3, "aclMatchExternal: " << acl->def->name << " user is authenticated.");
-    }
-
     const char *key = makeExternalAclKey(ch, acl);
+    assert(key);
 
-    if (!key) {
-        debugs(82, 1, "externalAclLookup: lookup in '" << def->name <<
-               "', prerequisit failure (ch=" << ch << ")");
-        callback(callback_data, NULL);
-        return;
-    }
-
-    debugs(82, 2, "externalAclLookup: lookup in '" << def->name << "' for '" << key << "'");
-
-    external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key));
-
-    if (entry && external_acl_entry_expired(def, entry))
-        entry = NULL;
+    debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
+           def->name << "' for '" << key << "'");
 
     /* Check for a pending lookup to hook into */
-    for (node = def->queue.head; node; node = node->next) {
-        externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
-
-        if (strcmp(key, oldstatetmp->key) == 0) {
-            oldstate = oldstatetmp;
-            break;
-        }
-    }
+    // only possible if we are caching results.
+    externalAclState *oldstate = NULL;
+    if (def->cache_size > 0) {
+        for (dlink_node *node = def->queue.head; node; node = node->next) {
+            externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
 
-    if (entry && external_acl_grace_expired(def, entry)) {
-        if (oldstate) {
-            debugs(82, 4, "externalAclLookup: in grace period, but already pending lookup ('" << key << "', ch=" << ch << ")");
-            callback(callback_data, entry);
-            return;
-        } else {
-            graceful = 1; // grace expired, (neg)ttl did not, and we must start a new lookup.
+            if (strcmp(key, oldstatetmp->key) == 0) {
+                oldstate = oldstatetmp;
+                break;
+            }
         }
     }
 
-    // The entry is in the cache, grace_ttl did not expired.
-    if (!graceful && entry && !external_acl_grace_expired(def, entry)) {
-        /* Should not really happen, but why not.. */
-        callback(callback_data, entry);
-        debugs(82, 4, "externalAclLookup: no lookup pending for '" << key << "', and grace not expired");
-        debugs(82, 4, "externalAclLookup: (what tha' hell?)");
+    // A background refresh has no need to piggiback on a pending request:
+    // When the pending request completes, the cache will be refreshed anyway.
+    if (oldstate && inBackground) {
+        debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
         return;
     }
 
-    /* No pending lookup found. Sumbit to helper */
-    state = cbdataAlloc(externalAclState);
-
+    externalAclState *state = cbdataAlloc(externalAclState);
     state->def = cbdataReference(def);
 
     state->key = xstrdup(key);
 
-    if (!graceful) {
-        state->callback = callback;
-        state->callback_data = cbdataReference(callback_data);
+    if (!inBackground) {
+        state->callback = &ExternalACLLookup::LookupDone;
+        state->callback_data = cbdataReference(checklist);
     }
 
     if (oldstate) {
@@ -1359,16 +1439,19 @@ ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me, EAH *
         state->queue = oldstate->queue;
         oldstate->queue = state;
     } else {
+        /* No pending lookup found. Sumbit to helper */
+
         /* Check for queue overload */
 
         if (def->theHelper->stats.queue_size >= (int)def->theHelper->childs.n_running) {
-            debugs(82, 1, "externalAclLookup: '" << def->name << "' queue overload (ch=" << ch << ")");
+            debugs(82, 7, HERE << "'" << def->name << "' queue is too long");
+            assert(inBackground); // or the caller should have checked
             cbdataFree(state);
-            callback(callback_data, entry);
             return;
         }
 
         /* Send it off to the helper */
+        MemBuf buf;
         buf.init();
 
         buf.Printf("%s\n", key);
@@ -1382,24 +1465,6 @@ ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me, EAH *
         buf.clean();
     }
 
-    if (graceful) {
-        /* No need to wait during grace period */
-        debugs(82, 4, "externalAclLookup: no need to wait for the result of '" <<
-               key << "' in '" << def->name << "' (ch=" << ch << ").");
-        debugs(82, 4, "externalAclLookup: using cached entry " << entry);
-
-        if (entry != NULL) {
-            debugs(82, 4, "externalAclLookup: entry = { date=" <<
-                   (long unsigned int) entry->date << ", result=" <<
-                   entry->result << ", user=" << entry->user << " tag=" <<
-                   entry->tag << " log=" << entry->log << " }");
-
-        }
-
-        callback(callback_data, entry);
-        return;
-    }
-
     debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
            "' in '" << def->name << "' (ch=" << ch << ").");
 }
@@ -1421,8 +1486,8 @@ static void
 externalAclRegisterWithCacheManager(void)
 {
     Mgr::RegisterAction("external_acl",
-                   "External ACL stats",
-                   externalAclStats, 0, 1);
+                        "External ACL stats",
+                        externalAclStats, 0, 1);
 }
 
 void
@@ -1440,7 +1505,7 @@ externalAclInit(void)
 
         p->theHelper->cmdline = p->cmdline;
 
-        p->theHelper->childs = p->children;
+        p->theHelper->childs.updateLimits(p->children);
 
         p->theHelper->ipc_type = IPC_TCP_SOCKET;
 
@@ -1484,9 +1549,10 @@ ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
     ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
     assert (me);
     checklist->asyncInProgress(true);
-    ACLExternal::ExternalAclLookup(checklist, me, LookupDone, checklist);
+    ACLExternal::ExternalAclLookup(checklist, me);
 }
 
+/// Called when an async lookup returns
 void
 ExternalACLLookup::LookupDone(void *data, void *result)
 {
@@ -1494,7 +1560,7 @@ ExternalACLLookup::LookupDone(void *data, void *result)
     checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
     checklist->asyncInProgress(false);
     checklist->changeState (ACLChecklist::NullState::Instance());
-    checklist->check();
+    checklist->matchNonBlocking();
 }
 
 /* This registers "external" in the registry. To do dynamic definitions
@@ -1532,5 +1598,9 @@ ACLExternal::typeString() const
 bool
 ACLExternal::isProxyAuth() const
 {
+#if USE_AUTH
     return data->def->require_auth;
+#else
+    return false;
+#endif
 }