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