]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Checklist.cc
Merged from trunk
[thirdparty/squid.git] / src / acl / Checklist.cc
1 /*
2 * $Id$
3 *
4 * DEBUG: section 28 Access Control
5 * AUTHOR: Duane Wessels
6 *
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
9 *
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.
18 *
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.
23 *
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.
28 *
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.
32 *
33 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
34 */
35
36 #include "squid.h"
37 #include "acl/Checklist.h"
38 #include "Debug.h"
39 #include "profiler/Profiler.h"
40
41 void
42 ACLChecklist::matchNonBlocking()
43 {
44 if (checking())
45 return;
46
47 if (callerGone()) {
48 checkCallback(ACCESS_DUNNO); // the answer does not really matter
49 return;
50 }
51
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);
58 return;
59 }
60
61 allow_t lastSeenKeyword = ACCESS_DUNNO;
62 /* NOTE: This holds a cbdata reference to the current access_list
63 * entry, not the whole list.
64 */
65 while (accessList != NULL) {
66 /** \par
67 * If the _acl_access is no longer valid (i.e. its been
68 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
69 */
70
71 if (!cbdataReferenceValid(accessList)) {
72 cbdataReferenceDone(accessList);
73 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
74 checkCallback(ACCESS_DUNNO);
75 return;
76 }
77
78 checking (true);
79 checkAccessList();
80 checking (false);
81
82 if (asyncInProgress()) {
83 return;
84 }
85
86 if (finished()) {
87 /** \par
88 * Either the request is allowed, denied, requires authentication.
89 */
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 */
94 return;
95 }
96
97 lastSeenKeyword = accessList->allow;
98
99 /*
100 * Reference the next access entry
101 */
102 const acl_access *A = accessList;
103
104 assert (A);
105
106 accessList = cbdataReference(A->next);
107
108 cbdataReferenceDone(A);
109 }
110
111 calcImplicitAnswer(lastSeenKeyword);
112 checkCallback(currentAnswer());
113 }
114
115 bool
116 ACLChecklist::asyncNeeded() const
117 {
118 return state_ != NullState::Instance();
119 }
120
121 bool
122 ACLChecklist::asyncInProgress() const
123 {
124 return async_;
125 }
126
127 void
128 ACLChecklist::asyncInProgress(bool const newAsync)
129 {
130 assert (!finished() && !(asyncInProgress() && newAsync));
131 async_ = newAsync;
132 debugs(28, 3, "ACLChecklist::asyncInProgress: " << this <<
133 " async set to " << async_);
134 }
135
136 bool
137 ACLChecklist::finished() const
138 {
139 return finished_;
140 }
141
142 void
143 ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
144 {
145 assert (!finished() && !asyncInProgress());
146 finished_ = true;
147 allow_ = finalAnswer;
148 debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
149 }
150
151 /// Called first (and once) by all checks to initialize their state
152 void
153 ACLChecklist::preCheck(const char *what)
154 {
155 debugs(28, 3, HERE << this << " checking " << what);
156 finished_ = false;
157 }
158
159 void
160 ACLChecklist::checkAccessList()
161 {
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");
166
167 // If we are not finished() here, the caller must distinguish between
168 // slow async calls and pure rule mismatches using asyncInProgress().
169 }
170
171 void
172 ACLChecklist::checkForAsync()
173 {
174 asyncState()->checkForAsync(this);
175 }
176
177 // ACLFilledChecklist overwrites this to unclock something before we
178 // "delete this"
179 void
180 ACLChecklist::checkCallback(allow_t answer)
181 {
182 ACLCB *callback_;
183 void *cbdata_;
184 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer);
185
186 callback_ = callback;
187 callback = NULL;
188
189 if (cbdataReferenceValidDone(callback_data, &cbdata_))
190 callback_(answer, cbdata_);
191
192 delete this;
193 }
194
195 /// An ACLChecklist::matchNodes() wrapper to simplify profiling.
196 bool
197 ACLChecklist::matchAclList(const ACLList * head, bool const fast)
198 {
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);
203 return result;
204 }
205
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().
211 */
212 bool
213 ACLChecklist::matchNodes(const ACLList * head, bool const fast)
214 {
215 assert(!finished());
216
217 for (const ACLList *node = head; node; node = node->next) {
218
219 const NodeMatchingResult resultBeforeAsync = matchNode(*node, fast);
220
221 if (resultBeforeAsync == nmrMatch)
222 continue;
223
224 if (resultBeforeAsync == nmrMismatch || resultBeforeAsync == nmrFinished)
225 return false;
226
227 assert(resultBeforeAsync == nmrNeedsAsync);
228
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()?
232 checkForAsync();
233
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
241
242 if (finished()) {
243 debugs(28, 3, HERE << this << " finished after failing to go async: " << currentAnswer());
244 return false; // an exceptional case
245 }
246
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)
251 continue;
252
253 assert(resultAfterAsync == nmrMismatch || resultAfterAsync == nmrFinished);
254 return false;
255 }
256
257 assert(!finished()); // async operation is truly asynchronous
258 debugs(28, 3, HERE << this << " awaiting async operation");
259 return false;
260 }
261
262 debugs(28, 3, HERE << this << " success: all ACLs matched");
263 return true;
264 }
265
266 /// Check whether a single ACL matches, returning NodeMatchingResult
267 ACLChecklist::NodeMatchingResult
268 ACLChecklist::matchNode(const ACLList &node, bool const fast)
269 {
270 const bool nodeMatched = node.matches(this);
271 const bool needsAsync = asyncNeeded();
272 const bool matchFinished = finished();
273
274 debugs(28, 3, HERE << this <<
275 " matched=" << nodeMatched <<
276 " async=" << needsAsync <<
277 " finished=" << matchFinished);
278
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)
285 */
286
287 if (nodeMatched) {
288 // matches() should return false in all special cases
289 assert(!needsAsync && !matchFinished);
290 return nmrMatch;
291 }
292
293 if (matchFinished) {
294 // we cannot be done and need an async call at the same time
295 assert(!needsAsync);
296 debugs(28, 3, HERE << this << " exception: " << currentAnswer());
297 return nmrFinished;
298 }
299
300 if (!needsAsync) {
301 debugs(28, 3, HERE << this << " simple mismatch");
302 return nmrMismatch;
303 }
304
305 /* we need an async call */
306
307 if (fast) {
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");
311 return nmrFinished;
312 }
313
314 debugs(28, 3, HERE << this << " going async");
315 return nmrNeedsAsync;
316 }
317
318 ACLChecklist::ACLChecklist() :
319 accessList (NULL),
320 callback (NULL),
321 callback_data (NULL),
322 async_(false),
323 finished_(false),
324 allow_(ACCESS_DENIED),
325 state_(NullState::Instance())
326 {
327 }
328
329 ACLChecklist::~ACLChecklist()
330 {
331 assert (!asyncInProgress());
332
333 cbdataReferenceDone(accessList);
334
335 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
336 }
337
338 void
339 ACLChecklist::AsyncState::changeState (ACLChecklist *checklist, AsyncState *newState) const
340 {
341 checklist->changeState(newState);
342 }
343
344 ACLChecklist::NullState *
345 ACLChecklist::NullState::Instance()
346 {
347 return &_instance;
348 }
349
350 void
351 ACLChecklist::NullState::checkForAsync(ACLChecklist *) const
352 {}
353
354 ACLChecklist::NullState ACLChecklist::NullState::_instance;
355
356 void
357 ACLChecklist::changeState (AsyncState *newState)
358 {
359 /* only change from null to active and back again,
360 * not active to active.
361 * relax this once conversion to states is complete
362 * RBC 02 2003
363 */
364 assert (state_ == NullState::Instance() || newState == NullState::Instance());
365 state_ = newState;
366 }
367
368 ACLChecklist::AsyncState *
369 ACLChecklist::asyncState() const
370 {
371 return state_;
372 }
373
374 /**
375 * Kick off a non-blocking (slow) ACL access list test
376 *
377 * NP: this should probably be made Async now.
378 */
379 void
380 ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
381 {
382 preCheck("slow rules");
383 callback = callback_;
384 callback_data = cbdataReference(callback_data_);
385 matchNonBlocking();
386 }
387
388 allow_t const &
389 ACLChecklist::fastCheck(const ACLList * list)
390 {
391 PROF_start(aclCheckFast);
392
393 preCheck("fast ACLs");
394
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();
402 }
403
404 /* Warning: do not cbdata lock this here - it
405 * may be static or on the stack
406 */
407 allow_t const &
408 ACLChecklist::fastCheck()
409 {
410 PROF_start(aclCheckFast);
411
412 preCheck("fast rules");
413
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");
421
422 // if finished (on a match or in exceptional cases), stop
423 if (finished()) {
424 cbdataReferenceDone(acl);
425 PROF_stop(aclCheckFast);
426 return currentAnswer();
427 }
428
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);
434 }
435
436 // There were no rules to match or no rules matched
437 calcImplicitAnswer(lastSeenKeyword);
438 PROF_stop(aclCheckFast);
439
440 return currentAnswer();
441 }
442
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.
446 void
447 ACLChecklist::calcImplicitAnswer(const allow_t &lastSeenAction)
448 {
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
455
456 debugs(28, 3, HERE << this << " NO match found, last action " <<
457 lastSeenAction << " so returning " << implicitRuleAnswer);
458 markFinished(implicitRuleAnswer, "implicit rule won");
459 }
460
461 bool
462 ACLChecklist::checking() const
463 {
464 return checking_;
465 }
466
467 void
468 ACLChecklist::checking (bool const newValue)
469 {
470 checking_ = newValue;
471 }
472
473 bool
474 ACLChecklist::callerGone()
475 {
476 return !cbdataReferenceValid(callback_data);
477 }