]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/Checklist.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Checklist.cc
CommitLineData
225b7b10 1/*
4ac4a490 2 * Copyright (C) 1996-2017 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
640fe8fb
CT
17#include <algorithm>
18
6f58d7d7
AR
19/// common parts of nonBlockingCheck() and resumeNonBlockingCheck()
20bool
21ACLChecklist::prepNonBlocking()
225b7b10 22{
6f58d7d7 23 assert(accessList);
62e76326 24
315b856d 25 if (callerGone()) {
e0f7153c 26 checkCallback(ACCESS_DUNNO); // the answer does not really matter
6f58d7d7 27 return false;
070b2c0c
AJ
28 }
29
e936c41c 30 /** \par
b5e48e70 31 * If the accessList is no longer valid (i.e. its been
e936c41c
AR
32 * freed because of a reconfigure), then bail with ACCESS_DUNNO.
33 */
62e76326 34
e936c41c
AR
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
ef689e2b 42 return true;
225b7b10 43}
44
45void
6f58d7d7 46ACLChecklist::completeNonBlocking()
225b7b10 47{
6f58d7d7 48 assert(!asyncInProgress());
225b7b10 49
6f58d7d7
AR
50 if (!finished())
51 calcImplicitAnswer();
52
53 cbdataReferenceDone(accessList);
54 checkCallback(currentAnswer());
225b7b10 55}
56
57void
e0f7153c 58ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason)
225b7b10 59{
3841dd46 60 assert (!finished() && !asyncInProgress());
225b7b10 61 finished_ = true;
e0f7153c
AR
62 allow_ = finalAnswer;
63 debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason);
225b7b10 64}
65
e0f7153c 66/// Called first (and once) by all checks to initialize their state
c35e260d 67void
e0f7153c 68ACLChecklist::preCheck(const char *what)
c35e260d 69{
e0f7153c 70 debugs(28, 3, HERE << this << " checking " << what);
7c469a68
AR
71
72 // concurrent checks using the same Checklist are not supported
73 assert(!occupied_);
74 occupied_ = true;
1707b9ad 75 asyncLoopDepth_ = 0;
7c469a68 76
6f58d7d7 77 AclMatchedName = NULL;
e0f7153c 78 finished_ = false;
c35e260d 79}
80
6f58d7d7
AR
81bool
82ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const ACL *child)
225b7b10 83{
6f58d7d7
AR
84 assert(current && child);
85
115b045f 86 // Remember the current tree location to prevent "async loop" cases where
6f58d7d7
AR
87 // the same child node wants to go async more than once.
88 matchLoc_ = Breadcrumb(current, pos);
1707b9ad 89 asyncLoopDepth_ = 0;
6f58d7d7
AR
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 }
e0f7153c 109
6f58d7d7
AR
110 matchLoc_.clear();
111 return result;
225b7b10 112}
113
6f58d7d7
AR
114bool
115ACLChecklist::goAsync(AsyncState *state)
225b7b10 116{
6f58d7d7
AR
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_) {
1707b9ad
AJ
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;
6f58d7d7
AR
135 }
136
137 asyncLoc_ = matchLoc_; // prevent async loops
1707b9ad 138 ++asyncLoopDepth_;
6f58d7d7
AR
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 }
e936c41c 150
6f58d7d7
AR
151 // yes, we must pause until the async callback calls resumeNonBlockingCheck
152 asyncStage_ = asyncRunning;
153 return true;
225b7b10 154}
155
351fe86d
AR
156// ACLFilledChecklist overwrites this to unclock something before we
157// "delete this"
225b7b10 158void
159ACLChecklist::checkCallback(allow_t answer)
160{
2efeb0b7 161 ACLCB *callback_;
225b7b10 162 void *cbdata_;
bf8fe701 163 debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer);
164
225b7b10 165 callback_ = callback;
166 callback = NULL;
62e76326 167
225b7b10 168 if (cbdataReferenceValidDone(callback_data, &cbdata_))
62e76326 169 callback_(answer, cbdata_);
170
7c469a68
AR
171 // not really meaningful just before delete, but here for completeness sake
172 occupied_ = false;
173
00d77d6b 174 delete this;
225b7b10 175}
62e76326 176
cc192b50 177ACLChecklist::ACLChecklist() :
f53969cc
SM
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)
225b7b10 188{
225b7b10 189}
190
191ACLChecklist::~ACLChecklist()
192{
193 assert (!asyncInProgress());
62e76326 194
3d29e126 195 changeAcl(nullptr);
62e76326 196
bf8fe701 197 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
225b7b10 198}
199
225b7b10 200ACLChecklist::NullState *
201ACLChecklist::NullState::Instance()
202{
203 return &_instance;
204}
205
206void
207ACLChecklist::NullState::checkForAsync(ACLChecklist *) const
6f58d7d7
AR
208{
209 assert(false); // or the Checklist will never get out of the async state
210}
225b7b10 211
212ACLChecklist::NullState ACLChecklist::NullState::_instance;
213
214void
215ACLChecklist::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
226ACLChecklist::AsyncState *
227ACLChecklist::asyncState() const
228{
229 return state_;
230}
231
b50e327b
AJ
232/**
233 * Kick off a non-blocking (slow) ACL access list test
234 *
235 * NP: this should probably be made Async now.
236 */
225b7b10 237void
2efeb0b7 238ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
225b7b10 239{
e0f7153c 240 preCheck("slow rules");
225b7b10 241 callback = callback_;
242 callback_data = cbdataReference(callback_data_);
6f58d7d7
AR
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
261void
262ACLChecklist::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
ef689e2b
AR
281 if (!finished())
282 matchAndFinish();
6f58d7d7
AR
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
291void
e936c41c
AR
292ACLChecklist::matchAndFinish()
293{
6f58d7d7
AR
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 }
e936c41c 302
6f58d7d7
AR
303 if (result) // the entire tree matched
304 markFinished(accessList->winningAction(), "match");
2efeb0b7
AJ
305}
306
307allow_t const &
6f58d7d7 308ACLChecklist::fastCheck(const Acl::Tree * list)
2efeb0b7
AJ
309{
310 PROF_start(aclCheckFast);
e0f7153c
AR
311
312 preCheck("fast ACLs");
6f58d7d7
AR
313 asyncCaller_ = false;
314
7c469a68
AR
315 // Concurrent checks are not supported, but sequential checks are, and they
316 // may use a mixture of fastCheck(void) and fastCheck(list) calls.
3d29e126 317 const Acl::Tree * const savedList = changeAcl(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
3d29e126 326 changeAcl(savedList);
7c469a68 327 occupied_ = false;
2efeb0b7
AJ
328 PROF_stop(aclCheckFast);
329 return currentAnswer();
225b7b10 330}
331
b448c119 332/* Warning: do not cbdata lock this here - it
333 * may be static or on the stack
334 */
2efeb0b7 335allow_t const &
b448c119 336ACLChecklist::fastCheck()
337{
338 PROF_start(aclCheckFast);
2efeb0b7 339
e0f7153c 340 preCheck("fast rules");
6f58d7d7 341 asyncCaller_ = false;
e0f7153c 342
bf8fe701 343 debugs(28, 5, "aclCheckFast: list: " << accessList);
6f58d7d7
AR
344 const Acl::Tree *acl = cbdataReference(accessList);
345 if (acl != NULL && cbdataReferenceValid(acl)) {
346 matchAndFinish(); // calls markFinished() on success
e0f7153c
AR
347
348 // if finished (on a match or in exceptional cases), stop
2efeb0b7 349 if (finished()) {
bea5dacc 350 cbdataReferenceDone(acl);
7c469a68 351 occupied_ = false;
e0f7153c 352 PROF_stop(aclCheckFast);
2efeb0b7 353 return currentAnswer();
b448c119 354 }
355
6f58d7d7 356 // fall through for mismatch handling
b448c119 357 }
358
e0f7153c 359 // There were no rules to match or no rules matched
6f58d7d7
AR
360 calcImplicitAnswer();
361 cbdataReferenceDone(acl);
7c469a68 362 occupied_ = false;
b448c119 363 PROF_stop(aclCheckFast);
2efeb0b7
AJ
364
365 return currentAnswer();
b448c119 366}
367
b5e48e70
AR
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).
e0f7153c 370void
6f58d7d7 371ACLChecklist::calcImplicitAnswer()
e0f7153c 372{
b5e48e70
AR
373 const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ?
374 accessList->lastAction() : allow_t(ACCESS_DUNNO);
e0f7153c 375 allow_t implicitRuleAnswer = ACCESS_DUNNO;
b5e48e70 376 if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
e0f7153c 377 implicitRuleAnswer = ACCESS_ALLOWED;
b5e48e70 378 else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
e0f7153c
AR
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 " <<
b5e48e70 383 lastAction << " so returning " << implicitRuleAnswer);
e0f7153c
AR
384 markFinished(implicitRuleAnswer, "implicit rule won");
385}
b448c119 386
351fe86d
AR
387bool
388ACLChecklist::callerGone()
b448c119 389{
351fe86d 390 return !cbdataReferenceValid(callback_data);
b448c119 391}
f53969cc 392
640fe8fb
CT
393bool
394ACLChecklist::bannedAction(const allow_t &action) const
395{
396 const bool found = std::find(bannedActions_.begin(), bannedActions_.end(), action) != bannedActions_.end();
0f250ac4 397 debugs(28, 5, "Action '" << action << "/" << action.kind << (found ? "' is " : "' is not") << " banned");
640fe8fb
CT
398 return found;
399}
400
401void
402ACLChecklist::banAction(const allow_t &action)
403{
404 bannedActions_.push_back(action);
405}
406