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