]> 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 "ACL.h"
6 #include "HttpRequest.h"
7 #include "HttpReply.h"
8 #include "ACLChecklist.h"
9 #include "adaptation/Service.h"
10 #include "adaptation/ServiceGroups.h"
11 #include "adaptation/AccessRule.h"
12 #include "adaptation/Config.h"
13 #include "adaptation/AccessCheck.h"
14
15
16 cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN;
17
18 bool
19 Adaptation::AccessCheck::Start(Method method, VectPoint vp,
20 HttpRequest *req, HttpReply *rep, AccessCheckCallback *cb, void *cbdata) {
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, "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, "Adaptation::AccessCheck::check");
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, "Adaptation::AccessCheck::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, "Adaptation::AccessCheck has " << candidates.size() << " rules");
103
104 while (!candidates.empty()) {
105 if (AccessRule *r = FindRule(topCandidate())) {
106 // XXX: we do not have access to conn->rfc931 here.
107 acl_checklist = aclChecklistCreate(r->acl, req, dash_str);
108 acl_checklist->reply = rep ? HTTPMSGLOCK(rep) : NULL;
109 acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this);
110 return;
111 }
112
113 candidates.shift(); // the rule apparently went away (reconfigure)
114 }
115
116 // when there are no canidates, fake answer 1
117 debugs(93, 4, "Adaptation::AccessCheck::check: NO candidates left");
118 noteAnswer(1);
119 }
120
121 void
122 Adaptation::AccessCheck::AccessCheckCallbackWrapper(int answer, void *data)
123 {
124 debugs(93, 8, "AccessCheckCallbackWrapper: answer=" << answer);
125 AccessCheck *ac = (AccessCheck*)data;
126
127 /** \todo AYJ 2008-06-12: If answer == ACCESS_REQ_PROXY_AUTH
128 * we should be kicking off an authentication before continuing
129 * with this request. see bug 2400 for details.
130 */
131 ac->noteAnswer(answer==ACCESS_ALLOWED);
132 }
133
134 void
135 Adaptation::AccessCheck::noteAnswer(int answer)
136 {
137 debugs(93, 5, HERE << "AccessCheck::noteAnswer " << answer);
138 if (candidates.size())
139 debugs(93, 5, HERE << "was checking rule" << topCandidate());
140
141 if (!answer) {
142 candidates.shift(); // the rule did not match
143 checkCandidates();
144 return;
145 }
146
147 /*
148 * We use an event here to break deep function call sequences
149 */
150 // XXX: use AsyncCall for callback and remove
151 CallJobHere(93, 5, this, Adaptation::AccessCheck::do_callback);
152 }
153
154 void
155 Adaptation::AccessCheck::do_callback()
156 {
157 debugs(93, 3, "Adaptation::AccessCheck::do_callback");
158
159 if (candidates.size())
160 debugs(93, 3, HERE << "was checking rule" << topCandidate());
161
162 void *validated_cbdata;
163 if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) {
164 debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping");
165 return;
166 }
167
168 ServicePointer service = NULL;
169 if (candidates.size()) {
170 if (AccessRule *r = FindRule(topCandidate())) {
171 service = findBestService(*r, true);
172 if (service != NULL)
173 debugs(93,3,HERE << "do_callback: with service " << service->cfg().uri);
174 else
175 debugs(93,3,HERE << "do_callback: no service for rule" << r->id);
176 } else {
177 debugs(93,3,HERE << "do_callback: no rule" << topCandidate());
178 }
179 candidates.shift(); // done with topCandidate()
180 } else {
181 debugs(93,3,HERE << "do_callback: no candidate rules");
182 }
183
184 callback(service, validated_cbdata);
185 done = TRUE;
186 }
187
188 Adaptation::ServicePointer
189 Adaptation::AccessCheck::findBestService(AccessRule &r, bool preferUp) {
190
191 const char *what = preferUp ? "up " : "";
192 debugs(93,7,HERE << "looking for the first matching " <<
193 what << "service in group " << r.groupId);
194
195 ServicePointer secondBest;
196
197 ServiceGroup *g = FindGroup(r.groupId);
198
199 if (!g) {
200 debugs(93,5,HERE << "lost " << r.groupId << " group in rule" << r.id);
201 return ServicePointer();
202 }
203
204 ServiceGroup::Loop loop(g->initialServices());
205 typedef ServiceGroup::iterator SGI;
206 for (SGI i = loop.begin; i != loop.end; ++i) {
207
208 ServicePointer service = FindService(*i);
209
210 if (!service)
211 continue;
212
213 if (method != service->cfg().method)
214 continue;
215
216 if (point != service->cfg().point)
217 continue;
218
219 // sending a message to a broken service is likely to cause errors
220 if (service->cfg().bypass && service->broken())
221 continue;
222
223 if (service->up()) {
224 // sending a message to a service that does not want it is useless
225 // note that we cannot check wantsUrl for service that is not "up"
226 // note that even essential services are skipped on unwanted URLs!
227 if (!service->wantsUrl(req->urlpath))
228 continue;
229 } else {
230 if (!secondBest)
231 secondBest = service;
232 if (preferUp) {
233 // the caller asked for an "up" service and we can bypass this one
234 if (service->cfg().bypass)
235 continue;
236 debugs(93,5,HERE << "cannot skip an essential down service");
237 what = "down-but-essential ";
238 }
239 }
240
241 debugs(93,5,HERE << "found first matching " <<
242 what << "service for " << r.groupId << " group in rule" << r.id <<
243 ": " << service->cfg().key);
244
245 return service;
246 }
247
248 if (secondBest != NULL) {
249 what = "down ";
250 debugs(93,5,HERE << "found first matching " <<
251 what << "service for " << r.groupId << " group in rule" << r.id <<
252 ": " << secondBest->cfg().key);
253 return secondBest;
254 }
255
256 debugs(93,5,HERE << "found no matching " <<
257 what << "services for " << r.groupId << " group in rule" << r.id);
258 return ServicePointer();
259 }