]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/Checklist.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Checklist.cc
CommitLineData
225b7b10 1/*
bde978a6 2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
bbc27441
AJ
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.
225b7b10 7 */
8
bbc27441
AJ
9/* DEBUG: section 28 Access Control */
10
582c2af2 11#include "squid.h"
351fe86d 12#include "acl/Checklist.h"
6f58d7d7 13#include "acl/Tree.h"
582c2af2
FC
14#include "Debug.h"
15#include "profiler/Profiler.h"
225b7b10 16
6f58d7d7
AR
17/// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
18bool
19ACLChecklist::prepNonBlocking()
225b7b10 20{
6f58d7d7 21 assert(accessList);
62e76326 22
315b856d 23 if (callerGone()) {
e0f7153c 24 checkCallback(ACCESS_DUNNO); // the answer does not really matter
6f58d7d7 25 return false;
070b2c0c
AJ
26 }
27
e936c41c 28 /** \par
b5e48e70 29 * If the accessList is no longer valid (i.e. its been
e936c41c
AR
30 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
31 */
62e76326 32
e936c41c
AR
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
ef689e2b 40 return true;
225b7b10 41}
42
43void
6f58d7d7 44ACLChecklist::completeNonBlocking()
225b7b10 45{
6f58d7d7 46 assert(!asyncInProgress());
225b7b10 47
6f58d7d7
AR
48 if (!finished())
49 calcImplicitAnswer();
50
51 cbdataReferenceDone(accessList);
52 checkCallback(currentAnswer());
225b7b10 53}
54
55void
e0f7153c 56ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
225b7b10 57{
3841dd46 58 assert (!finished() && !asyncInProgress());
225b7b10 59 finished_ = true;
e0f7153c
AR
60 allow_ = finalAnswer;
61 debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
225b7b10 62}
63
e0f7153c 64/// Called first (and once) by all checks to initialize their state
c35e260d 65void
e0f7153c 66ACLChecklist::preCheck(const char *what)
c35e260d 67{
e0f7153c 68 debugs(28, 3, HERE << this << " checking " << what);
7c469a68
AR
69
70 // concurrent checks using the same Checklist are not supported
71 assert(!occupied_);
72 occupied_ = true;
1707b9ad 73 asyncLoopDepth_ = 0;
7c469a68 74
6f58d7d7 75 AclMatchedName = NULL;
e0f7153c 76 finished_ = false;
c35e260d 77}
78
6f58d7d7
AR
79bool
80ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const ACL *child)
225b7b10 81{
6f58d7d7
AR
82 assert(current && child);
83
115b045f 84 // Remember the current tree location to prevent "async loop" cases where
6f58d7d7
AR
85 // the same child node wants to go async more than once.
86 matchLoc_ = Breadcrumb(current, pos);
1707b9ad 87 asyncLoopDepth_ = 0;
6f58d7d7
AR
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 }
e0f7153c 107
6f58d7d7
AR
108 matchLoc_.clear();
109 return result;
225b7b10 110}
111
6f58d7d7
AR
112bool
113ACLChecklist::goAsync(AsyncState *state)
225b7b10 114{
6f58d7d7
AR
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_) {
1707b9ad
AJ
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;
6f58d7d7
AR
133 }
134
135 asyncLoc_ = matchLoc_; // prevent async loops
1707b9ad 136 ++asyncLoopDepth_;
6f58d7d7
AR
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 }
e936c41c 148
6f58d7d7
AR
149 // yes, we must pause until the async callback calls resumeNonBlockingCheck
150 asyncStage_ = asyncRunning;
151 return true;
225b7b10 152}
153
351fe86d
AR
154// ACLFilledChecklist overwrites this to unclock something before we
155// "delete this"
225b7b10 156void
157ACLChecklist::checkCallback(allow_t answer)
158{
2efeb0b7 159 ACLCB *callback_;
225b7b10 160 void *cbdata_;
bf8fe701 161 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer);
162
225b7b10 163 callback_ = callback;
164 callback = NULL;
62e76326 165
225b7b10 166 if (cbdataReferenceValidDone(callback_data, &cbdata_))
62e76326 167 callback_(answer, cbdata_);
168
7c469a68
AR
169 // not really meaningful just before delete, but here for completeness sake
170 occupied_ = false;
171
00d77d6b 172 delete this;
225b7b10 173}
62e76326 174
cc192b50 175ACLChecklist::ACLChecklist() :
f53969cc
SM
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)
225b7b10 186{
225b7b10 187}
188
189ACLChecklist::~ACLChecklist()
190{
191 assert (!asyncInProgress());
62e76326 192
225b7b10 193 cbdataReferenceDone(accessList);
62e76326 194
bf8fe701 195 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
225b7b10 196}
197
225b7b10 198ACLChecklist::NullState *
199ACLChecklist::NullState::Instance()
200{
201 return &_instance;
202}
203
204void
205ACLChecklist::NullState::checkForAsync(ACLChecklist *) const
6f58d7d7
AR
206{
207 assert(false); // or the Checklist will never get out of the async state
208}
225b7b10 209
210ACLChecklist::NullState ACLChecklist::NullState::_instance;
211
212void
213ACLChecklist::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
224ACLChecklist::AsyncState *
225ACLChecklist::asyncState() const
226{
227 return state_;
228}
229
b50e327b
AJ
230/**
231 * Kick off a non-blocking (slow) ACL access list test
232 *
233 * NP: this should probably be made Async now.
234 */
225b7b10 235void
2efeb0b7 236ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
225b7b10 237{
e0f7153c 238 preCheck("slow rules");
225b7b10 239 callback = callback_;
240 callback_data = cbdataReference(callback_data_);
6f58d7d7
AR
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
259void
260ACLChecklist::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
ef689e2b
AR
279 if (!finished())
280 matchAndFinish();
6f58d7d7
AR
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
289void
e936c41c
AR
290ACLChecklist::matchAndFinish()
291{
6f58d7d7
AR
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 }
e936c41c 300
6f58d7d7
AR
301 if (result) // the entire tree matched
302 markFinished(accessList->winningAction(), "match");
2efeb0b7
AJ
303}
304
305allow_t const &
6f58d7d7 306ACLChecklist::fastCheck(const Acl::Tree * list)
2efeb0b7
AJ
307{
308 PROF_start(aclCheckFast);
e0f7153c
AR
309
310 preCheck("fast ACLs");
6f58d7d7
AR
311 asyncCaller_ = false;
312
7c469a68
AR
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
2154a209 317 accessList = cbdataReference(list);
e0f7153c 318
b5e48e70 319 // assume DENY/ALLOW on mis/matches due to action-free accessList
6f58d7d7 320 // matchAndFinish() takes care of the ALLOW case
6f58d7d7
AR
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);
7c469a68
AR
327 accessList = savedList;
328 occupied_ = false;
2efeb0b7
AJ
329 PROF_stop(aclCheckFast);
330 return currentAnswer();
225b7b10 331}
332
b448c119 333/* Warning: do not cbdata lock this here - it
334 * may be static or on the stack
335 */
2efeb0b7 336allow_t const &
b448c119 337ACLChecklist::fastCheck()
338{
339 PROF_start(aclCheckFast);
2efeb0b7 340
e0f7153c 341 preCheck("fast rules");
6f58d7d7 342 asyncCaller_ = false;
e0f7153c 343
bf8fe701 344 debugs(28, 5, "aclCheckFast: list: " << accessList);
6f58d7d7
AR
345 const Acl::Tree *acl = cbdataReference(accessList);
346 if (acl != NULL && cbdataReferenceValid(acl)) {
347 matchAndFinish(); // calls markFinished() on success
e0f7153c
AR
348
349 // if finished (on a match or in exceptional cases), stop
2efeb0b7 350 if (finished()) {
bea5dacc 351 cbdataReferenceDone(acl);
7c469a68 352 occupied_ = false;
e0f7153c 353 PROF_stop(aclCheckFast);
2efeb0b7 354 return currentAnswer();
b448c119 355 }
356
6f58d7d7 357 // fall through for mismatch handling
b448c119 358 }
359
e0f7153c 360 // There were no rules to match or no rules matched
6f58d7d7
AR
361 calcImplicitAnswer();
362 cbdataReferenceDone(acl);
7c469a68 363 occupied_ = false;
b448c119 364 PROF_stop(aclCheckFast);
2efeb0b7
AJ
365
366 return currentAnswer();
b448c119 367}
368
b5e48e70
AR
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).
e0f7153c 371void
6f58d7d7 372ACLChecklist::calcImplicitAnswer()
e0f7153c 373{
b5e48e70
AR
374 const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ?
375 accessList->lastAction() : allow_t(ACCESS_DUNNO);
e0f7153c 376 allow_t implicitRuleAnswer = ACCESS_DUNNO;
b5e48e70 377 if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
e0f7153c 378 implicitRuleAnswer = ACCESS_ALLOWED;
b5e48e70 379 else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
e0f7153c
AR
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 " <<
b5e48e70 384 lastAction << " so returning " << implicitRuleAnswer);
e0f7153c
AR
385 markFinished(implicitRuleAnswer, "implicit rule won");
386}
b448c119 387
351fe86d
AR
388bool
389ACLChecklist::callerGone()
b448c119 390{
351fe86d 391 return !cbdataReferenceValid(callback_data);
b448c119 392}
f53969cc 393