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