]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/external_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / external_acl.cc
index dff0ea91d5076647d52559b92617c0b3774723ae..85019696c41bd6f1cf167686cca8a7aad7854927 100644 (file)
  */
 
 #include "squid.h"
-#include "mgr/Registration.h"
-#include "ExternalACL.h"
-#include "ExternalACLEntry.h"
-#if USE_AUTH
-#include "auth/Acl.h"
-#include "auth/Gadgets.h"
-#include "auth/UserRequest.h"
-#endif
-#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 "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
@@ -187,6 +189,9 @@ struct _external_acl_format {
 #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;
@@ -260,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);
 
@@ -334,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) {
@@ -360,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;
@@ -464,8 +472,14 @@ parse_externalAclHelper(external_acl ** list)
         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();
         }
 
@@ -558,6 +572,10 @@ 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
@@ -582,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;
@@ -647,7 +656,7 @@ external_acl::add(ExternalACLEntry *anEntry)
     anEntry->def = this;
     hash_join(cache, anEntry);
     dlinkAdd(anEntry, &anEntry->lru, &lru_list);
-    cache_entries++;
+    ++cache_entries;
 }
 
 void
@@ -657,7 +666,6 @@ external_acl::trimCache()
         external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
 }
 
-
 /******************************************************************
  * external acl type
  */
@@ -709,12 +717,12 @@ 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;
         }
     }
@@ -735,18 +743,45 @@ 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, HERE << "acl=\"" << acl->def->name << "\"");
-    entry = ch->extacl_entry;
+    external_acl_entry *entry = ch->extacl_entry;
 
     if (entry) {
         if (cbdataReferenceValid(entry) && entry->def == acl->def) {
-            /* Ours, use it.. */
+            /* 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.");
@@ -766,84 +801,101 @@ aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
         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 */
                 }
             }
         }
     }
 
-    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) {
+    debugs(82, 4, HERE << "entry = { date=" <<
+           (long unsigned int) entry->date <<
+           ", result=" << entry->result <<
+           " tag=" << entry->tag <<
+           " log=" << entry->log << " }");
 #if USE_AUTH
-        if (entry->user.size())
-            ch->request->extacl_user = entry->user;
-
-        if (entry->password.size())
-            ch->request->extacl_passwd = entry->password;
+    debugs(82, 4, HERE << "entry user=" << entry->user);
 #endif
-        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;
-    }
+    external_acl_cache_touch(acl->def, entry);
+    external_acl_message = entry->message.termedBuf();
 
-    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 *
@@ -899,8 +951,13 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
         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
@@ -926,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
@@ -1021,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);
@@ -1032,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);
@@ -1043,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);
@@ -1054,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);
@@ -1068,6 +1127,15 @@ makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
             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:
@@ -1246,7 +1314,7 @@ externalAclHandleReply(void *data, char *reply)
     char *value;
     char *t = NULL;
     ExternalACLEntryData entryData;
-    entryData.result = 0;
+    entryData.result = ACCESS_DENIED;
     external_acl_entry *entry = NULL;
 
     debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
@@ -1255,13 +1323,14 @@ 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);
@@ -1317,53 +1386,28 @@ 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 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, 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.");
-    }
-#endif
-
     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 */
     // only possible if we are caching results.
+    externalAclState *oldstate = NULL;
     if (def->cache_size > 0) {
-        for (node = def->queue.head; node; node = node->next) {
+        for (dlink_node *node = def->queue.head; node; node = node->next) {
             externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
 
             if (strcmp(key, oldstatetmp->key) == 0) {
@@ -1373,35 +1417,21 @@ ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me, EAH *
         }
     }
 
-    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.
-        }
-    }
-
-    // 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) {
@@ -1409,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);
@@ -1432,28 +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 <<
-                   " tag=" << entry->tag <<
-                   " log=" << entry->log << " }");
-#if USE_AUTH
-            debugs(82, 4, "externalAclLookup: user=" << entry->user);
-#endif
-
-        }
-
-        callback(callback_data, entry);
-        return;
-    }
-
     debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
            "' in '" << def->name << "' (ch=" << ch << ").");
 }
@@ -1494,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;
 
@@ -1538,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)
 {
@@ -1548,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