2 * DEBUG: section 28 Access Control
3 * AUTHOR: Duane Wessels
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
35 #include "acl/Checklist.h"
37 #include "profiler/Profiler.h"
40 ACLChecklist::matchNonBlocking()
46 checkCallback(ACCESS_DUNNO
); // the answer does not really matter
50 /** The ACL List should NEVER be NULL when calling this method.
51 * Always caller should check for NULL and handle appropriate to its needs first.
52 * We cannot select a sensible default for all callers here. */
53 if (accessList
== NULL
) {
54 debugs(28, DBG_CRITICAL
, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
55 checkCallback(ACCESS_DUNNO
);
59 allow_t lastSeenKeyword
= ACCESS_DUNNO
;
60 /* NOTE: This holds a cbdata reference to the current access_list
61 * entry, not the whole list.
63 while (accessList
!= NULL
) {
65 * If the _acl_access is no longer valid (i.e. its been
66 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
69 if (!cbdataReferenceValid(accessList
)) {
70 cbdataReferenceDone(accessList
);
71 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
72 checkCallback(ACCESS_DUNNO
);
80 if (asyncInProgress()) {
86 * Either the request is allowed, denied, requires authentication.
88 debugs(28, 3, "ACLChecklist::check: " << this << " match found, calling back with " << currentAnswer());
89 cbdataReferenceDone(accessList
); /* A */
90 checkCallback(currentAnswer());
91 /* From here on in, this may be invalid */
95 lastSeenKeyword
= accessList
->allow
;
98 * Reference the next access entry
100 const acl_access
*A
= accessList
;
104 accessList
= cbdataReference(A
->next
);
106 cbdataReferenceDone(A
);
109 calcImplicitAnswer(lastSeenKeyword
);
110 checkCallback(currentAnswer());
114 ACLChecklist::asyncNeeded() const
116 return state_
!= NullState::Instance();
120 ACLChecklist::asyncInProgress() const
126 ACLChecklist::asyncInProgress(bool const newAsync
)
128 assert (!finished() && !(asyncInProgress() && newAsync
));
130 debugs(28, 3, "ACLChecklist::asyncInProgress: " << this <<
131 " async set to " << async_
);
135 ACLChecklist::finished() const
141 ACLChecklist::markFinished(const allow_t
&finalAnswer
, const char *reason
)
143 assert (!finished() && !asyncInProgress());
145 allow_
= finalAnswer
;
146 debugs(28, 3, HERE
<< this << " answer " << allow_
<< " for " << reason
);
149 /// Called first (and once) by all checks to initialize their state
151 ACLChecklist::preCheck(const char *what
)
153 debugs(28, 3, HERE
<< this << " checking " << what
);
158 ACLChecklist::checkAccessList()
160 debugs(28, 3, HERE
<< this << " checking '" << accessList
->cfgline
<< "'");
161 /* does the current AND clause match */
162 if (matchAclList(accessList
->aclList
, false))
163 markFinished(accessList
->allow
, "first matching rule won");
165 // If we are not finished() here, the caller must distinguish between
166 // slow async calls and pure rule mismatches using asyncInProgress().
170 ACLChecklist::checkForAsync()
172 asyncState()->checkForAsync(this);
175 // ACLFilledChecklist overwrites this to unclock something before we
178 ACLChecklist::checkCallback(allow_t answer
)
182 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer
);
184 callback_
= callback
;
187 if (cbdataReferenceValidDone(callback_data
, &cbdata_
))
188 callback_(answer
, cbdata_
);
193 /// An ACLChecklist::matchNodes() wrapper to simplify profiling.
195 ACLChecklist::matchAclList(const ACLList
* head
, bool const fast
)
197 // TODO: remove by using object con/destruction-based PROF_* macros.
198 PROF_start(aclMatchAclList
);
199 const bool result
= matchNodes(head
, fast
);
200 PROF_stop(aclMatchAclList
);
204 /** Returns true if and only if there was a match. If false is returned:
205 finished() indicates an error or exception of some kind, while
206 !finished() means there was a mismatch or an allowed slow async call.
207 If async calls are allowed (i.e. 'fast' was false), then those last
208 two cases can be distinguished using asyncInProgress().
211 ACLChecklist::matchNodes(const ACLList
* head
, bool const fast
)
215 for (const ACLList
*node
= head
; node
; node
= node
->next
) {
217 const NodeMatchingResult resultBeforeAsync
= matchNode(*node
, fast
);
219 if (resultBeforeAsync
== nmrMatch
)
222 if (resultBeforeAsync
== nmrMismatch
|| resultBeforeAsync
== nmrFinished
)
225 assert(resultBeforeAsync
== nmrNeedsAsync
);
227 // Ideally, this should be inside match() itself, but that requires
228 // prohibiting slow ACLs in options that do not support them.
229 // TODO: rename to maybeStartAsync()?
232 // Some match() code claims that an async lookup is needed, but then
233 // fails to start an async lookup when given a chance. We catch such
234 // cases here and call matchNode() again, hoping that some cached data
235 // prevents us from going async again.
236 // This is inefficient and ugly, but fixing all match() code, including
237 // the code it calls, such as ipcache_nbgethostbyname(), takes time.
238 if (!asyncInProgress()) { // failed to start an async operation
241 debugs(28, 3, HERE
<< this << " finished after failing to go async: " << currentAnswer());
242 return false; // an exceptional case
245 const NodeMatchingResult resultAfterAsync
= matchNode(*node
, true);
246 // the second call disables slow checks so we cannot go async again
247 assert(resultAfterAsync
!= nmrNeedsAsync
);
248 if (resultAfterAsync
== nmrMatch
)
251 assert(resultAfterAsync
== nmrMismatch
|| resultAfterAsync
== nmrFinished
);
255 assert(!finished()); // async operation is truly asynchronous
256 debugs(28, 3, HERE
<< this << " awaiting async operation");
260 debugs(28, 3, HERE
<< this << " success: all ACLs matched");
264 /// Check whether a single ACL matches, returning NodeMatchingResult
265 ACLChecklist::NodeMatchingResult
266 ACLChecklist::matchNode(const ACLList
&node
, bool const fast
)
268 const bool nodeMatched
= node
.matches(this);
269 const bool needsAsync
= asyncNeeded();
270 const bool matchFinished
= finished();
272 debugs(28, 3, HERE
<< this <<
273 " matched=" << nodeMatched
<<
274 " async=" << needsAsync
<<
275 " finished=" << matchFinished
);
277 /* There are eight possible outcomes of the matches() call based on
278 (matched, async, finished) permutations. We support these four:
279 matched,!async,!finished: a match (must check next rule node)
280 !matched,!async,!finished: a mismatch (whole rule fails to match)
281 !matched,!async,finished: error or special condition (propagate)
282 !matched,async,!finished: ACL needs to make an async call (pause)
286 // matches() should return false in all special cases
287 assert(!needsAsync
&& !matchFinished
);
292 // we cannot be done and need an async call at the same time
294 debugs(28, 3, HERE
<< this << " exception: " << currentAnswer());
299 debugs(28, 3, HERE
<< this << " simple mismatch");
303 /* we need an async call */
306 changeState(NullState::Instance()); // disable async checks
307 markFinished(ACCESS_DUNNO
, "async required but prohibited");
308 debugs(28, 3, HERE
<< this << " DUNNO because cannot async");
312 debugs(28, 3, HERE
<< this << " going async");
313 return nmrNeedsAsync
;
316 ACLChecklist::ACLChecklist() :
319 callback_data (NULL
),
322 allow_(ACCESS_DENIED
),
323 state_(NullState::Instance())
327 ACLChecklist::~ACLChecklist()
329 assert (!asyncInProgress());
331 cbdataReferenceDone(accessList
);
333 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
337 ACLChecklist::AsyncState::changeState (ACLChecklist
*checklist
, AsyncState
*newState
) const
339 checklist
->changeState(newState
);
342 ACLChecklist::NullState
*
343 ACLChecklist::NullState::Instance()
349 ACLChecklist::NullState::checkForAsync(ACLChecklist
*) const
352 ACLChecklist::NullState
ACLChecklist::NullState::_instance
;
355 ACLChecklist::changeState (AsyncState
*newState
)
357 /* only change from null to active and back again,
358 * not active to active.
359 * relax this once conversion to states is complete
362 assert (state_
== NullState::Instance() || newState
== NullState::Instance());
366 ACLChecklist::AsyncState
*
367 ACLChecklist::asyncState() const
373 * Kick off a non-blocking (slow) ACL access list test
375 * NP: this should probably be made Async now.
378 ACLChecklist::nonBlockingCheck(ACLCB
* callback_
, void *callback_data_
)
380 preCheck("slow rules");
381 callback
= callback_
;
382 callback_data
= cbdataReference(callback_data_
);
387 ACLChecklist::fastCheck(const ACLList
* list
)
389 PROF_start(aclCheckFast
);
391 preCheck("fast ACLs");
393 // assume DENY/ALLOW on mis/matches due to not having acl_access object
394 if (matchAclList(list
, true))
395 markFinished(ACCESS_ALLOWED
, "all ACLs matched");
396 else if (!finished())
397 markFinished(ACCESS_DENIED
, "ACL mismatched");
398 PROF_stop(aclCheckFast
);
399 return currentAnswer();
402 /* Warning: do not cbdata lock this here - it
403 * may be static or on the stack
406 ACLChecklist::fastCheck()
408 PROF_start(aclCheckFast
);
410 preCheck("fast rules");
412 allow_t lastSeenKeyword
= ACCESS_DUNNO
;
413 debugs(28, 5, "aclCheckFast: list: " << accessList
);
414 const acl_access
*acl
= cbdataReference(accessList
);
415 while (acl
!= NULL
&& cbdataReferenceValid(acl
)) {
416 // on a match, finish
417 if (matchAclList(acl
->aclList
, true))
418 markFinished(acl
->allow
, "first matching rule won");
420 // if finished (on a match or in exceptional cases), stop
422 cbdataReferenceDone(acl
);
423 PROF_stop(aclCheckFast
);
424 return currentAnswer();
427 // on a mismatch, try the next access rule
428 lastSeenKeyword
= acl
->allow
;
429 const acl_access
*A
= acl
;
430 acl
= cbdataReference(acl
->next
);
431 cbdataReferenceDone(A
);
434 // There were no rules to match or no rules matched
435 calcImplicitAnswer(lastSeenKeyword
);
436 PROF_stop(aclCheckFast
);
438 return currentAnswer();
441 /// When no rules matched, the answer is the inversion of the last seen rule
442 /// action (or ACCESS_DUNNO if the reversal is not possible). The caller
443 /// should set lastSeenAction to ACCESS_DUNNO if there were no rules to see.
445 ACLChecklist::calcImplicitAnswer(const allow_t
&lastSeenAction
)
447 allow_t implicitRuleAnswer
= ACCESS_DUNNO
;
448 if (lastSeenAction
== ACCESS_DENIED
) // reverse last seen "deny"
449 implicitRuleAnswer
= ACCESS_ALLOWED
;
450 else if (lastSeenAction
== ACCESS_ALLOWED
) // reverse last seen "allow"
451 implicitRuleAnswer
= ACCESS_DENIED
;
452 // else we saw no rules and will respond with ACCESS_DUNNO
454 debugs(28, 3, HERE
<< this << " NO match found, last action " <<
455 lastSeenAction
<< " so returning " << implicitRuleAnswer
);
456 markFinished(implicitRuleAnswer
, "implicit rule won");
460 ACLChecklist::checking() const
466 ACLChecklist::checking (bool const newValue
)
468 checking_
= newValue
;
472 ACLChecklist::callerGone()
474 return !cbdataReferenceValid(callback_data
);