2 * DEBUG: section 28 Access Control
6 #include "acl/Checklist.h"
9 #include "profiler/Profiler.h"
11 /// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
13 ACLChecklist::prepNonBlocking()
18 checkCallback(ACCESS_DUNNO
); // the answer does not really matter
23 * If the _acl_access is no longer valid (i.e. its been
24 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
27 if (!cbdataReferenceValid(accessList
)) {
28 cbdataReferenceDone(accessList
);
29 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
30 checkCallback(ACCESS_DUNNO
);
34 // If doNonBlocking() was called for a finished() checklist to call
35 // the callbacks, then do not try to match again. XXX: resumeNonBlockingCheck() should check for this instead.
40 * Either the request is allowed, denied, requires authentication.
42 debugs(28, 3, this << " calling back with " << currentAnswer());
43 cbdataReferenceDone(accessList
); /* A */
44 checkCallback(currentAnswer());
45 /* From here on in, this may be invalid */
50 ACLChecklist::completeNonBlocking()
52 assert(!asyncInProgress());
57 cbdataReferenceDone(accessList
);
58 checkCallback(currentAnswer());
62 ACLChecklist::markFinished(const allow_t
&finalAnswer
, const char *reason
)
64 assert (!finished() && !asyncInProgress());
67 debugs(28, 3, HERE
<< this << " answer " << allow_
<< " for " << reason
);
70 /// Called first (and once) by all checks to initialize their state
72 ACLChecklist::preCheck(const char *what
)
74 debugs(28, 3, HERE
<< this << " checking " << what
);
75 AclMatchedName
= NULL
;
80 ACLChecklist::matchChild(const Acl::InnerNode
*current
, Acl::Nodes::const_iterator pos
, const ACL
*child
)
82 assert(current
&& child
);
84 // Remember the current tree location to prevent "async loop" cases where
85 // the same child node wants to go async more than once.
86 matchLoc_
= Breadcrumb(current
, pos
);
88 // if there are any breadcrumbs left, then follow them on the way down
90 if (matchPath
.empty()) {
91 result
= child
->matches(this);
93 const Breadcrumb
top(matchPath
.top());
94 assert(child
== top
.parent
);
96 result
= top
.parent
->resumeMatchingAt(this, top
.position
);
99 if (asyncInProgress()) {
100 // We get here for node N that called goAsync() and then, as the call
101 // stack unwinds, for the nodes higher in the ACL tree that led to N.
102 matchPath
.push(Breadcrumb(current
, pos
));
112 ACLChecklist::goAsync(AsyncState
*state
)
115 assert(!asyncInProgress());
116 assert(matchLoc_
.parent
);
118 // TODO: add a once-in-a-while WARNING about fast directive using slow ACL?
120 debugs(28, 2, this << " a fast-only directive uses a slow ACL!");
124 // TODO: add a once-in-a-while WARNING about async loops?
125 if (matchLoc_
== asyncLoc_
) {
126 debugs(28, 2, this << " a slow ACL resumes by going async again!");
130 asyncLoc_
= matchLoc_
; // prevent async loops
132 asyncStage_
= asyncStarting
;
134 state
->checkForAsync(this); // this is supposed to go async
136 // Did AsyncState object actually go async? If not, tell the caller.
137 if (asyncStage_
!= asyncStarting
) {
138 assert(asyncStage_
== asyncFailed
);
139 asyncStage_
= asyncNone
; // sanity restored
143 // yes, we must pause until the async callback calls resumeNonBlockingCheck
144 asyncStage_
= asyncRunning
;
148 // ACLFilledChecklist overwrites this to unclock something before we
151 ACLChecklist::checkCallback(allow_t answer
)
155 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer
);
157 callback_
= callback
;
160 if (cbdataReferenceValidDone(callback_data
, &cbdata_
))
161 callback_(answer
, cbdata_
);
166 ACLChecklist::ACLChecklist() :
169 callback_data (NULL
),
172 allow_(ACCESS_DENIED
),
173 asyncStage_(asyncNone
),
174 state_(NullState::Instance())
178 ACLChecklist::~ACLChecklist()
180 assert (!asyncInProgress());
182 cbdataReferenceDone(accessList
);
184 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
187 ACLChecklist::NullState
*
188 ACLChecklist::NullState::Instance()
194 ACLChecklist::NullState::checkForAsync(ACLChecklist
*) const
196 assert(false); // or the Checklist will never get out of the async state
199 ACLChecklist::NullState
ACLChecklist::NullState::_instance
;
202 ACLChecklist::changeState (AsyncState
*newState
)
204 /* only change from null to active and back again,
205 * not active to active.
206 * relax this once conversion to states is complete
209 assert (state_
== NullState::Instance() || newState
== NullState::Instance());
213 ACLChecklist::AsyncState
*
214 ACLChecklist::asyncState() const
220 * Kick off a non-blocking (slow) ACL access list test
222 * NP: this should probably be made Async now.
225 ACLChecklist::nonBlockingCheck(ACLCB
* callback_
, void *callback_data_
)
227 preCheck("slow rules");
228 callback
= callback_
;
229 callback_data
= cbdataReference(callback_data_
);
232 /** The ACL List should NEVER be NULL when calling this method.
233 * Always caller should check for NULL and handle appropriate to its needs first.
234 * We cannot select a sensible default for all callers here. */
235 if (accessList
== NULL
) {
236 debugs(28, DBG_CRITICAL
, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
237 checkCallback(ACCESS_DUNNO
);
241 if (prepNonBlocking()) {
242 matchAndFinish(); // calls markFinished() on success
243 if (!asyncInProgress())
244 completeNonBlocking();
245 } // else checkCallback() has been called
249 ACLChecklist::resumeNonBlockingCheck(AsyncState
*state
)
251 assert(asyncState() == state
);
252 changeState(NullState::Instance());
254 if (asyncStage_
== asyncStarting
) { // oops, we did not really go async
255 asyncStage_
= asyncFailed
; // goAsync() checks for that
256 // Do not fall through to resume checks from the async callback. Let
257 // the still-pending(!) goAsync() notice and notify its caller instead.
260 assert(asyncStage_
== asyncRunning
);
261 asyncStage_
= asyncNone
;
263 assert(!matchPath
.empty());
265 if (!prepNonBlocking())
266 return; // checkCallback() has been called
270 if (asyncInProgress())
271 assert(!matchPath
.empty()); // we have breadcrumbs to resume matching
273 completeNonBlocking();
276 /// performs (or resumes) an ACL tree match and, if successful, sets the action
278 ACLChecklist::matchAndFinish()
281 if (matchPath
.empty()) {
282 result
= accessList
->matches(this);
284 const Breadcrumb
top(matchPath
.top());
286 result
= top
.parent
->resumeMatchingAt(this, top
.position
);
289 if (result
) // the entire tree matched
290 markFinished(accessList
->winningAction(), "match");
294 ACLChecklist::fastCheck(const Acl::Tree
* list
)
296 PROF_start(aclCheckFast
);
298 preCheck("fast ACLs");
299 asyncCaller_
= false;
301 // This call is not compatible with a pre-set accessList because we cannot
302 // tell whether this Checklist is used by some other concurent call, which
307 // assume DENY/ALLOW on mis/matches due to not having acl_access object
308 // matchAndFinish() takes care of the ALLOW case
309 cbdataReference(accessList
); // required for cbdataReferenceValid()
310 if (accessList
&& cbdataReferenceValid(accessList
))
311 matchAndFinish(); // calls markFinished() on success
313 markFinished(ACCESS_DENIED
, "ACLs failed to match");
315 cbdataReferenceDone(accessList
);
316 PROF_stop(aclCheckFast
);
317 return currentAnswer();
320 /* Warning: do not cbdata lock this here - it
321 * may be static or on the stack
324 ACLChecklist::fastCheck()
326 PROF_start(aclCheckFast
);
328 preCheck("fast rules");
329 asyncCaller_
= false;
331 debugs(28, 5, "aclCheckFast: list: " << accessList
);
332 const Acl::Tree
*acl
= cbdataReference(accessList
);
333 if (acl
!= NULL
&& cbdataReferenceValid(acl
)) {
334 matchAndFinish(); // calls markFinished() on success
336 // if finished (on a match or in exceptional cases), stop
338 cbdataReferenceDone(acl
);
339 PROF_stop(aclCheckFast
);
340 return currentAnswer();
343 // fall through for mismatch handling
346 // There were no rules to match or no rules matched
347 calcImplicitAnswer();
348 cbdataReferenceDone(acl
);
349 PROF_stop(aclCheckFast
);
351 return currentAnswer();
354 /// When no rules matched, the answer is the inversion of the last seen rule
355 /// action (or ACCESS_DUNNO if the reversal is not possible). The caller
356 /// should set lastSeenAction to ACCESS_DUNNO if there were no rules to see.
358 ACLChecklist::calcImplicitAnswer()
360 // XXX: rename lastSeenAction after review and before commit
361 const allow_t lastSeenAction
= (accessList
&& cbdataReferenceValid(accessList
)) ?
362 accessList
->lastAction() : allow_t(ACCESS_DUNNO
);
363 allow_t implicitRuleAnswer
= ACCESS_DUNNO
;
364 if (lastSeenAction
== ACCESS_DENIED
) // reverse last seen "deny"
365 implicitRuleAnswer
= ACCESS_ALLOWED
;
366 else if (lastSeenAction
== ACCESS_ALLOWED
) // reverse last seen "allow"
367 implicitRuleAnswer
= ACCESS_DENIED
;
368 // else we saw no rules and will respond with ACCESS_DUNNO
370 debugs(28, 3, HERE
<< this << " NO match found, last action " <<
371 lastSeenAction
<< " so returning " << implicitRuleAnswer
);
372 markFinished(implicitRuleAnswer
, "implicit rule won");
376 ACLChecklist::callerGone()
378 return !cbdataReferenceValid(callback_data
);