]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/Checklist.cc
Upgrade Acl::Node::name to SBuf; remove AclMatchedName global (#1766)
[thirdparty/squid.git] / src / acl / Checklist.cc
CommitLineData
225b7b10 1/*
b8ae064d 2 * Copyright (C) 1996-2023 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"
5fb6afec 13#include "acl/FilledChecklist.h"
6f58d7d7 14#include "acl/Tree.h"
675b8408 15#include "debug/Stream.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
329c128c 58ACLChecklist::markFinished(const Acl::Answer &finalAnswer, const char *reason)
225b7b10 59{
3841dd46 60 assert (!finished() && !asyncInProgress());
225b7b10 61 finished_ = true;
329c128c 62 answer_ = finalAnswer;
25aa6c9a 63 answer_.lastCheckedName = lastCheckedName_;
bf95c10a 64 debugs(28, 3, this << " answer " << answer_ << " for " << reason);
225b7b10 65}
66
e0f7153c 67/// Called first (and once) by all checks to initialize their state
c35e260d 68void
e0f7153c 69ACLChecklist::preCheck(const char *what)
c35e260d 70{
bf95c10a 71 debugs(28, 3, this << " checking " << what);
7c469a68
AR
72
73 // concurrent checks using the same Checklist are not supported
74 assert(!occupied_);
75 occupied_ = true;
1707b9ad 76 asyncLoopDepth_ = 0;
7c469a68 77
25aa6c9a 78 lastCheckedName_.reset();
e0f7153c 79 finished_ = false;
c35e260d 80}
81
6f58d7d7 82bool
922513e5 83ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const Acl::Node *child)
225b7b10 84{
6f58d7d7
AR
85 assert(current && child);
86
115b045f 87 // Remember the current tree location to prevent "async loop" cases where
6f58d7d7
AR
88 // the same child node wants to go async more than once.
89 matchLoc_ = Breadcrumb(current, pos);
1707b9ad 90 asyncLoopDepth_ = 0;
6f58d7d7
AR
91
92 // if there are any breadcrumbs left, then follow them on the way down
93 bool result = false;
94 if (matchPath.empty()) {
95 result = child->matches(this);
96 } else {
97 const Breadcrumb top(matchPath.top());
98 assert(child == top.parent);
99 matchPath.pop();
100 result = top.parent->resumeMatchingAt(this, top.position);
101 }
102
103 if (asyncInProgress()) {
104 // We get here for node N that called goAsync() and then, as the call
105 // stack unwinds, for the nodes higher in the ACL tree that led to N.
106 matchPath.push(Breadcrumb(current, pos));
107 } else {
108 asyncLoc_.clear();
109 }
e0f7153c 110
6f58d7d7
AR
111 matchLoc_.clear();
112 return result;
225b7b10 113}
114
6f58d7d7 115bool
922513e5 116ACLChecklist::goAsync(AsyncStarter starter, const Acl::Node &acl)
225b7b10 117{
6f58d7d7
AR
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;
5fb6afec 141 starter(*Filled(this), acl); // this is supposed to go async
6f58d7d7 142
5fb6afec 143 // Did starter() actually go async? If not, tell the caller.
6f58d7d7
AR
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
25aa6c9a 158ACLChecklist::checkCallback(const 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 184 asyncStage_(asyncNone),
f53969cc 185 asyncLoopDepth_(0)
225b7b10 186{
225b7b10 187}
188
189ACLChecklist::~ACLChecklist()
190{
191 assert (!asyncInProgress());
62e76326 192
3d29e126 193 changeAcl(nullptr);
62e76326 194
bf8fe701 195 debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
225b7b10 196}
197
b50e327b
AJ
198/**
199 * Kick off a non-blocking (slow) ACL access list test
200 *
201 * NP: this should probably be made Async now.
202 */
225b7b10 203void
2efeb0b7 204ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
225b7b10 205{
e0f7153c 206 preCheck("slow rules");
225b7b10 207 callback = callback_;
208 callback_data = cbdataReference(callback_data_);
6f58d7d7
AR
209 asyncCaller_ = true;
210
922513e5 211 /** The ACL list should NEVER be NULL when calling this method.
6f58d7d7
AR
212 * Always caller should check for NULL and handle appropriate to its needs first.
213 * We cannot select a sensible default for all callers here. */
aee3523a 214 if (accessList == nullptr) {
6f58d7d7
AR
215 debugs(28, DBG_CRITICAL, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
216 checkCallback(ACCESS_DUNNO);
217 return;
218 }
219
220 if (prepNonBlocking()) {
221 matchAndFinish(); // calls markFinished() on success
222 if (!asyncInProgress())
223 completeNonBlocking();
224 } // else checkCallback() has been called
225}
226
227void
5fb6afec 228ACLChecklist::resumeNonBlockingCheck()
6f58d7d7 229{
6f58d7d7
AR
230 if (asyncStage_ == asyncStarting) { // oops, we did not really go async
231 asyncStage_ = asyncFailed; // goAsync() checks for that
232 // Do not fall through to resume checks from the async callback. Let
233 // the still-pending(!) goAsync() notice and notify its caller instead.
234 return;
235 }
236 assert(asyncStage_ == asyncRunning);
237 asyncStage_ = asyncNone;
238
239 assert(!matchPath.empty());
240
241 if (!prepNonBlocking())
242 return; // checkCallback() has been called
243
ef689e2b
AR
244 if (!finished())
245 matchAndFinish();
6f58d7d7
AR
246
247 if (asyncInProgress())
248 assert(!matchPath.empty()); // we have breadcrumbs to resume matching
249 else
250 completeNonBlocking();
251}
252
253/// performs (or resumes) an ACL tree match and, if successful, sets the action
254void
e936c41c
AR
255ACLChecklist::matchAndFinish()
256{
6f58d7d7
AR
257 bool result = false;
258 if (matchPath.empty()) {
259 result = accessList->matches(this);
260 } else {
261 const Breadcrumb top(matchPath.top());
262 matchPath.pop();
263 result = top.parent->resumeMatchingAt(this, top.position);
264 }
e936c41c 265
6f58d7d7
AR
266 if (result) // the entire tree matched
267 markFinished(accessList->winningAction(), "match");
2efeb0b7
AJ
268}
269
329c128c 270Acl::Answer const &
6f58d7d7 271ACLChecklist::fastCheck(const Acl::Tree * list)
2efeb0b7 272{
e0f7153c 273 preCheck("fast ACLs");
6f58d7d7
AR
274 asyncCaller_ = false;
275
7c469a68
AR
276 // Concurrent checks are not supported, but sequential checks are, and they
277 // may use a mixture of fastCheck(void) and fastCheck(list) calls.
3d29e126 278 const Acl::Tree * const savedList = changeAcl(list);
e0f7153c 279
b5e48e70 280 // assume DENY/ALLOW on mis/matches due to action-free accessList
6f58d7d7 281 // matchAndFinish() takes care of the ALLOW case
6f58d7d7
AR
282 if (accessList && cbdataReferenceValid(accessList))
283 matchAndFinish(); // calls markFinished() on success
284 if (!finished())
285 markFinished(ACCESS_DENIED, "ACLs failed to match");
286
3d29e126 287 changeAcl(savedList);
7c469a68 288 occupied_ = false;
2efeb0b7 289 return currentAnswer();
225b7b10 290}
291
b448c119 292/* Warning: do not cbdata lock this here - it
293 * may be static or on the stack
294 */
329c128c 295Acl::Answer const &
b448c119 296ACLChecklist::fastCheck()
297{
e0f7153c 298 preCheck("fast rules");
6f58d7d7 299 asyncCaller_ = false;
e0f7153c 300
bf8fe701 301 debugs(28, 5, "aclCheckFast: list: " << accessList);
6f58d7d7 302 const Acl::Tree *acl = cbdataReference(accessList);
aee3523a 303 if (acl != nullptr && cbdataReferenceValid(acl)) {
6f58d7d7 304 matchAndFinish(); // calls markFinished() on success
e0f7153c
AR
305
306 // if finished (on a match or in exceptional cases), stop
2efeb0b7 307 if (finished()) {
bea5dacc 308 cbdataReferenceDone(acl);
7c469a68 309 occupied_ = false;
2efeb0b7 310 return currentAnswer();
b448c119 311 }
312
6f58d7d7 313 // fall through for mismatch handling
b448c119 314 }
315
e0f7153c 316 // There were no rules to match or no rules matched
6f58d7d7
AR
317 calcImplicitAnswer();
318 cbdataReferenceDone(acl);
7c469a68 319 occupied_ = false;
2efeb0b7
AJ
320
321 return currentAnswer();
b448c119 322}
323
b5e48e70
AR
324/// When no rules matched, the answer is the inversion of the last rule
325/// action (or ACCESS_DUNNO if the reversal is not possible).
e0f7153c 326void
6f58d7d7 327ACLChecklist::calcImplicitAnswer()
e0f7153c 328{
329c128c 329 const auto lastAction = (accessList && cbdataReferenceValid(accessList)) ?
a70e75b7 330 accessList->lastAction() : Acl::Answer(ACCESS_DUNNO);
329c128c 331 auto implicitRuleAnswer = Acl::Answer(ACCESS_DUNNO);
b5e48e70 332 if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
329c128c 333 implicitRuleAnswer = Acl::Answer(ACCESS_ALLOWED);
b5e48e70 334 else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
329c128c 335 implicitRuleAnswer = Acl::Answer(ACCESS_DENIED);
e0f7153c
AR
336 // else we saw no rules and will respond with ACCESS_DUNNO
337
1c2b4465 338 implicitRuleAnswer.implicit = true;
bf95c10a 339 debugs(28, 3, this << " NO match found, last action " <<
b5e48e70 340 lastAction << " so returning " << implicitRuleAnswer);
e0f7153c
AR
341 markFinished(implicitRuleAnswer, "implicit rule won");
342}
b448c119 343
351fe86d
AR
344bool
345ACLChecklist::callerGone()
b448c119 346{
351fe86d 347 return !cbdataReferenceValid(callback_data);
b448c119 348}
f53969cc 349
640fe8fb 350bool
329c128c 351ACLChecklist::bannedAction(const Acl::Answer &action) const
640fe8fb
CT
352{
353 const bool found = std::find(bannedActions_.begin(), bannedActions_.end(), action) != bannedActions_.end();
0f250ac4 354 debugs(28, 5, "Action '" << action << "/" << action.kind << (found ? "' is " : "' is not") << " banned");
640fe8fb
CT
355 return found;
356}
357
358void
329c128c 359ACLChecklist::banAction(const Acl::Answer &action)
640fe8fb
CT
360{
361 bannedActions_.push_back(action);
362}
363