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