]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Checklist.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Checklist.cc
1 /*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 28 Access Control */
10
11 #include "squid.h"
12 #include "acl/Checklist.h"
13 #include "acl/Tree.h"
14 #include "Debug.h"
15 #include "profiler/Profiler.h"
16
17 /// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
18 bool
19 ACLChecklist::prepNonBlocking()
20 {
21 assert(accessList);
22
23 if (callerGone()) {
24 checkCallback(ACCESS_DUNNO); // the answer does not really matter
25 return false;
26 }
27
28 /** \par
29 * If the accessList is no longer valid (i.e. its been
30 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
31 */
32
33 if (!cbdataReferenceValid(accessList)) {
34 cbdataReferenceDone(accessList);
35 debugs(28, 4, "ACLChecklist::check: " << this << " accessList is invalid");
36 checkCallback(ACCESS_DUNNO);
37 return false;
38 }
39
40 return true;
41 }
42
43 void
44 ACLChecklist::completeNonBlocking()
45 {
46 assert(!asyncInProgress());
47
48 if (!finished())
49 calcImplicitAnswer();
50
51 cbdataReferenceDone(accessList);
52 checkCallback(currentAnswer());
53 }
54
55 void
56 ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
57 {
58 assert (!finished() && !asyncInProgress());
59 finished_ = true;
60 allow_ = finalAnswer;
61 debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
62 }
63
64 /// Called first (and once) by all checks to initialize their state
65 void
66 ACLChecklist::preCheck(const char *what)
67 {
68 debugs(28, 3, HERE << this << " checking " << what);
69
70 // concurrent checks using the same Checklist are not supported
71 assert(!occupied_);
72 occupied_ = true;
73 asyncLoopDepth_ = 0;
74
75 AclMatchedName = NULL;
76 finished_ = false;
77 }
78
79 bool
80 ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const ACL *child)
81 {
82 assert(current && child);
83
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);
87 asyncLoopDepth_ = 0;
88
89 // if there are any breadcrumbs left, then follow them on the way down
90 bool result = false;
91 if (matchPath.empty()) {
92 result = child->matches(this);
93 } else {
94 const Breadcrumb top(matchPath.top());
95 assert(child == top.parent);
96 matchPath.pop();
97 result = top.parent->resumeMatchingAt(this, top.position);
98 }
99
100 if (asyncInProgress()) {
101 // We get here for node N that called goAsync() and then, as the call
102 // stack unwinds, for the nodes higher in the ACL tree that led to N.
103 matchPath.push(Breadcrumb(current, pos));
104 } else {
105 asyncLoc_.clear();
106 }
107
108 matchLoc_.clear();
109 return result;
110 }
111
112 bool
113 ACLChecklist::goAsync(AsyncState *state)
114 {
115 assert(state);
116 assert(!asyncInProgress());
117 assert(matchLoc_.parent);
118
119 // TODO: add a once-in-a-while WARNING about fast directive using slow ACL?
120 if (!asyncCaller_) {
121 debugs(28, 2, this << " a fast-only directive uses a slow ACL!");
122 return false;
123 }
124
125 // TODO: add a once-in-a-while WARNING about async loops?
126 if (matchLoc_ == asyncLoc_) {
127 debugs(28, 2, this << " a slow ACL resumes by going async again! (loop #" << asyncLoopDepth_ << ")");
128 // external_acl_type may cause async auth lookup plus its own async check
129 // which has the appearance of a loop. Allow some retries.
130 // TODO: make it configurable and check BH retry attempts vs this check?
131 if (asyncLoopDepth_ > 5)
132 return false;
133 }
134
135 asyncLoc_ = matchLoc_; // prevent async loops
136 ++asyncLoopDepth_;
137
138 asyncStage_ = asyncStarting;
139 changeState(state);
140 state->checkForAsync(this); // this is supposed to go async
141
142 // Did AsyncState object actually go async? If not, tell the caller.
143 if (asyncStage_ != asyncStarting) {
144 assert(asyncStage_ == asyncFailed);
145 asyncStage_ = asyncNone; // sanity restored
146 return false;
147 }
148
149 // yes, we must pause until the async callback calls resumeNonBlockingCheck
150 asyncStage_ = asyncRunning;
151 return true;
152 }
153
154 // ACLFilledChecklist overwrites this to unclock something before we
155 // "delete this"
156 void
157 ACLChecklist::checkCallback(allow_t answer)
158 {
159 ACLCB *callback_;
160 void *cbdata_;
161 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer);
162
163 callback_ = callback;
164 callback = NULL;
165
166 if (cbdataReferenceValidDone(callback_data, &cbdata_))
167 callback_(answer, cbdata_);
168
169 // not really meaningful just before delete, but here for completeness sake
170 occupied_ = false;
171
172 delete this;
173 }
174
175 ACLChecklist::ACLChecklist() :
176 accessList (NULL),
177 callback (NULL),
178 callback_data (NULL),
179 asyncCaller_(false),
180 occupied_(false),
181 finished_(false),
182 allow_(ACCESS_DENIED),
183 asyncStage_(asyncNone),
184 state_(NullState::Instance()),
185 asyncLoopDepth_(0)
186 {
187 }
188
189 ACLChecklist::~ACLChecklist()
190 {
191 assert (!asyncInProgress());
192
193 cbdataReferenceDone(accessList);
194
195 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
196 }
197
198 ACLChecklist::NullState *
199 ACLChecklist::NullState::Instance()
200 {
201 return &_instance;
202 }
203
204 void
205 ACLChecklist::NullState::checkForAsync(ACLChecklist *) const
206 {
207 assert(false); // or the Checklist will never get out of the async state
208 }
209
210 ACLChecklist::NullState ACLChecklist::NullState::_instance;
211
212 void
213 ACLChecklist::changeState (AsyncState *newState)
214 {
215 /* only change from null to active and back again,
216 * not active to active.
217 * relax this once conversion to states is complete
218 * RBC 02 2003
219 */
220 assert (state_ == NullState::Instance() || newState == NullState::Instance());
221 state_ = newState;
222 }
223
224 ACLChecklist::AsyncState *
225 ACLChecklist::asyncState() const
226 {
227 return state_;
228 }
229
230 /**
231 * Kick off a non-blocking (slow) ACL access list test
232 *
233 * NP: this should probably be made Async now.
234 */
235 void
236 ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
237 {
238 preCheck("slow rules");
239 callback = callback_;
240 callback_data = cbdataReference(callback_data_);
241 asyncCaller_ = true;
242
243 /** The ACL List should NEVER be NULL when calling this method.
244 * Always caller should check for NULL and handle appropriate to its needs first.
245 * We cannot select a sensible default for all callers here. */
246 if (accessList == NULL) {
247 debugs(28, DBG_CRITICAL, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
248 checkCallback(ACCESS_DUNNO);
249 return;
250 }
251
252 if (prepNonBlocking()) {
253 matchAndFinish(); // calls markFinished() on success
254 if (!asyncInProgress())
255 completeNonBlocking();
256 } // else checkCallback() has been called
257 }
258
259 void
260 ACLChecklist::resumeNonBlockingCheck(AsyncState *state)
261 {
262 assert(asyncState() == state);
263 changeState(NullState::Instance());
264
265 if (asyncStage_ == asyncStarting) { // oops, we did not really go async
266 asyncStage_ = asyncFailed; // goAsync() checks for that
267 // Do not fall through to resume checks from the async callback. Let
268 // the still-pending(!) goAsync() notice and notify its caller instead.
269 return;
270 }
271 assert(asyncStage_ == asyncRunning);
272 asyncStage_ = asyncNone;
273
274 assert(!matchPath.empty());
275
276 if (!prepNonBlocking())
277 return; // checkCallback() has been called
278
279 if (!finished())
280 matchAndFinish();
281
282 if (asyncInProgress())
283 assert(!matchPath.empty()); // we have breadcrumbs to resume matching
284 else
285 completeNonBlocking();
286 }
287
288 /// performs (or resumes) an ACL tree match and, if successful, sets the action
289 void
290 ACLChecklist::matchAndFinish()
291 {
292 bool result = false;
293 if (matchPath.empty()) {
294 result = accessList->matches(this);
295 } else {
296 const Breadcrumb top(matchPath.top());
297 matchPath.pop();
298 result = top.parent->resumeMatchingAt(this, top.position);
299 }
300
301 if (result) // the entire tree matched
302 markFinished(accessList->winningAction(), "match");
303 }
304
305 allow_t const &
306 ACLChecklist::fastCheck(const Acl::Tree * list)
307 {
308 PROF_start(aclCheckFast);
309
310 preCheck("fast ACLs");
311 asyncCaller_ = false;
312
313 // Concurrent checks are not supported, but sequential checks are, and they
314 // may use a mixture of fastCheck(void) and fastCheck(list) calls.
315 const Acl::Tree * const savedList = accessList;
316
317 accessList = cbdataReference(list);
318
319 // assume DENY/ALLOW on mis/matches due to action-free accessList
320 // matchAndFinish() takes care of the ALLOW case
321 if (accessList && cbdataReferenceValid(accessList))
322 matchAndFinish(); // calls markFinished() on success
323 if (!finished())
324 markFinished(ACCESS_DENIED, "ACLs failed to match");
325
326 cbdataReferenceDone(accessList);
327 accessList = savedList;
328 occupied_ = false;
329 PROF_stop(aclCheckFast);
330 return currentAnswer();
331 }
332
333 /* Warning: do not cbdata lock this here - it
334 * may be static or on the stack
335 */
336 allow_t const &
337 ACLChecklist::fastCheck()
338 {
339 PROF_start(aclCheckFast);
340
341 preCheck("fast rules");
342 asyncCaller_ = false;
343
344 debugs(28, 5, "aclCheckFast: list: " << accessList);
345 const Acl::Tree *acl = cbdataReference(accessList);
346 if (acl != NULL && cbdataReferenceValid(acl)) {
347 matchAndFinish(); // calls markFinished() on success
348
349 // if finished (on a match or in exceptional cases), stop
350 if (finished()) {
351 cbdataReferenceDone(acl);
352 occupied_ = false;
353 PROF_stop(aclCheckFast);
354 return currentAnswer();
355 }
356
357 // fall through for mismatch handling
358 }
359
360 // There were no rules to match or no rules matched
361 calcImplicitAnswer();
362 cbdataReferenceDone(acl);
363 occupied_ = false;
364 PROF_stop(aclCheckFast);
365
366 return currentAnswer();
367 }
368
369 /// When no rules matched, the answer is the inversion of the last rule
370 /// action (or ACCESS_DUNNO if the reversal is not possible).
371 void
372 ACLChecklist::calcImplicitAnswer()
373 {
374 const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ?
375 accessList->lastAction() : allow_t(ACCESS_DUNNO);
376 allow_t implicitRuleAnswer = ACCESS_DUNNO;
377 if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
378 implicitRuleAnswer = ACCESS_ALLOWED;
379 else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
380 implicitRuleAnswer = ACCESS_DENIED;
381 // else we saw no rules and will respond with ACCESS_DUNNO
382
383 debugs(28, 3, HERE << this << " NO match found, last action " <<
384 lastAction << " so returning " << implicitRuleAnswer);
385 markFinished(implicitRuleAnswer, "implicit rule won");
386 }
387
388 bool
389 ACLChecklist::callerGone()
390 {
391 return !cbdataReferenceValid(callback_data);
392 }
393