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