2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 28 Access Control */
12 #include "acl/Checklist.h"
15 #include "profiler/Profiler.h"
19 /// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
21 ACLChecklist::prepNonBlocking()
26 checkCallback(ACCESS_DUNNO
); // the answer does not really matter
31 * If the accessList is no longer valid (i.e. its been
32 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
35 if (!cbdataReferenceValid(accessList
)) {
36 cbdataReferenceDone(accessList
);
37 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
38 checkCallback(ACCESS_DUNNO
);
46 ACLChecklist::completeNonBlocking()
48 assert(!asyncInProgress());
53 cbdataReferenceDone(accessList
);
54 checkCallback(currentAnswer());
58 ACLChecklist::markFinished(const allow_t
&finalAnswer
, const char *reason
)
60 assert (!finished() && !asyncInProgress());
63 debugs(28, 3, HERE
<< this << " answer " << allow_
<< " for " << reason
);
66 /// Called first (and once) by all checks to initialize their state
68 ACLChecklist::preCheck(const char *what
)
70 debugs(28, 3, HERE
<< this << " checking " << what
);
72 // concurrent checks using the same Checklist are not supported
77 AclMatchedName
= NULL
;
82 ACLChecklist::matchChild(const Acl::InnerNode
*current
, Acl::Nodes::const_iterator pos
, const ACL
*child
)
84 assert(current
&& child
);
86 // Remember the current tree location to prevent "async loop" cases where
87 // the same child node wants to go async more than once.
88 matchLoc_
= Breadcrumb(current
, pos
);
91 // if there are any breadcrumbs left, then follow them on the way down
93 if (matchPath
.empty()) {
94 result
= child
->matches(this);
96 const Breadcrumb
top(matchPath
.top());
97 assert(child
== top
.parent
);
99 result
= top
.parent
->resumeMatchingAt(this, top
.position
);
102 if (asyncInProgress()) {
103 // We get here for node N that called goAsync() and then, as the call
104 // stack unwinds, for the nodes higher in the ACL tree that led to N.
105 matchPath
.push(Breadcrumb(current
, pos
));
115 ACLChecklist::goAsync(AsyncState
*state
)
118 assert(!asyncInProgress());
119 assert(matchLoc_
.parent
);
121 // TODO: add a once-in-a-while WARNING about fast directive using slow ACL?
123 debugs(28, 2, this << " a fast-only directive uses a slow ACL!");
127 // TODO: add a once-in-a-while WARNING about async loops?
128 if (matchLoc_
== asyncLoc_
) {
129 debugs(28, 2, this << " a slow ACL resumes by going async again! (loop #" << asyncLoopDepth_
<< ")");
130 // external_acl_type may cause async auth lookup plus its own async check
131 // which has the appearance of a loop. Allow some retries.
132 // TODO: make it configurable and check BH retry attempts vs this check?
133 if (asyncLoopDepth_
> 5)
137 asyncLoc_
= matchLoc_
; // prevent async loops
140 asyncStage_
= asyncStarting
;
142 state
->checkForAsync(this); // this is supposed to go async
144 // Did AsyncState object actually go async? If not, tell the caller.
145 if (asyncStage_
!= asyncStarting
) {
146 assert(asyncStage_
== asyncFailed
);
147 asyncStage_
= asyncNone
; // sanity restored
151 // yes, we must pause until the async callback calls resumeNonBlockingCheck
152 asyncStage_
= asyncRunning
;
156 // ACLFilledChecklist overwrites this to unclock something before we
159 ACLChecklist::checkCallback(allow_t answer
)
163 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer
);
165 callback_
= callback
;
168 if (cbdataReferenceValidDone(callback_data
, &cbdata_
))
169 callback_(answer
, cbdata_
);
171 // not really meaningful just before delete, but here for completeness sake
177 ACLChecklist::ACLChecklist() :
180 callback_data (NULL
),
184 allow_(ACCESS_DENIED
),
185 asyncStage_(asyncNone
),
186 state_(NullState::Instance()),
191 ACLChecklist::~ACLChecklist()
193 assert (!asyncInProgress());
197 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
200 ACLChecklist::NullState
*
201 ACLChecklist::NullState::Instance()
207 ACLChecklist::NullState::checkForAsync(ACLChecklist
*) const
209 assert(false); // or the Checklist will never get out of the async state
212 ACLChecklist::NullState
ACLChecklist::NullState::_instance
;
215 ACLChecklist::changeState (AsyncState
*newState
)
217 /* only change from null to active and back again,
218 * not active to active.
219 * relax this once conversion to states is complete
222 assert (state_
== NullState::Instance() || newState
== NullState::Instance());
226 ACLChecklist::AsyncState
*
227 ACLChecklist::asyncState() const
233 * Kick off a non-blocking (slow) ACL access list test
235 * NP: this should probably be made Async now.
238 ACLChecklist::nonBlockingCheck(ACLCB
* callback_
, void *callback_data_
)
240 preCheck("slow rules");
241 callback
= callback_
;
242 callback_data
= cbdataReference(callback_data_
);
245 /** The ACL List should NEVER be NULL when calling this method.
246 * Always caller should check for NULL and handle appropriate to its needs first.
247 * We cannot select a sensible default for all callers here. */
248 if (accessList
== NULL
) {
249 debugs(28, DBG_CRITICAL
, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
250 checkCallback(ACCESS_DUNNO
);
254 if (prepNonBlocking()) {
255 matchAndFinish(); // calls markFinished() on success
256 if (!asyncInProgress())
257 completeNonBlocking();
258 } // else checkCallback() has been called
262 ACLChecklist::resumeNonBlockingCheck(AsyncState
*state
)
264 assert(asyncState() == state
);
265 changeState(NullState::Instance());
267 if (asyncStage_
== asyncStarting
) { // oops, we did not really go async
268 asyncStage_
= asyncFailed
; // goAsync() checks for that
269 // Do not fall through to resume checks from the async callback. Let
270 // the still-pending(!) goAsync() notice and notify its caller instead.
273 assert(asyncStage_
== asyncRunning
);
274 asyncStage_
= asyncNone
;
276 assert(!matchPath
.empty());
278 if (!prepNonBlocking())
279 return; // checkCallback() has been called
284 if (asyncInProgress())
285 assert(!matchPath
.empty()); // we have breadcrumbs to resume matching
287 completeNonBlocking();
290 /// performs (or resumes) an ACL tree match and, if successful, sets the action
292 ACLChecklist::matchAndFinish()
295 if (matchPath
.empty()) {
296 result
= accessList
->matches(this);
298 const Breadcrumb
top(matchPath
.top());
300 result
= top
.parent
->resumeMatchingAt(this, top
.position
);
303 if (result
) // the entire tree matched
304 markFinished(accessList
->winningAction(), "match");
308 ACLChecklist::fastCheck(const Acl::Tree
* list
)
310 PROF_start(aclCheckFast
);
312 preCheck("fast ACLs");
313 asyncCaller_
= false;
315 // Concurrent checks are not supported, but sequential checks are, and they
316 // may use a mixture of fastCheck(void) and fastCheck(list) calls.
317 const Acl::Tree
* const savedList
= changeAcl(list
);
319 // assume DENY/ALLOW on mis/matches due to action-free accessList
320 // matchAndFinish() takes care of the ALLOW case
321 if (accessList
&& cbdataReferenceValid(accessList
))
322 matchAndFinish(); // calls markFinished() on success
324 markFinished(ACCESS_DENIED
, "ACLs failed to match");
326 changeAcl(savedList
);
328 PROF_stop(aclCheckFast
);
329 return currentAnswer();
332 /* Warning: do not cbdata lock this here - it
333 * may be static or on the stack
336 ACLChecklist::fastCheck()
338 PROF_start(aclCheckFast
);
340 preCheck("fast rules");
341 asyncCaller_
= false;
343 debugs(28, 5, "aclCheckFast: list: " << accessList
);
344 const Acl::Tree
*acl
= cbdataReference(accessList
);
345 if (acl
!= NULL
&& cbdataReferenceValid(acl
)) {
346 matchAndFinish(); // calls markFinished() on success
348 // if finished (on a match or in exceptional cases), stop
350 cbdataReferenceDone(acl
);
352 PROF_stop(aclCheckFast
);
353 return currentAnswer();
356 // fall through for mismatch handling
359 // There were no rules to match or no rules matched
360 calcImplicitAnswer();
361 cbdataReferenceDone(acl
);
363 PROF_stop(aclCheckFast
);
365 return currentAnswer();
368 /// When no rules matched, the answer is the inversion of the last rule
369 /// action (or ACCESS_DUNNO if the reversal is not possible).
371 ACLChecklist::calcImplicitAnswer()
373 const allow_t lastAction
= (accessList
&& cbdataReferenceValid(accessList
)) ?
374 accessList
->lastAction() : allow_t(ACCESS_DUNNO
);
375 allow_t implicitRuleAnswer
= ACCESS_DUNNO
;
376 if (lastAction
== ACCESS_DENIED
) // reverse last seen "deny"
377 implicitRuleAnswer
= ACCESS_ALLOWED
;
378 else if (lastAction
== ACCESS_ALLOWED
) // reverse last seen "allow"
379 implicitRuleAnswer
= ACCESS_DENIED
;
380 // else we saw no rules and will respond with ACCESS_DUNNO
382 debugs(28, 3, HERE
<< this << " NO match found, last action " <<
383 lastAction
<< " so returning " << implicitRuleAnswer
);
384 markFinished(implicitRuleAnswer
, "implicit rule won");
388 ACLChecklist::callerGone()
390 return !cbdataReferenceValid(callback_data
);
394 ACLChecklist::bannedAction(const allow_t
&action
) const
396 const bool found
= std::find(bannedActions_
.begin(), bannedActions_
.end(), action
) != bannedActions_
.end();
397 debugs(28, 5, "Action '" << action
<< "/" << action
.kind
<< (found
? " is " : "is not") << " banned");
402 ACLChecklist::banAction(const allow_t
&action
)
404 bannedActions_
.push_back(action
);