#include "acl/Checklist.h"
-class external_acl;
+/** \todo CLEANUP: kill this typedef. */
+typedef struct _external_acl_data external_acl_data;
class ExternalACLLookup : public ACLChecklist::AsyncState
{
static ExternalACLLookup *Instance();
virtual void checkForAsync(ACLChecklist *)const;
+ // If possible, starts an asynchronous lookup of an external ACL.
+ // Otherwise, asserts (or bails if background refresh is requested).
+ static void Start(ACLChecklist *checklist, external_acl_data *acl, bool bg);
+
private:
static ExternalACLLookup instance_;
static void LookupDone(void *data, void *result);
};
-/** \todo CLEANUP: kill this typedef. */
-typedef struct _external_acl_data external_acl_data;
-
#include "acl/Acl.h"
class ACLExternal : public ACL
public:
MEMPROXY_CLASS(ACLExternal);
- static void ExternalAclLookup(ACLChecklist * ch, ACLExternal *, EAH * callback, void *callback_data);
+ static void ExternalAclLookup(ACLChecklist * ch, ACLExternal *);
ACLExternal(char const *);
ACLList::matches (ACLChecklist *checklist) const
{
assert (_acl);
+ // XXX: AclMatchedName does not contain a matched ACL name when the acl
+ // does not match (or contains stale name if no ACLs are checked). In
+ // either case, we get misleading debugging and possibly incorrect error
+ // messages. Unfortunately, deny_info's "when none http_access
+ // lines match" exception essentially requires this mess.
+ // TODO: Rework by using an acl-free deny_info for the no-match cases?
AclMatchedName = _acl->name;
debugs(28, 3, "ACLList::matches: checking " << (op ? null_string : "!") << _acl->name);
if (_acl->checklistMatches(checklist) != op) {
debugs(28, 4, "ACLList::matches: result is false");
- return checklist->lastACLResult(false);
+ return false;
}
debugs(28, 4, "ACLList::matches: result is true");
- return checklist->lastACLResult(true);
+ return true;
}
#include "squid-old.h"
#include "acl/Checklist.h"
-allow_t const &
-ACLChecklist::currentAnswer() const
-{
- return allow_;
-}
-
-void
-ACLChecklist::currentAnswer(allow_t const newAnswer)
-{
- allow_ = newAnswer;
-}
-
void
ACLChecklist::matchNonBlocking()
{
if (checking())
return;
- /** Deny if no rules present. */
- currentAnswer(ACCESS_DENIED);
-
if (callerGone()) {
- checkCallback(currentAnswer());
+ checkCallback(ACCESS_DUNNO); // the answer does not really matter
return;
}
* We cannot select a sensible default for all callers here. */
if (accessList == NULL) {
debugs(28, DBG_CRITICAL, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
- currentAnswer(ACCESS_DENIED);
- checkCallback(currentAnswer());
+ checkCallback(ACCESS_DUNNO);
return;
}
+ allow_t lastSeenKeyword = ACCESS_DUNNO;
/* NOTE: This holds a cbdata reference to the current access_list
* entry, not the whole list.
*/
while (accessList != NULL) {
/** \par
* If the _acl_access is no longer valid (i.e. its been
- * freed because of a reconfigure), then bail on this
- * access check. For now, return ACCESS_DENIED.
+ * freed because of a reconfigure), then bail with ACCESS_DUNNO.
*/
if (!cbdataReferenceValid(accessList)) {
cbdataReferenceDone(accessList);
debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
- continue;
+ checkCallback(ACCESS_DUNNO);
+ return;
}
checking (true);
return;
}
+ lastSeenKeyword = accessList->allow;
+
/*
* Reference the next access entry
*/
cbdataReferenceDone(A);
}
- /** If dropped off the end of the list return inversion of last line allow/deny action. */
- debugs(28, 3, HERE << this << " NO match found, returning " <<
- (currentAnswer() != ACCESS_DENIED ? ACCESS_DENIED : ACCESS_ALLOWED));
+ calcImplicitAnswer(lastSeenKeyword);
+ checkCallback(currentAnswer());
+}
- checkCallback(currentAnswer() != ACCESS_DENIED ? ACCESS_DENIED : ACCESS_ALLOWED);
+bool
+ACLChecklist::asyncNeeded() const
+{
+ return state_ != NullState::Instance();
}
bool
}
void
-ACLChecklist::markFinished()
+ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
{
assert (!finished() && !asyncInProgress());
finished_ = true;
- debugs(28, 3, "ACLChecklist::markFinished: " << this <<
- " checklist processing finished");
+ allow_ = finalAnswer;
+ debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
}
+/// Called first (and once) by all checks to initialize their state
void
-ACLChecklist::preCheck()
+ACLChecklist::preCheck(const char *what)
{
- debugs(28, 3, "ACLChecklist::preCheck: " << this << " checking '" << accessList->cfgline << "'");
- /* what is our result on a match? */
- currentAnswer(accessList->allow);
+ debugs(28, 3, HERE << this << " checking " << what);
+ finished_ = false;
}
void
ACLChecklist::checkAccessList()
{
- preCheck();
+ debugs(28, 3, HERE << this << " checking '" << accessList->cfgline << "'");
/* does the current AND clause match */
- matchAclList(accessList->aclList, false);
+ if (matchAclList(accessList->aclList, false))
+ markFinished(accessList->allow, "first matching rule won");
+
+ // If we are not finished() here, the caller must distinguish between
+ // slow async calls and pure rule mismatches using asyncInProgress().
}
void
delete this;
}
-void
+/// An ACLChecklist::matchNodes() wrapper to simplify profiling.
+bool
ACLChecklist::matchAclList(const ACLList * head, bool const fast)
{
+ // TODO: remove by using object con/destruction-based PROF_* macros.
PROF_start(aclMatchAclList);
- const ACLList *node = head;
+ const bool result = matchNodes(head, fast);
+ PROF_stop(aclMatchAclList);
+ return result;
+}
- finished_ = false;
+/** Returns true if and only if there was a match. If false is returned:
+ finished() indicates an error or exception of some kind, while
+ !finished() means there was a mismatch or an allowed slow async call.
+ If async calls are allowed (i.e. 'fast' was false), then those last
+ two cases can be distinguished using asyncInProgress().
+*/
+bool
+ACLChecklist::matchNodes(const ACLList * head, bool const fast)
+{
+ assert(!finished());
- while (node) {
- bool nodeMatched = node->matches(this);
+ for (const ACLList *node = head; node; node = node->next) {
- if (fast)
- changeState(NullState::Instance());
+ const NodeMatchingResult resultBeforeAsync = matchNode(*node, fast);
- if (finished()) {
- PROF_stop(aclMatchAclList);
- return;
- }
+ if (resultBeforeAsync == nmrMatch)
+ continue;
+
+ if (resultBeforeAsync == nmrMismatch || resultBeforeAsync == nmrFinished)
+ return false;
+
+ assert(resultBeforeAsync == nmrNeedsAsync);
+
+ // Ideally, this should be inside match() itself, but that requires
+ // prohibiting slow ACLs in options that do not support them.
+ // TODO: rename to maybeStartAsync()?
+ checkForAsync();
+
+ // Some match() code claims that an async lookup is needed, but then
+ // fails to start an async lookup when given a chance. We catch such
+ // cases here and call matchNode() again, hoping that some cached data
+ // prevents us from going async again.
+ // This is inefficient and ugly, but fixing all match() code, including
+ // the code it calls, such as ipcache_nbgethostbyname(), takes time.
+ if (!asyncInProgress()) { // failed to start an async operation
+
+ if (finished()) {
+ debugs(28, 3, HERE << this << " finished after failing to go async: " << currentAnswer());
+ return false; // an exceptional case
+ }
- if (!nodeMatched || state_ != NullState::Instance()) {
+ const NodeMatchingResult resultAfterAsync = matchNode(*node, true);
+ // the second call disables slow checks so we cannot go async again
+ assert(resultAfterAsync != nmrNeedsAsync);
+ if (resultAfterAsync == nmrMatch)
+ continue;
- bool async = state_ != NullState::Instance();
+ assert(resultAfterAsync == nmrMismatch || resultAfterAsync == nmrFinished);
+ return false;
+ }
- checkForAsync();
+ assert(!finished()); // async operation is truly asynchronous
+ debugs(28, 3, HERE << this << " awaiting async operation");
+ return false;
+ }
- bool async_in_progress = asyncInProgress();
- debugs(28, 3, "aclmatchAclList: async=" << (async ? 1 : 0) <<
- " nodeMatched=" << (nodeMatched ? 1 : 0) <<
- " async_in_progress=" << (async_in_progress ? 1 : 0) <<
- " lastACLResult() = " << (lastACLResult() ? 1 : 0) <<
- " finished() = " << finished());
+ debugs(28, 3, HERE << this << " success: all ACLs matched");
+ return true;
+}
- if (finished()) {
- debugs(28, 3, "aclmatchAclList: " << this << " returning (AND list entry failed to match)");
- PROF_stop(aclMatchAclList);
- return;
- }
- if (async && nodeMatched && !asyncInProgress() && lastACLResult()) {
- // async acl, but using cached response, and it was a match
- node = node->next;
- continue;
- }
+/// Check whether a single ACL matches, returning NodeMatchingResult
+ACLChecklist::NodeMatchingResult
+ACLChecklist::matchNode(const ACLList &node, bool const fast)
+{
+ const bool nodeMatched = node.matches(this);
+ const bool needsAsync = asyncNeeded();
+ const bool matchFinished = finished();
+
+ debugs(28, 3, HERE << this <<
+ " matched=" << nodeMatched <<
+ " async=" << needsAsync <<
+ " finished=" << matchFinished);
+
+ /* There are eight possible outcomes of the matches() call based on
+ (matched, async, finished) permutations. We support these four:
+ matched,!async,!finished: a match (must check next rule node)
+ !matched,!async,!finished: a mismatch (whole rule fails to match)
+ !matched,!async,finished: error or special condition (propagate)
+ !matched,async,!finished: ACL needs to make an async call (pause)
+ */
- debugs(28, 3, "aclmatchAclList: " << this << " returning (AND list entry awaiting an async lookup)");
- PROF_stop(aclMatchAclList);
- return;
- }
+ if (nodeMatched) {
+ // matches() should return false in all special cases
+ assert(!needsAsync && !matchFinished);
+ return nmrMatch;
+ }
+
+ if (matchFinished) {
+ // we cannot be done and need an async call at the same time
+ assert(!needsAsync);
+ debugs(28, 3, HERE << this << " exception: " << currentAnswer());
+ return nmrFinished;
+ }
- node = node->next;
+ if (!needsAsync) {
+ debugs(28, 3, HERE << this << " simple mismatch");
+ return nmrMismatch;
}
- debugs(28, 3, "aclmatchAclList: " << this << " returning true (AND list satisfied)");
+ /* we need an async call */
- markFinished();
- PROF_stop(aclMatchAclList);
+ if (fast) {
+ changeState(NullState::Instance()); // disable async checks
+ markFinished(ACCESS_DUNNO, "async required but prohibited");
+ debugs(28, 3, HERE << this << " DUNNO because cannot async");
+ return nmrFinished;
+ }
+
+ debugs(28, 3, HERE << this << " going async");
+ return nmrNeedsAsync;
}
ACLChecklist::ACLChecklist() :
async_(false),
finished_(false),
allow_(ACCESS_DENIED),
- state_(NullState::Instance()),
- lastACLResult_(false)
+ state_(NullState::Instance())
{
}
void
ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
{
+ preCheck("slow rules");
callback = callback_;
callback_data = cbdataReference(callback_data_);
matchNonBlocking();
ACLChecklist::fastCheck(const ACLList * list)
{
PROF_start(aclCheckFast);
- currentAnswer(ACCESS_DUNNO);
- matchAclList(list, true);
- // assume ALLOWED on matches due to not having an acl_access object
- if (finished())
- currentAnswer(ACCESS_ALLOWED);
+
+ preCheck("fast ACLs");
+
+ // assume DENY/ALLOW on mis/matches due to not having acl_access object
+ if (matchAclList(list, true))
+ markFinished(ACCESS_ALLOWED, "all ACLs matched");
+ else
+ if (!finished())
+ markFinished(ACCESS_DENIED, "ACL mismatched");
PROF_stop(aclCheckFast);
return currentAnswer();
}
ACLChecklist::fastCheck()
{
PROF_start(aclCheckFast);
- currentAnswer(ACCESS_DUNNO);
+ preCheck("fast rules");
+
+ allow_t lastSeenKeyword = ACCESS_DUNNO;
debugs(28, 5, "aclCheckFast: list: " << accessList);
const acl_access *acl = cbdataReference(accessList);
while (acl != NULL && cbdataReferenceValid(acl)) {
- matchAclList(acl->aclList, true);
+ // on a match, finish
+ if (matchAclList(acl->aclList, true))
+ markFinished(acl->allow, "first matching rule won");
+
+ // if finished (on a match or in exceptional cases), stop
if (finished()) {
- currentAnswer(acl->allow);
- PROF_stop(aclCheckFast);
cbdataReferenceDone(acl);
+ PROF_stop(aclCheckFast);
return currentAnswer();
}
- /*
- * Reference the next access entry
- */
+ // on a mismatch, try the next access rule
+ lastSeenKeyword = acl->allow;
const acl_access *A = acl;
acl = cbdataReference(acl->next);
cbdataReferenceDone(A);
}
- debugs(28, 5, "aclCheckFast: no matches, returning: " << currentAnswer());
+ // There were no rules to match or no rules matched
+ calcImplicitAnswer(lastSeenKeyword);
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.
+void
+ACLChecklist::calcImplicitAnswer(const allow_t &lastSeenAction)
+{
+ allow_t implicitRuleAnswer = ACCESS_DUNNO;
+ if (lastSeenAction == ACCESS_DENIED) // reverse last seen "deny"
+ implicitRuleAnswer = ACCESS_ALLOWED;
+ else if (lastSeenAction == 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);
+ markFinished(implicitRuleAnswer, "implicit rule won");
+}
bool
ACLChecklist::checking() const
virtual ~ACLChecklist();
/**
- * Trigger off a non-blocking access check for a set of *_access options..
- * The callback specified will be called with true/false
- * when the results of the ACL tests are known.
+ * Start a non-blocking (async) check for a list of allow/deny rules.
+ * Each rule comes with a list of ACLs.
+ *
+ * The callback specified will be called with the result of the check.
+ *
+ * The first rule where all ACLs match wins. If there is such a rule,
+ * the result becomes that rule keyword (ACCESS_ALLOWED or ACCESS_DENIED).
+ *
+ * If there are rules but all ACL lists mismatch, an implicit rule is used.
+ * Its result is the negation of the keyword of the last seen rule.
+ *
+ * Some ACLs may stop the check prematurely by setting an exceptional
+ * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a
+ * match or mismatch.
+ *
+ * If there are no rules to check at all, the result becomes ACCESS_DUNNO.
+ * Calling this method with no rules to check wastes a lot of CPU cycles
+ * and will result in a DBG_CRITICAL debugging message.
*/
void nonBlockingCheck(ACLCB * callback, void *callback_data);
/**
- * Trigger a blocking access check for a set of *_access options.
+ * Perform a blocking (immediate) check for a list of allow/deny rules.
+ * Each rule comes with a list of ACLs.
+ *
+ * The first rule where all ACLs match wins. If there is such a rule,
+ * the result becomes that rule keyword (ACCESS_ALLOWED or ACCESS_DENIED).
*
- * ACLs which cannot be satisfied directly from available data are ignored.
- * This means any proxy_auth, external_acl, DNS lookups, Ident lookups etc
- * which have not already been performed and cached will not be checked.
+ * If there are rules but all ACL lists mismatch, an implicit rule is used
+ * Its result is the negation of the keyword of the last seen rule.
*
- * If there is no access list to check the default is to return ALLOWED.
- * However callers should perform their own check and default based on local
- * knowledge of the ACL usage rather than depend on this default.
- * That will also save on work setting up ACLChecklist fields for a no-op.
+ * Some ACLs may stop the check prematurely by setting an exceptional
+ * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a
+ * match or mismatch.
*
- * \retval ACCESS_DUNNO Unable to determine any result
- * \retval ACCESS_ALLOWED Access Allowed
- * \retval ACCESS_DENIED Access Denied
+ * Some ACLs may require an async lookup which is prohibited by this
+ * method. In this case, the exceptional check result of ACCESS_DUNNO is
+ * immediately returned.
+ *
+ * If there are no rules to check at all, the result becomes ACCESS_DUNNO.
*/
allow_t const & fastCheck();
/**
- * A version of fastCheck() for use when there is a one-line set of ACLs
- * to be tested and a match determins the result action to be done.
+ * Perform a blocking (immediate) check whether a list of ACLs matches.
+ * This method is meant to be used with squid.conf ACL-driven options that
+ * lack allow/deny keywords and are tested one ACL list at a time. Whether
+ * the checks for other occurrences of the same option continue after this
+ * call is up to the caller and option semantics.
+ *
+ * If all ACLs match, the result becomes ACCESS_ALLOWED.
*
- * \retval ACCESS_DUNNO Unable to determine any result
- * \retval ACCESS_ALLOWED ACLs all matched
+ * If all ACLs mismatch, the result becomes ACCESS_DENIED.
+ *
+ * Some ACLs may stop the check prematurely by setting an exceptional
+ * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a
+ * match or mismatch.
+ *
+ * Some ACLs may require an async lookup which is prohibited by this
+ * method. In this case, the exceptional check result of ACCESS_DUNNO is
+ * immediately returned.
+ *
+ * If there are no ACLs to check at all, the result becomes ACCESS_ALLOWED.
*/
allow_t const & fastCheck(const ACLList * list);
+ // whether the last checked ACL of the current rule needs
+ // an async operation to determine whether there was a match
+ bool asyncNeeded() const;
bool asyncInProgress() const;
void asyncInProgress(bool const);
+ /// whether markFinished() was called
bool finished() const;
- void markFinished();
+ /// called when no more ACLs should be checked; sets the final answer and
+ /// prints a debugging message explaining the reason for that answer
+ void markFinished(const allow_t &newAnswer, const char *reason);
- allow_t const & currentAnswer() const;
- void currentAnswer(allow_t const);
+ const allow_t ¤tAnswer() const { return allow_; }
void changeState(AsyncState *);
AsyncState *asyncState() const;
void *callback_data;
/**
- * Attempt to check the current checklist against current data.
- * This is the core routine behind all ACL test routines.
- * As much as possible of current tests are performed immediately
- * and the result is maybe delayed to wait for async lookups.
- *
- * When all tests are done callback is presented with one of:
- * - ACCESS_ALLOWED Access explicitly Allowed
- * - ACCESS_DENIED Access explicitly Denied
+ * Performs non-blocking check starting with the current rule.
+ * Used by nonBlockingCheck() to initiate the checks and by
+ * async operation callbacks to resume checks after the async
+ * operation updates the current Squid state. See nonBlockingCheck()
+ * for details on final result determination.
*/
void matchNonBlocking();
private: /* internal methods */
- void preCheck();
- void matchAclList(const ACLList * list, bool const fast);
+ /// possible outcomes when trying to match a single ACL node in a list
+ typedef enum { nmrMatch, nmrMismatch, nmrFinished, nmrNeedsAsync }
+ NodeMatchingResult;
+
+ /// prepare for checking ACLs; called once per check
+ void preCheck(const char *what);
+ bool matchAclList(const ACLList * list, bool const fast);
+ bool matchNodes(const ACLList * head, bool const fast);
+ NodeMatchingResult matchNode(const ACLList &node, bool const fast);
+ void calcImplicitAnswer(const allow_t &lastSeenAction);
bool async_;
bool finished_;
bool checking() const;
void checking (bool const);
- bool lastACLResult_;
bool callerGone();
-
-public:
- bool lastACLResult(bool x) { return lastACLResult_ = x; }
-
- bool lastACLResult() const { return lastACLResult_; }
};
#endif /* SQUID_ACLCHECKLIST_H */
break;
case AUTH_ACL_HELPER:
- debugs(28, 4, HERE << "returning " << ACCESS_DENIED << " sending credentials to helper.");
+ debugs(28, 4, HERE << "returning " << ACCESS_DUNNO << " sending credentials to helper.");
checklist->changeState(ProxyAuthLookup::Instance());
return ACCESS_DUNNO; // XXX: break this down into DUNNO, EXPIRED_OK, EXPIRED_BAD states
case AUTH_ACL_CHALLENGE:
- debugs(28, 4, HERE << "returning " << ACCESS_DENIED << " sending authentication challenge.");
- checklist->changeState(ProxyAuthNeeded::Instance());
+ debugs(28, 4, HERE << "returning " << ACCESS_AUTH_REQUIRED << " sending authentication challenge.");
+ /* Client is required to resend the request with correct authentication
+ * credentials. (This may be part of a stateful auth protocol.)
+ * The request is denied.
+ */
return ACCESS_AUTH_REQUIRED;
default:
{
ACLFilledChecklist *checklist = Filled(cl);
allow_t answer = AuthenticateAcl(checklist);
- checklist->currentAnswer(answer);
int ti;
// convert to tri-state ACL match 1,0,-1
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, "AuthenticateAcl exception");
return -1; // other
}
}
ACLProxyAuth::match(ACLChecklist *checklist)
{
allow_t answer = AuthenticateAcl(checklist);
- checklist->currentAnswer(answer);
// convert to tri-state ACL match 1,0,-1
switch (answer) {
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, "AuthenticateAcl exception");
return -1; // other
}
}
return true;
}
-ProxyAuthNeeded ProxyAuthNeeded::instance_;
-
-ProxyAuthNeeded *
-ProxyAuthNeeded::Instance()
-{
- return &instance_;
-}
-
ProxyAuthLookup ProxyAuthLookup::instance_;
ProxyAuthLookup *
checklist->matchNonBlocking();
}
-void
-ProxyAuthNeeded::checkForAsync(ACLChecklist *checklist) const
-{
- /* Client is required to resend the request with correct authentication
- * credentials. (This may be part of a stateful auth protocol.)
- * The request is denied.
- */
- debugs(28, 6, "ACLChecklist::checkForAsync: requiring Proxy Auth header.");
- checklist->currentAnswer(ACCESS_AUTH_REQUIRED);
- checklist->changeState (ACLChecklist::NullState::Instance());
- checklist->markFinished();
-}
-
ACL *
ACLProxyAuth::clone() const
{
static void LookupDone(void *data, char *result);
};
-class ProxyAuthNeeded : public ACLChecklist::AsyncState
-{
-
-public:
- static ProxyAuthNeeded *Instance();
- virtual void checkForAsync(ACLChecklist *)const;
-
-private:
- static ProxyAuthNeeded instance_;
-};
-
class ACLProxyAuth : public ACL
{
debugs(82, 9, HERE << "No helper entry available");
#if USE_AUTH
if (acl->def->require_auth) {
- int ti = AuthenticateAcl(ch);
/* Make sure the user is authenticated */
debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
- if (ti != 1) {
+ const allow_t ti = AuthenticateAcl(ch);
+ if (ti != ACCESS_ALLOWED) {
debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
- return ACCESS_AUTH_REQUIRED;
+ return ti;
}
debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
}
entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
- if (!entry || external_acl_grace_expired(acl->def, 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));
debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
ch->changeState(ExternalACLLookup::Instance());
debugs(82, 2, HERE << "\"" << key << "\": return -1.");
- return ACCESS_DUNNO; // to get here we have to have an expired cache entry. MUST not use.
+ return ACCESS_DUNNO; // expired cached or simply absent entry
} else {
- if (!entry) {
+ 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";
} else {
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);
external_acl_message = entry->message.termedBuf();
ACLExternal::match(ACLChecklist *checklist)
{
allow_t answer = aclMatchExternal(data, Filled(checklist));
- checklist->currentAnswer(answer);
// convert to tri-state ACL match 1,0,-1
switch (answer) {
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
}
}
}
void
-ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me, EAH * callback, void *callback_data)
+ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
{
- MemBuf buf;
- external_acl_data *acl = me->data;
- external_acl *def = acl->def;
- externalAclState *state;
- dlink_node *node;
- externalAclState *oldstate = NULL;
- bool graceful = 0;
+ ExternalACLLookup::Start(checklist, me->data, false);
+}
+void
+ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
+{
+ external_acl *def = acl->def;
+
ACLFilledChecklist *ch = Filled(checklist);
-#if USE_AUTH
- if (acl->def->require_auth) {
- int ti;
- /* Make sure the user is authenticated */
- debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
-
- if ((ti = AuthenticateAcl(ch)) != 1) {
- debugs(82, DBG_IMPORTANT, "WARNING: " << acl->def->name <<
- " user authentication failure (" << ti << ", ch=" << ch << ")");
- callback(callback_data, NULL);
- return;
- }
- debugs(82, 3, HERE << 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) {
}
}
- 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) {
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);
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
- copyResultsFromEntry(ch->request, entry);
- }
-
- callback(callback_data, entry);
- return;
- }
-
debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
"' in '" << def->name << "' (ch=" << ch << ").");
}
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)
{
if (Config.accessList.reply) {
ACLFilledChecklist ch(Config.accessList.reply, originalRequest(), NULL);
ch.reply = HTTPMSGLOCK(reply);
- if (!ch.fastCheck()) { // TODO: support slow lookups?
+ if (ch.fastCheck() != ACCESS_ALLOWED) { // TODO: support slow lookups?
debugs(11, 3, HERE << "ignoring denied 1xx");
proceedAfter1xx();
return;
}
ACLFilledChecklist ch(Config.accessList.brokenPosts, originalRequest(), NULL);
- if (!ch.fastCheck()) {
+ if (ch.fastCheck() != ACCESS_ALLOWED) {
debugs(11, 5, HERE << "didn't match brokenPosts");
return false;
}
return data->match(checklist->rfc931);
} else if (checklist->conn() != NULL && checklist->conn()->clientConnection != NULL && checklist->conn()->clientConnection->rfc931[0]) {
return data->match(checklist->conn()->clientConnection->rfc931);
- } else {
+ } else if (checklist->conn() != NULL && Comm::IsConnOpen(checklist->conn()->clientConnection)) {
debugs(28, 3, HERE << "switching to ident lookup state");
checklist->changeState(IdentLookup::Instance());
return 0;
+ } else {
+ debugs(28, DBG_IMPORTANT, HERE << "Can't start ident lookup. No client connection" );
+ checklist->markFinished(ACCESS_DUNNO, "cannot start ident lookup");
+ return -1;
}
}
IdentLookup::checkForAsync(ACLChecklist *cl)const
{
ACLFilledChecklist *checklist = Filled(cl);
- if (checklist->conn() != NULL && Comm::IsConnOpen(checklist->conn()->clientConnection)) {
+ const ConnStateData *conn = checklist->conn();
+ // check that ACLIdent::match() tested this lookup precondition
+ assert(conn && Comm::IsConnOpen(conn->clientConnection));
debugs(28, 3, HERE << "Doing ident lookup" );
checklist->asyncInProgress(true);
Ident::Start(checklist->conn()->clientConnection, LookupDone, checklist);
- } else {
- debugs(28, DBG_IMPORTANT, "IdentLookup::checkForAsync: Can't start ident lookup. No client connection" );
- checklist->currentAnswer(ACCESS_DENIED);
- checklist->markFinished();
- }
}
void