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