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