/*
- * 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()
}
/** \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.
*/
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
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;
}
// 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;
// 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);
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)
{
}
{
assert (!asyncInProgress());
- cbdataReferenceDone(accessList);
+ changeAcl(nullptr);
debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
}
if (!prepNonBlocking())
return; // checkCallback() has been called
- matchAndFinish();
+ if (!finished())
+ matchAndFinish();
if (asyncInProgress())
assert(!matchPath.empty()); // we have breadcrumbs to resume matching
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();
}
// if finished (on a match or in exceptional cases), stop
if (finished()) {
cbdataReferenceDone(acl);
+ occupied_ = false;
PROF_stop(aclCheckFast);
return currentAnswer();
}
// 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");
}
{
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);
+}
+