4 * DEBUG: section 28 Access Control
5 * AUTHOR: Duane Wessels
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
37 #include "acl/Checklist.h"
39 #include "profiler/Profiler.h"
42 ACLChecklist::matchNonBlocking()
48 checkCallback(ACCESS_DUNNO
); // the answer does not really matter
52 /** The ACL List should NEVER be NULL when calling this method.
53 * Always caller should check for NULL and handle appropriate to its needs first.
54 * We cannot select a sensible default for all callers here. */
55 if (accessList
== NULL
) {
56 debugs(28, DBG_CRITICAL
, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
57 checkCallback(ACCESS_DUNNO
);
61 allow_t lastSeenKeyword
= ACCESS_DUNNO
;
62 /* NOTE: This holds a cbdata reference to the current access_list
63 * entry, not the whole list.
65 while (accessList
!= NULL
) {
67 * If the _acl_access is no longer valid (i.e. its been
68 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
71 if (!cbdataReferenceValid(accessList
)) {
72 cbdataReferenceDone(accessList
);
73 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
74 checkCallback(ACCESS_DUNNO
);
82 if (asyncInProgress()) {
88 * Either the request is allowed, denied, requires authentication.
90 debugs(28, 3, "ACLChecklist::check: " << this << " match found, calling back with " << currentAnswer());
91 cbdataReferenceDone(accessList
); /* A */
92 checkCallback(currentAnswer());
93 /* From here on in, this may be invalid */
97 lastSeenKeyword
= accessList
->allow
;
100 * Reference the next access entry
102 const acl_access
*A
= accessList
;
106 accessList
= cbdataReference(A
->next
);
108 cbdataReferenceDone(A
);
111 calcImplicitAnswer(lastSeenKeyword
);
112 checkCallback(currentAnswer());
116 ACLChecklist::asyncNeeded() const
118 return state_
!= NullState::Instance();
122 ACLChecklist::asyncInProgress() const
128 ACLChecklist::asyncInProgress(bool const newAsync
)
130 assert (!finished() && !(asyncInProgress() && newAsync
));
132 debugs(28, 3, "ACLChecklist::asyncInProgress: " << this <<
133 " async set to " << async_
);
137 ACLChecklist::finished() const
143 ACLChecklist::markFinished(const allow_t
&finalAnswer
, const char *reason
)
145 assert (!finished() && !asyncInProgress());
147 allow_
= finalAnswer
;
148 debugs(28, 3, HERE
<< this << " answer " << allow_
<< " for " << reason
);
151 /// Called first (and once) by all checks to initialize their state
153 ACLChecklist::preCheck(const char *what
)
155 debugs(28, 3, HERE
<< this << " checking " << what
);
160 ACLChecklist::checkAccessList()
162 debugs(28, 3, HERE
<< this << " checking '" << accessList
->cfgline
<< "'");
163 /* does the current AND clause match */
164 if (matchAclList(accessList
->aclList
, false))
165 markFinished(accessList
->allow
, "first matching rule won");
167 // If we are not finished() here, the caller must distinguish between
168 // slow async calls and pure rule mismatches using asyncInProgress().
172 ACLChecklist::checkForAsync()
174 asyncState()->checkForAsync(this);
177 // ACLFilledChecklist overwrites this to unclock something before we
180 ACLChecklist::checkCallback(allow_t answer
)
184 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer
);
186 callback_
= callback
;
189 if (cbdataReferenceValidDone(callback_data
, &cbdata_
))
190 callback_(answer
, cbdata_
);
195 /// An ACLChecklist::matchNodes() wrapper to simplify profiling.
197 ACLChecklist::matchAclList(const ACLList
* head
, bool const fast
)
199 // TODO: remove by using object con/destruction-based PROF_* macros.
200 PROF_start(aclMatchAclList
);
201 const bool result
= matchNodes(head
, fast
);
202 PROF_stop(aclMatchAclList
);
206 /** Returns true if and only if there was a match. If false is returned:
207 finished() indicates an error or exception of some kind, while
208 !finished() means there was a mismatch or an allowed slow async call.
209 If async calls are allowed (i.e. 'fast' was false), then those last
210 two cases can be distinguished using asyncInProgress().
213 ACLChecklist::matchNodes(const ACLList
* head
, bool const fast
)
217 for (const ACLList
*node
= head
; node
; node
= node
->next
) {
219 const NodeMatchingResult resultBeforeAsync
= matchNode(*node
, fast
);
221 if (resultBeforeAsync
== nmrMatch
)
224 if (resultBeforeAsync
== nmrMismatch
|| resultBeforeAsync
== nmrFinished
)
227 assert(resultBeforeAsync
== nmrNeedsAsync
);
229 // Ideally, this should be inside match() itself, but that requires
230 // prohibiting slow ACLs in options that do not support them.
231 // TODO: rename to maybeStartAsync()?
234 // Some match() code claims that an async lookup is needed, but then
235 // fails to start an async lookup when given a chance. We catch such
236 // cases here and call matchNode() again, hoping that some cached data
237 // prevents us from going async again.
238 // This is inefficient and ugly, but fixing all match() code, including
239 // the code it calls, such as ipcache_nbgethostbyname(), takes time.
240 if (!asyncInProgress()) { // failed to start an async operation
243 debugs(28, 3, HERE
<< this << " finished after failing to go async: " << currentAnswer());
244 return false; // an exceptional case
247 const NodeMatchingResult resultAfterAsync
= matchNode(*node
, true);
248 // the second call disables slow checks so we cannot go async again
249 assert(resultAfterAsync
!= nmrNeedsAsync
);
250 if (resultAfterAsync
== nmrMatch
)
253 assert(resultAfterAsync
== nmrMismatch
|| resultAfterAsync
== nmrFinished
);
257 assert(!finished()); // async operation is truly asynchronous
258 debugs(28, 3, HERE
<< this << " awaiting async operation");
262 debugs(28, 3, HERE
<< this << " success: all ACLs matched");
266 /// Check whether a single ACL matches, returning NodeMatchingResult
267 ACLChecklist::NodeMatchingResult
268 ACLChecklist::matchNode(const ACLList
&node
, bool const fast
)
270 const bool nodeMatched
= node
.matches(this);
271 const bool needsAsync
= asyncNeeded();
272 const bool matchFinished
= finished();
274 debugs(28, 3, HERE
<< this <<
275 " matched=" << nodeMatched
<<
276 " async=" << needsAsync
<<
277 " finished=" << matchFinished
);
279 /* There are eight possible outcomes of the matches() call based on
280 (matched, async, finished) permutations. We support these four:
281 matched,!async,!finished: a match (must check next rule node)
282 !matched,!async,!finished: a mismatch (whole rule fails to match)
283 !matched,!async,finished: error or special condition (propagate)
284 !matched,async,!finished: ACL needs to make an async call (pause)
288 // matches() should return false in all special cases
289 assert(!needsAsync
&& !matchFinished
);
294 // we cannot be done and need an async call at the same time
296 debugs(28, 3, HERE
<< this << " exception: " << currentAnswer());
301 debugs(28, 3, HERE
<< this << " simple mismatch");
305 /* we need an async call */
308 changeState(NullState::Instance()); // disable async checks
309 markFinished(ACCESS_DUNNO
, "async required but prohibited");
310 debugs(28, 3, HERE
<< this << " DUNNO because cannot async");
314 debugs(28, 3, HERE
<< this << " going async");
315 return nmrNeedsAsync
;
318 ACLChecklist::ACLChecklist() :
321 callback_data (NULL
),
324 allow_(ACCESS_DENIED
),
325 state_(NullState::Instance())
329 ACLChecklist::~ACLChecklist()
331 assert (!asyncInProgress());
333 cbdataReferenceDone(accessList
);
335 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
339 ACLChecklist::AsyncState::changeState (ACLChecklist
*checklist
, AsyncState
*newState
) const
341 checklist
->changeState(newState
);
344 ACLChecklist::NullState
*
345 ACLChecklist::NullState::Instance()
351 ACLChecklist::NullState::checkForAsync(ACLChecklist
*) const
354 ACLChecklist::NullState
ACLChecklist::NullState::_instance
;
357 ACLChecklist::changeState (AsyncState
*newState
)
359 /* only change from null to active and back again,
360 * not active to active.
361 * relax this once conversion to states is complete
364 assert (state_
== NullState::Instance() || newState
== NullState::Instance());
368 ACLChecklist::AsyncState
*
369 ACLChecklist::asyncState() const
375 * Kick off a non-blocking (slow) ACL access list test
377 * NP: this should probably be made Async now.
380 ACLChecklist::nonBlockingCheck(ACLCB
* callback_
, void *callback_data_
)
382 preCheck("slow rules");
383 callback
= callback_
;
384 callback_data
= cbdataReference(callback_data_
);
389 ACLChecklist::fastCheck(const ACLList
* list
)
391 PROF_start(aclCheckFast
);
393 preCheck("fast ACLs");
395 // assume DENY/ALLOW on mis/matches due to not having acl_access object
396 if (matchAclList(list
, true))
397 markFinished(ACCESS_ALLOWED
, "all ACLs matched");
398 else if (!finished())
399 markFinished(ACCESS_DENIED
, "ACL mismatched");
400 PROF_stop(aclCheckFast
);
401 return currentAnswer();
404 /* Warning: do not cbdata lock this here - it
405 * may be static or on the stack
408 ACLChecklist::fastCheck()
410 PROF_start(aclCheckFast
);
412 preCheck("fast rules");
414 allow_t lastSeenKeyword
= ACCESS_DUNNO
;
415 debugs(28, 5, "aclCheckFast: list: " << accessList
);
416 const acl_access
*acl
= cbdataReference(accessList
);
417 while (acl
!= NULL
&& cbdataReferenceValid(acl
)) {
418 // on a match, finish
419 if (matchAclList(acl
->aclList
, true))
420 markFinished(acl
->allow
, "first matching rule won");
422 // if finished (on a match or in exceptional cases), stop
424 cbdataReferenceDone(acl
);
425 PROF_stop(aclCheckFast
);
426 return currentAnswer();
429 // on a mismatch, try the next access rule
430 lastSeenKeyword
= acl
->allow
;
431 const acl_access
*A
= acl
;
432 acl
= cbdataReference(acl
->next
);
433 cbdataReferenceDone(A
);
436 // There were no rules to match or no rules matched
437 calcImplicitAnswer(lastSeenKeyword
);
438 PROF_stop(aclCheckFast
);
440 return currentAnswer();
443 /// When no rules matched, the answer is the inversion of the last seen rule
444 /// action (or ACCESS_DUNNO if the reversal is not possible). The caller
445 /// should set lastSeenAction to ACCESS_DUNNO if there were no rules to see.
447 ACLChecklist::calcImplicitAnswer(const allow_t
&lastSeenAction
)
449 allow_t implicitRuleAnswer
= ACCESS_DUNNO
;
450 if (lastSeenAction
== ACCESS_DENIED
) // reverse last seen "deny"
451 implicitRuleAnswer
= ACCESS_ALLOWED
;
452 else if (lastSeenAction
== ACCESS_ALLOWED
) // reverse last seen "allow"
453 implicitRuleAnswer
= ACCESS_DENIED
;
454 // else we saw no rules and will respond with ACCESS_DUNNO
456 debugs(28, 3, HERE
<< this << " NO match found, last action " <<
457 lastSeenAction
<< " so returning " << implicitRuleAnswer
);
458 markFinished(implicitRuleAnswer
, "implicit rule won");
462 ACLChecklist::checking() const
468 ACLChecklist::checking (bool const newValue
)
470 checking_
= newValue
;
474 ACLChecklist::callerGone()
476 return !cbdataReferenceValid(callback_data
);