]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/acl/Checklist.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Checklist.cc
index 6774c738e9326e4999201e6996bc761dc82d207b..1c6ee95d5dc413399f907a521cc40c3c14ad48e9 100644 (file)
@@ -1,13 +1,21 @@
 /*
- * DEBUG: section 28    Access Control
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
+/* DEBUG: section 28    Access Control */
+
 #include "squid.h"
 #include "acl/Checklist.h"
 #include "acl/Tree.h"
 #include "Debug.h"
 #include "profiler/Profiler.h"
 
+#include <algorithm>
+
 /// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
 bool
 ACLChecklist::prepNonBlocking()
@@ -20,7 +28,7 @@ ACLChecklist::prepNonBlocking()
     }
 
     /** \par
-     * If the _acl_access is no longer valid (i.e. its been
+     * If the accessList is no longer valid (i.e. its been
      * freed because of a reconfigure), then bail with ACCESS_DUNNO.
      */
 
@@ -31,19 +39,7 @@ ACLChecklist::prepNonBlocking()
         return false;
     }
 
-    // If doNonBlocking() was called for a finished() checklist to call
-    // the callbacks, then do not try to match again. XXX: resumeNonBlockingCheck() should check for this instead.
-    if (!finished())
-        return true;
-
-    /** \par
-     * Either the request is allowed, denied, requires authentication.
-     */
-    debugs(28, 3, this << " calling back with " << currentAnswer());
-    cbdataReferenceDone(accessList); /* A */
-    checkCallback(currentAnswer());
-    /* From here on in, this may be invalid */
-    return false;
+    return true;
 }
 
 void
@@ -72,6 +68,12 @@ void
 ACLChecklist::preCheck(const char *what)
 {
     debugs(28, 3, HERE << this << " checking " << what);
+
+    // concurrent checks using the same Checklist are not supported
+    assert(!occupied_);
+    occupied_ = true;
+    asyncLoopDepth_ = 0;
+
     AclMatchedName = NULL;
     finished_ = false;
 }
@@ -84,6 +86,7 @@ ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterat
     // Remember the current tree location to prevent "async loop" cases where
     // the same child node wants to go async more than once.
     matchLoc_ = Breadcrumb(current, pos);
+    asyncLoopDepth_ = 0;
 
     // if there are any breadcrumbs left, then follow them on the way down
     bool result = false;
@@ -123,11 +126,16 @@ ACLChecklist::goAsync(AsyncState *state)
 
     // TODO: add a once-in-a-while WARNING about async loops?
     if (matchLoc_ == asyncLoc_) {
-        debugs(28, 2, this << " a slow ACL resumes by going async again!");
-        return false;
+        debugs(28, 2, this << " a slow ACL resumes by going async again! (loop #" << asyncLoopDepth_ << ")");
+        // external_acl_type may cause async auth lookup plus its own async check
+        // which has the appearance of a loop. Allow some retries.
+        // TODO: make it configurable and check BH retry attempts vs this check?
+        if (asyncLoopDepth_ > 5)
+            return false;
     }
 
     asyncLoc_ = matchLoc_; // prevent async loops
+    ++asyncLoopDepth_;
 
     asyncStage_ = asyncStarting;
     changeState(state);
@@ -160,18 +168,23 @@ ACLChecklist::checkCallback(allow_t answer)
     if (cbdataReferenceValidDone(callback_data, &cbdata_))
         callback_(answer, cbdata_);
 
+    // not really meaningful just before delete, but here for completeness sake
+    occupied_ = false;
+
     delete this;
 }
 
 ACLChecklist::ACLChecklist() :
-        accessList (NULL),
-        callback (NULL),
-        callback_data (NULL),
-        asyncCaller_(false),
-        finished_(false),
-        allow_(ACCESS_DENIED),
-        asyncStage_(asyncNone),
-        state_(NullState::Instance())
+    accessList (NULL),
+    callback (NULL),
+    callback_data (NULL),
+    asyncCaller_(false),
+    occupied_(false),
+    finished_(false),
+    allow_(ACCESS_DENIED),
+    asyncStage_(asyncNone),
+    state_(NullState::Instance()),
+    asyncLoopDepth_(0)
 {
 }
 
@@ -179,7 +192,7 @@ ACLChecklist::~ACLChecklist()
 {
     assert (!asyncInProgress());
 
-    cbdataReferenceDone(accessList);
+    changeAcl(nullptr);
 
     debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
 }
@@ -265,7 +278,8 @@ ACLChecklist::resumeNonBlockingCheck(AsyncState *state)
     if (!prepNonBlocking())
         return; // checkCallback() has been called
 
-    matchAndFinish();
+    if (!finished())
+        matchAndFinish();
 
     if (asyncInProgress())
         assert(!matchPath.empty()); // we have breadcrumbs to resume matching
@@ -298,21 +312,19 @@ ACLChecklist::fastCheck(const Acl::Tree * list)
     preCheck("fast ACLs");
     asyncCaller_ = false;
 
-    // This call is not compatible with a pre-set accessList because we cannot
-    // tell whether this Checklist is used by some other concurent call, which
-    // is not supported.
-    assert(!accessList);
-    accessList = list;
+    // Concurrent checks are not supported, but sequential checks are, and they
+    // may use a mixture of fastCheck(void) and fastCheck(list) calls.
+    const Acl::Tree * const savedList = changeAcl(list);
 
-    // assume DENY/ALLOW on mis/matches due to not having acl_access object
+    // assume DENY/ALLOW on mis/matches due to action-free accessList
     // matchAndFinish() takes care of the ALLOW case
-    cbdataReference(accessList); // required for cbdataReferenceValid()
     if (accessList && cbdataReferenceValid(accessList))
         matchAndFinish(); // calls markFinished() on success
     if (!finished())
         markFinished(ACCESS_DENIED, "ACLs failed to match");
 
-    cbdataReferenceDone(accessList);
+    changeAcl(savedList);
+    occupied_ = false;
     PROF_stop(aclCheckFast);
     return currentAnswer();
 }
@@ -336,6 +348,7 @@ ACLChecklist::fastCheck()
         // if finished (on a match or in exceptional cases), stop
         if (finished()) {
             cbdataReferenceDone(acl);
+            occupied_ = false;
             PROF_stop(aclCheckFast);
             return currentAnswer();
         }
@@ -346,29 +359,28 @@ ACLChecklist::fastCheck()
     // There were no rules to match or no rules matched
     calcImplicitAnswer();
     cbdataReferenceDone(acl);
+    occupied_ = false;
     PROF_stop(aclCheckFast);
 
     return currentAnswer();
 }
 
-/// When no rules matched, the answer is the inversion of the last seen rule
-/// action (or ACCESS_DUNNO if the reversal is not possible). The caller
-/// should set lastSeenAction to ACCESS_DUNNO if there were no rules to see.
+/// When no rules matched, the answer is the inversion of the last rule
+/// action (or ACCESS_DUNNO if the reversal is not possible).
 void
 ACLChecklist::calcImplicitAnswer()
 {
-    // XXX: rename lastSeenAction after review and before commit
-    const allow_t lastSeenAction = (accessList && cbdataReferenceValid(accessList)) ?
-                                   accessList->lastAction() : allow_t(ACCESS_DUNNO);
+    const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ?
+                               accessList->lastAction() : allow_t(ACCESS_DUNNO);
     allow_t implicitRuleAnswer = ACCESS_DUNNO;
-    if (lastSeenAction == ACCESS_DENIED) // reverse last seen "deny"
+    if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
         implicitRuleAnswer = ACCESS_ALLOWED;
-    else if (lastSeenAction == ACCESS_ALLOWED) // reverse last seen "allow"
+    else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
         implicitRuleAnswer = ACCESS_DENIED;
     // else we saw no rules and will respond with ACCESS_DUNNO
 
     debugs(28, 3, HERE << this << " NO match found, last action " <<
-           lastSeenAction << " so returning " << implicitRuleAnswer);
+           lastAction << " so returning " << implicitRuleAnswer);
     markFinished(implicitRuleAnswer, "implicit rule won");
 }
 
@@ -377,3 +389,18 @@ ACLChecklist::callerGone()
 {
     return !cbdataReferenceValid(callback_data);
 }
+
+bool
+ACLChecklist::bannedAction(const allow_t &action) const
+{
+    const bool found = std::find(bannedActions_.begin(), bannedActions_.end(), action) != bannedActions_.end();
+    debugs(28, 5, "Action '" << action << "/" << action.kind << (found ? "' is " : "' is not") << " banned");
+    return found;
+}
+
+void
+ACLChecklist::banAction(const allow_t &action)
+{
+    bannedActions_.push_back(action);
+}
+