Checklist.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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/FilledChecklist.h"
14 #include "acl/Tree.h"
15 #include "debug/Stream.h"
16 
17 #include <algorithm>
18 
20 bool
22 {
24 
25  if (callerGone()) {
26  checkCallback("caller is gone"); // the answer does not really matter
27  return false;
28  }
29 
30  return true;
31 }
32 
33 void
35 {
37 
38  if (!finished())
40 
41  checkCallback(nullptr);
42 }
43 
44 void
45 ACLChecklist::markFinished(const Acl::Answer &finalAnswer, const char *reason)
46 {
47  assert (!finished() && !asyncInProgress());
48  finished_ = true;
49  answer_ = finalAnswer;
51  debugs(28, 3, this << " answer " << answer_ << " for " << reason);
52 }
53 
55 void
56 ACLChecklist::preCheck(const char *what)
57 {
58  debugs(28, 3, this << " checking " << what);
59 
60  // concurrent checks using the same Checklist are not supported
61  assert(!occupied_);
62  occupied_ = true;
63  asyncLoopDepth_ = 0;
64 
65  lastCheckedName_.reset();
66  finished_ = false;
67 }
68 
69 bool
70 ACLChecklist::matchChild(const Acl::InnerNode * const current, const Acl::Nodes::const_iterator pos)
71 {
72  const auto &child = *pos;
73  assert(current && child);
74 
75  // Remember the current tree location to prevent "async loop" cases where
76  // the same child node wants to go async more than once.
77  matchLoc_ = Breadcrumb(current, pos);
78  asyncLoopDepth_ = 0;
79 
80  // if there are any breadcrumbs left, then follow them on the way down
81  bool result = false;
82  if (matchPath.empty()) {
83  result = child->matches(this);
84  } else {
85  const Breadcrumb top(matchPath.top());
86  assert(child == top.parent);
87  matchPath.pop();
88  result = top.parent->resumeMatchingAt(this, top.position);
89  }
90 
91  if (asyncInProgress()) {
92  // We get here for node N that called goAsync() and then, as the call
93  // stack unwinds, for the nodes higher in the ACL tree that led to N.
94  matchPath.push(Breadcrumb(current, pos));
95  } else {
96  asyncLoc_.clear();
97  }
98 
99  matchLoc_.clear();
100  return result;
101 }
102 
103 bool
105 {
108 
109  // TODO: add a once-in-a-while WARNING about fast directive using slow ACL?
110  if (!asyncCaller_) {
111  debugs(28, 2, this << " a fast-only directive uses a slow ACL!");
112  return false;
113  }
114 
115  // TODO: add a once-in-a-while WARNING about async loops?
116  if (matchLoc_ == asyncLoc_) {
117  debugs(28, 2, this << " a slow ACL resumes by going async again! (loop #" << asyncLoopDepth_ << ")");
118  // external_acl_type may cause async auth lookup plus its own async check
119  // which has the appearance of a loop. Allow some retries.
120  // TODO: make it configurable and check BH retry attempts vs this check?
121  if (asyncLoopDepth_ > 5)
122  return false;
123  }
124 
125  asyncLoc_ = matchLoc_; // prevent async loops
126  ++asyncLoopDepth_;
127 
129  starter(*Filled(this), acl); // this is supposed to go async
130 
131  // Did starter() actually go async? If not, tell the caller.
132  if (asyncStage_ != asyncStarting) {
134  asyncStage_ = asyncNone; // sanity restored
135  return false;
136  }
137 
138  // yes, we must pause until the async callback calls resumeNonBlockingCheck
140  return true;
141 }
142 
143 // ACLFilledChecklist overwrites this to unclock something before we
144 // "delete this"
145 void
146 ACLChecklist::checkCallback(const char * const abortReason)
147 {
148  if (abortReason)
149  markFinished(ACCESS_DUNNO, abortReason);
150  Assure(finished());
151 
152  ACLCB *callback_;
153  void *cbdata_;
154 
155  callback_ = callback;
156  callback = nullptr;
157 
159  callback_(currentAnswer(), cbdata_);
160 
161  // not really meaningful just before delete, but here for completeness sake
162  occupied_ = false;
163 
164  delete this;
165 }
166 
168  accessList (nullptr),
169  callback (nullptr),
170  callback_data (nullptr),
171  asyncCaller_(false),
172  occupied_(false),
173  finished_(false),
174  answer_(ACCESS_DENIED),
175  asyncStage_(asyncNone),
176  asyncLoopDepth_(0)
177 {
178 }
179 
181 {
182  assert (!asyncInProgress());
183  debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this);
184 }
185 
186 void
187 ACLChecklist::changeAcl(const acl_access * const replacement)
188 {
189  accessList = replacement ? *replacement : nullptr;
190 }
191 
193 ACLChecklist::swapAcl(const acl_access * const replacement)
194 {
195  const auto old = accessList;
196  changeAcl(replacement);
197  return old;
198 }
199 
205 void
206 ACLChecklist::nonBlockingCheck(ACLCB * callback_, void *callback_data_)
207 {
208  preCheck("slow rules");
209  callback = callback_;
210  callback_data = cbdataReference(callback_data_);
211  asyncCaller_ = true;
212 
216  if (accessList == nullptr) {
217  debugs(28, DBG_CRITICAL, "SECURITY ERROR: ACL " << this << " checked with nothing to match against!!");
218  checkCallback("nonBlockingCheck() without accessList");
219  return;
220  }
221 
222  if (prepNonBlocking()) {
223  matchAndFinish(); // calls markFinished() on success
224  if (!asyncInProgress())
226  } // else checkCallback() has been called
227 }
228 
229 void
231 {
232  if (asyncStage_ == asyncStarting) { // oops, we did not really go async
233  asyncStage_ = asyncFailed; // goAsync() checks for that
234  // Do not fall through to resume checks from the async callback. Let
235  // the still-pending(!) goAsync() notice and notify its caller instead.
236  return;
237  }
240 
241  assert(!matchPath.empty());
242 
243  if (!prepNonBlocking())
244  return; // checkCallback() has been called
245 
246  if (!finished())
247  matchAndFinish();
248 
249  if (asyncInProgress())
250  assert(!matchPath.empty()); // we have breadcrumbs to resume matching
251  else
253 }
254 
256 void
258 {
259  bool result = false;
260  if (matchPath.empty()) {
261  result = accessList->matches(this);
262  } else {
263  const Breadcrumb top(matchPath.top());
264  matchPath.pop();
265  result = top.parent->resumeMatchingAt(this, top.position);
266  }
267 
268  if (result) // the entire tree matched
270 }
271 
272 const Acl::Answer &
273 ACLChecklist::fastCheck(const ACLList * const list)
274 {
275  preCheck("fast ACLs");
276  asyncCaller_ = false;
277 
278  // Concurrent checks are not supported, but sequential checks are, and they
279  // may use a mixture of fastCheck(void) and fastCheck(list) calls.
280  const auto savedList = swapAcl(list);
281 
282  // assume DENY/ALLOW on mis/matches due to action-free accessList
283  // matchAndFinish() takes care of the ALLOW case
284  if (accessList)
285  matchAndFinish(); // calls markFinished() on success
286  if (!finished())
287  markFinished(ACCESS_DENIED, "ACLs failed to match");
288 
289  changeAcl(&savedList);
290  occupied_ = false;
291  return currentAnswer();
292 }
293 
294 /* Warning: do not cbdata lock this here - it
295  * may be static or on the stack
296  */
297 Acl::Answer const &
299 {
300  preCheck("fast rules");
301  asyncCaller_ = false;
302 
303  debugs(28, 5, "aclCheckFast: list: " << accessList);
304  if (accessList) {
305  matchAndFinish(); // calls markFinished() on success
306 
307  // if finished (on a match or in exceptional cases), stop
308  if (finished()) {
309  occupied_ = false;
310  return currentAnswer();
311  }
312 
313  // fall through for mismatch handling
314  }
315 
316  // There were no rules to match or no rules matched
318  occupied_ = false;
319 
320  return currentAnswer();
321 }
322 
325 void
327 {
328  const auto lastAction = accessList ?
330  auto implicitRuleAnswer = Acl::Answer(ACCESS_DUNNO);
331  if (lastAction == ACCESS_DENIED) // reverse last seen "deny"
332  implicitRuleAnswer = Acl::Answer(ACCESS_ALLOWED);
333  else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow"
334  implicitRuleAnswer = Acl::Answer(ACCESS_DENIED);
335  // else we saw no rules and will respond with ACCESS_DUNNO
336 
337  implicitRuleAnswer.implicit = true;
338  debugs(28, 3, this << " NO match found, last action " <<
339  lastAction << " so returning " << implicitRuleAnswer);
340  markFinished(implicitRuleAnswer, "implicit rule won");
341 }
342 
343 bool
345 {
347 }
348 
349 bool
351 {
352  const bool found = std::find(bannedActions_.begin(), bannedActions_.end(), action) != bannedActions_.end();
353  debugs(28, 5, "Action '" << action << "/" << action.kind << (found ? "' is " : "' is not") << " banned");
354  return found;
355 }
356 
357 void
359 {
360  bannedActions_.push_back(action);
361 }
362 
void(ACLFilledChecklist &, const Acl::Node &) AsyncStarter
a function that initiates asynchronous ACL checks; see goAsync()
Definition: Checklist.h:36
Answer winningAction() const
Returns the corresponding action after a successful tree match.
Definition: Tree.cc:15
#define DBG_CRITICAL
Definition: Stream.h:37
bool finished_
Definition: Checklist.h:201
#define cbdataReferenceValidDone(var, ptr)
Definition: cbdata.h:239
std::stack< Breadcrumb > matchPath
suspended (due to an async lookup) matches() in the ACL tree
Definition: Checklist.h:213
Acl::TreePointer accessList
Definition: Checklist.h:164
void banAction(const Acl::Answer &action)
add action to the list of banned actions
Definition: Checklist.cc:358
bool goAsync(AsyncStarter, const Acl::Node &)
Definition: Checklist.cc:104
int cbdataReferenceValid(const void *p)
Definition: cbdata.cc:270
std::optional< SBuf > lastCheckedName_
the name of the last evaluated ACL (if any ACLs were evaluated)
Definition: Checklist.h:218
ACLCB * callback
Definition: Checklist.h:168
An intermediate Acl::Node tree node. Manages a collection of child tree nodes.
Definition: InnerNode.h:22
#define cbdataReference(var)
Definition: cbdata.h:348
Position of a child node within an Acl::Node tree.
Definition: Checklist.h:177
const Acl::Answer & currentAnswer() const
Definition: Checklist.h:106
bool asyncInProgress() const
async call has been started and has not finished (or failed) yet
Definition: Checklist.h:101
void ACLCB(Acl::Answer, void *)
ACL checklist callback.
Definition: Checklist.h:23
RefCount< const Acl::InnerNode > parent
intermediate node in the ACL tree
Definition: Checklist.h:185
Answer lastAction() const
what action to use if no nodes matched
Definition: Tree.cc:21
bool finished() const
whether markFinished() was called
Definition: Checklist.h:99
Acl::TreePointer swapAcl(const acl_access *)
change the current ACL list
Definition: Checklist.cc:193
void * callback_data
Definition: Checklist.h:169
ACLFilledChecklist * Filled(ACLChecklist *checklist)
convenience and safety wrapper for dynamic_cast<ACLFilledChecklist*>
const Acl::Answer & fastCheck()
Definition: Checklist.cc:298
std::vector< Acl::Answer > bannedActions_
the list of actions which must ignored during acl checks
Definition: Checklist.h:215
void markFinished(const Acl::Answer &newAnswer, const char *reason)
Definition: Checklist.cc:45
void calcImplicitAnswer()
Definition: Checklist.cc:326
#define assert(EX)
Definition: assert.h:17
void changeAcl(const acl_access *)
change the current ACL list
Definition: Checklist.cc:187
#define Assure(condition)
Definition: Assure.h:35
bool resumeMatchingAt(ACLChecklist *checklist, Acl::Nodes::const_iterator pos) const
Resumes matching (suspended by an async call) at the given position.
Definition: InnerNode.cc:96
void completeNonBlocking()
Definition: Checklist.cc:34
bool callerGone()
Definition: Checklist.cc:344
void preCheck(const char *what)
prepare for checking ACLs; called once per check
Definition: Checklist.cc:56
virtual ~ACLChecklist()
Definition: Checklist.cc:180
unsigned asyncLoopDepth_
how many times the current async state has resumed
Definition: Checklist.h:208
Acl::Answer answer_
Definition: Checklist.h:202
Acl::Nodes::const_iterator position
child position inside parent
Definition: Checklist.h:186
AsyncStage asyncStage_
Definition: Checklist.h:205
Definition: Node.h:25
void matchAndFinish()
performs (or resumes) an ACL tree match and, if successful, sets the action
Definition: Checklist.cc:257
@ ACCESS_ALLOWED
Definition: Acl.h:42
@ ACCESS_DENIED
Definition: Acl.h:41
bool prepNonBlocking()
common parts of nonBlockingCheck() and resumeNonBlockingCheck()
Definition: Checklist.cc:21
std::optional< SBuf > lastCheckedName
the name of the ACL (if any) that was evaluated last while obtaining this answer
Definition: Acl.h:105
@ ACCESS_DUNNO
Definition: Acl.h:43
int kind
the matched custom access list verb (or zero)
Definition: Acl.h:99
bool asyncCaller_
whether the caller supports async/slow ACLs
Definition: Checklist.h:199
Breadcrumb asyncLoc_
currentNode_ that called goAsync()
Definition: Checklist.h:207
bool matchChild(const Acl::InnerNode *parent, Acl::Nodes::const_iterator pos)
Definition: Checklist.cc:70
Breadcrumb matchLoc_
location of the node running matches() now
Definition: Checklist.h:206
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void checkCallback(const char *abortReason)
Definition: Checklist.cc:146
void nonBlockingCheck(ACLCB *callback, void *callback_data)
Definition: Checklist.cc:206
void resumeNonBlockingCheck()
Definition: Checklist.cc:230
bool matches(ACLChecklist *checklist) const
Definition: Acl.cc:189
bool bannedAction(const Acl::Answer &action) const
whether the action is banned or not
Definition: Checklist.cc:350
bool occupied_
whether a check (fast or non-blocking) is in progress
Definition: Checklist.h:200

 

Introduction

Documentation

Support

Miscellaneous