]>
Commit | Line | Data |
---|---|---|
62c7f90e AR |
1 | #include "squid.h" |
2 | #include "structs.h" | |
3 | ||
4 | #include "ConfigParser.h" | |
62c7f90e AR |
5 | #include "HttpRequest.h" |
6 | #include "HttpReply.h" | |
127dce76 | 7 | #include "acl/FilledChecklist.h" |
62c7f90e AR |
8 | #include "adaptation/Service.h" |
9 | #include "adaptation/ServiceGroups.h" | |
10 | #include "adaptation/AccessRule.h" | |
abd4b611 | 11 | #include "adaptation/Config.h" |
62c7f90e AR |
12 | #include "adaptation/AccessCheck.h" |
13 | ||
14 | ||
d85b8894 | 15 | /** \cond AUTODOCS-IGNORE */ |
62c7f90e | 16 | cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN; |
d85b8894 | 17 | /** \endcond */ |
62c7f90e | 18 | |
abd4b611 AR |
19 | bool |
20 | Adaptation::AccessCheck::Start(Method method, VectPoint vp, | |
26ac0430 AJ |
21 | HttpRequest *req, HttpReply *rep, AccessCheckCallback *cb, void *cbdata) |
22 | { | |
abd4b611 AR |
23 | |
24 | if (Config::Enabled) { | |
25 | // the new check will call the callback and delete self, eventually | |
26 | AccessCheck *check = new AccessCheck(method, vp, req, rep, cb, cbdata); | |
27 | check->check(); | |
28 | return true; | |
26ac0430 | 29 | } |
abd4b611 AR |
30 | |
31 | debugs(83, 3, HERE << "adaptation off, skipping"); | |
32 | return false; | |
33 | } | |
34 | ||
62c7f90e | 35 | Adaptation::AccessCheck::AccessCheck(Method aMethod, |
26ac0430 AJ |
36 | VectPoint aPoint, |
37 | HttpRequest *aReq, | |
38 | HttpReply *aRep, | |
39 | AccessCheckCallback *aCallback, | |
40 | void *aCallbackData): AsyncJob("AccessCheck"), done(FALSE) | |
62c7f90e AR |
41 | { |
42 | // TODO: assign these at creation time | |
43 | ||
44 | method = aMethod; | |
45 | point = aPoint; | |
46 | ||
47 | req = HTTPMSGLOCK(aReq); | |
48 | rep = aRep ? HTTPMSGLOCK(aRep) : NULL; | |
49 | ||
50 | callback = aCallback; | |
51 | ||
52 | callback_data = cbdataReference(aCallbackData); | |
53 | ||
54 | acl_checklist = NULL; | |
55 | ||
192378eb | 56 | debugs(93, 5, HERE << "AccessCheck constructed for " << methodStr(method) << " " << vectPointStr(point)); |
62c7f90e AR |
57 | } |
58 | ||
59 | Adaptation::AccessCheck::~AccessCheck() | |
60 | { | |
61 | HTTPMSGUNLOCK(req); | |
62 | HTTPMSGUNLOCK(rep); | |
7e81de2e AR |
63 | if (callback_data) |
64 | cbdataReferenceDone(callback_data); | |
62c7f90e AR |
65 | } |
66 | ||
67 | /* | |
68 | * Walk the access rules list and find all classes that have at least | |
69 | * one service with matching method and vectoring point. | |
70 | */ | |
71 | void | |
72 | Adaptation::AccessCheck::check() | |
73 | { | |
192378eb | 74 | debugs(93, 4, HERE << "start checking"); |
62c7f90e AR |
75 | |
76 | typedef AccessRules::iterator ARI; | |
77 | for (ARI i = AllRules().begin(); i != AllRules().end(); ++i) { | |
78 | ||
79 | /* | |
80 | * We only find the first matching service because we only need | |
81 | * one matching service to justify ACL-checking a class. We might | |
82 | * use other services belonging to the class if the first service | |
83 | * turns out to be unusable for some reason. | |
84 | */ | |
85 | AccessRule *r = *i; | |
86 | ServicePointer service = findBestService(*r, false); | |
87 | if (service != NULL) { | |
192378eb | 88 | debugs(93, 5, HERE << "check: rule '" << r->id << "' has candidate service '" << service->cfg().key << "'"); |
62c7f90e AR |
89 | candidates += r->id; |
90 | } | |
91 | } | |
92 | ||
93 | checkCandidates(); | |
94 | } | |
95 | ||
96 | // XXX: Here and everywhere we call FindRule(topCandidate()): | |
97 | // Once we identified the candidate, we should not just ignore it | |
98 | // if reconfigure changes rules. We should either lock the rule to | |
99 | // prevent reconfigure from stealing it or restart the check with | |
100 | // new rules. Throwing an exception may also be appropriate. | |
101 | void | |
102 | Adaptation::AccessCheck::checkCandidates() | |
103 | { | |
192378eb | 104 | debugs(93, 4, HERE << "has " << candidates.size() << " rules"); |
62c7f90e AR |
105 | |
106 | while (!candidates.empty()) { | |
107 | if (AccessRule *r = FindRule(topCandidate())) { | |
b50e327b | 108 | /* BUG 2526: what to do when r->acl is empty?? */ |
62c7f90e | 109 | // XXX: we do not have access to conn->rfc931 here. |
127dce76 | 110 | acl_checklist = new ACLFilledChecklist(r->acl, req, dash_str); |
1c906324 | 111 | acl_checklist->reply = rep ? HTTPMSGLOCK(rep) : NULL; |
62c7f90e AR |
112 | acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this); |
113 | return; | |
114 | } | |
115 | ||
116 | candidates.shift(); // the rule apparently went away (reconfigure) | |
117 | } | |
118 | ||
119 | // when there are no canidates, fake answer 1 | |
192378eb | 120 | debugs(93, 4, HERE << "NO candidates left"); |
62c7f90e AR |
121 | noteAnswer(1); |
122 | } | |
123 | ||
124 | void | |
125 | Adaptation::AccessCheck::AccessCheckCallbackWrapper(int answer, void *data) | |
126 | { | |
192378eb | 127 | debugs(93, 8, HERE << "callback answer=" << answer); |
62c7f90e | 128 | AccessCheck *ac = (AccessCheck*)data; |
f9cf4ba9 AJ |
129 | |
130 | /** \todo AYJ 2008-06-12: If answer == ACCESS_REQ_PROXY_AUTH | |
131 | * we should be kicking off an authentication before continuing | |
132 | * with this request. see bug 2400 for details. | |
133 | */ | |
83181157 | 134 | ac->noteAnswer(answer==ACCESS_ALLOWED); |
62c7f90e AR |
135 | } |
136 | ||
137 | void | |
138 | Adaptation::AccessCheck::noteAnswer(int answer) | |
139 | { | |
140 | debugs(93, 5, HERE << "AccessCheck::noteAnswer " << answer); | |
141 | if (candidates.size()) | |
1c906324 | 142 | debugs(93, 5, HERE << "was checking rule" << topCandidate()); |
62c7f90e AR |
143 | |
144 | if (!answer) { | |
1c906324 | 145 | candidates.shift(); // the rule did not match |
62c7f90e AR |
146 | checkCandidates(); |
147 | return; | |
148 | } | |
149 | ||
150 | /* | |
151 | * We use an event here to break deep function call sequences | |
152 | */ | |
153 | // XXX: use AsyncCall for callback and remove | |
154 | CallJobHere(93, 5, this, Adaptation::AccessCheck::do_callback); | |
155 | } | |
156 | ||
157 | void | |
158 | Adaptation::AccessCheck::do_callback() | |
159 | { | |
192378eb | 160 | debugs(93, 3, HERE); |
62c7f90e AR |
161 | |
162 | if (candidates.size()) | |
163 | debugs(93, 3, HERE << "was checking rule" << topCandidate()); | |
164 | ||
165 | void *validated_cbdata; | |
166 | if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) { | |
167 | debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping"); | |
168 | return; | |
169 | } | |
170 | ||
171 | ServicePointer service = NULL; | |
172 | if (candidates.size()) { | |
173 | if (AccessRule *r = FindRule(topCandidate())) { | |
174 | service = findBestService(*r, true); | |
175 | if (service != NULL) | |
176 | debugs(93,3,HERE << "do_callback: with service " << service->cfg().uri); | |
177 | else | |
178 | debugs(93,3,HERE << "do_callback: no service for rule" << r->id); | |
179 | } else { | |
180 | debugs(93,3,HERE << "do_callback: no rule" << topCandidate()); | |
181 | } | |
182 | candidates.shift(); // done with topCandidate() | |
26ac0430 | 183 | } else { |
62c7f90e | 184 | debugs(93,3,HERE << "do_callback: no candidate rules"); |
26ac0430 | 185 | } |
62c7f90e AR |
186 | |
187 | callback(service, validated_cbdata); | |
188 | done = TRUE; | |
189 | } | |
190 | ||
191 | Adaptation::ServicePointer | |
26ac0430 AJ |
192 | Adaptation::AccessCheck::findBestService(AccessRule &r, bool preferUp) |
193 | { | |
62c7f90e AR |
194 | |
195 | const char *what = preferUp ? "up " : ""; | |
26ac0430 AJ |
196 | debugs(93,7,HERE << "looking for the first matching " << |
197 | what << "service in group " << r.groupId); | |
62c7f90e AR |
198 | |
199 | ServicePointer secondBest; | |
200 | ||
201 | ServiceGroup *g = FindGroup(r.groupId); | |
202 | ||
203 | if (!g) { | |
204 | debugs(93,5,HERE << "lost " << r.groupId << " group in rule" << r.id); | |
205 | return ServicePointer(); | |
26ac0430 | 206 | } |
62c7f90e AR |
207 | |
208 | ServiceGroup::Loop loop(g->initialServices()); | |
209 | typedef ServiceGroup::iterator SGI; | |
210 | for (SGI i = loop.begin; i != loop.end; ++i) { | |
211 | ||
212 | ServicePointer service = FindService(*i); | |
213 | ||
214 | if (!service) | |
215 | continue; | |
216 | ||
217 | if (method != service->cfg().method) | |
218 | continue; | |
219 | ||
220 | if (point != service->cfg().point) | |
221 | continue; | |
222 | ||
223 | // sending a message to a broken service is likely to cause errors | |
224 | if (service->cfg().bypass && service->broken()) | |
225 | continue; | |
226 | ||
227 | if (service->up()) { | |
228 | // sending a message to a service that does not want it is useless | |
229 | // note that we cannot check wantsUrl for service that is not "up" | |
230 | // note that even essential services are skipped on unwanted URLs! | |
231 | if (!service->wantsUrl(req->urlpath)) | |
232 | continue; | |
233 | } else { | |
234 | if (!secondBest) | |
235 | secondBest = service; | |
236 | if (preferUp) { | |
237 | // the caller asked for an "up" service and we can bypass this one | |
238 | if (service->cfg().bypass) | |
239 | continue; | |
240 | debugs(93,5,HERE << "cannot skip an essential down service"); | |
241 | what = "down-but-essential "; | |
242 | } | |
243 | } | |
244 | ||
245 | debugs(93,5,HERE << "found first matching " << | |
26ac0430 AJ |
246 | what << "service for " << r.groupId << " group in rule" << r.id << |
247 | ": " << service->cfg().key); | |
62c7f90e AR |
248 | |
249 | return service; | |
250 | } | |
251 | ||
252 | if (secondBest != NULL) { | |
253 | what = "down "; | |
254 | debugs(93,5,HERE << "found first matching " << | |
26ac0430 AJ |
255 | what << "service for " << r.groupId << " group in rule" << r.id << |
256 | ": " << secondBest->cfg().key); | |
62c7f90e AR |
257 | return secondBest; |
258 | } | |
259 | ||
26ac0430 AJ |
260 | debugs(93,5,HERE << "found no matching " << |
261 | what << "services for " << r.groupId << " group in rule" << r.id); | |
62c7f90e AR |
262 | return ServicePointer(); |
263 | } |