]> 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 "AccessLogEntry.h"
3 #include "acl/FilledChecklist.h"
4 #include "adaptation/AccessCheck.h"
5 #include "adaptation/AccessRule.h"
6 #include "adaptation/Config.h"
7 #include "adaptation/Initiator.h"
8 #include "adaptation/Service.h"
9 #include "adaptation/ServiceGroups.h"
10 #include "base/AsyncJobCalls.h"
11 #include "base/TextException.h"
12 #include "ConfigParser.h"
13 #include "globals.h"
14 #include "HttpReply.h"
15 #include "HttpRequest.h"
16
17 /** \cond AUTODOCS_IGNORE */
18 cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN;
19 /** \endcond */
20
21 bool
22 Adaptation::AccessCheck::Start(Method method, VectPoint vp,
23 HttpRequest *req, HttpReply *rep,
24 AccessLogEntry::Pointer &al, Adaptation::Initiator *initiator)
25 {
26
27 if (Config::Enabled) {
28 // the new check will call the callback and delete self, eventually
29 AsyncJob::Start(new AccessCheck( // we do not store so not a CbcPointer
30 ServiceFilter(method, vp, req, rep, al), initiator));
31 return true;
32 }
33
34 debugs(83, 3, HERE << "adaptation off, skipping");
35 return false;
36 }
37
38 Adaptation::AccessCheck::AccessCheck(const ServiceFilter &aFilter,
39 Adaptation::Initiator *initiator):
40 AsyncJob("AccessCheck"), filter(aFilter),
41 theInitiator(initiator),
42 acl_checklist(NULL)
43 {
44 #if ICAP_CLIENT
45 Adaptation::Icap::History::Pointer h = filter.request->icapHistory();
46 if (h != NULL)
47 h->start("ACL");
48 #endif
49
50 debugs(93, 5, HERE << "AccessCheck constructed for " <<
51 methodStr(filter.method) << " " << vectPointStr(filter.point));
52 }
53
54 Adaptation::AccessCheck::~AccessCheck()
55 {
56 #if ICAP_CLIENT
57 Adaptation::Icap::History::Pointer h = filter.request->icapHistory();
58 if (h != NULL)
59 h->stop("ACL");
60 #endif
61 }
62
63 void
64 Adaptation::AccessCheck::start()
65 {
66 AsyncJob::start();
67
68 if (!usedDynamicRules())
69 check();
70 }
71
72 /// returns true if previous services configured dynamic chaining "rules"
73 bool
74 Adaptation::AccessCheck::usedDynamicRules()
75 {
76 Adaptation::History::Pointer ah = filter.request->adaptHistory();
77 if (!ah)
78 return false; // dynamic rules not enabled or not triggered
79
80 DynamicGroupCfg services;
81 if (!ah->extractFutureServices(services)) { // clears history
82 debugs(85,9, HERE << "no service-proposed rules stored");
83 return false; // earlier service did not plan for the future
84 }
85
86 debugs(85,3, HERE << "using stored service-proposed rules: " << services);
87
88 ServiceGroupPointer g = new DynamicServiceChain(services, filter);
89 callBack(g);
90 Must(done());
91 return true;
92 }
93
94 /// Walk the access rules list to find rules with applicable service groups
95 void
96 Adaptation::AccessCheck::check()
97 {
98 debugs(93, 4, HERE << "start checking");
99
100 typedef AccessRules::iterator ARI;
101 for (ARI i = AllRules().begin(); i != AllRules().end(); ++i) {
102 AccessRule *r = *i;
103 if (isCandidate(*r)) {
104 debugs(93, 5, HERE << "check: rule '" << r->id << "' is a candidate");
105 candidates.push_back(r->id);
106 }
107 }
108
109 checkCandidates();
110 }
111
112 // XXX: Here and everywhere we call FindRule(topCandidate()):
113 // Once we identified the candidate, we should not just ignore it
114 // if reconfigure changes rules. We should either lock the rule to
115 // prevent reconfigure from stealing it or restart the check with
116 // new rules. Throwing an exception may also be appropriate.
117 void
118 Adaptation::AccessCheck::checkCandidates()
119 {
120 debugs(93, 4, HERE << "has " << candidates.size() << " rules");
121
122 while (!candidates.empty()) {
123 if (AccessRule *r = FindRule(topCandidate())) {
124 /* BUG 2526: what to do when r->acl is empty?? */
125 // XXX: we do not have access to conn->rfc931 here.
126 acl_checklist = new ACLFilledChecklist(r->acl, filter.request, dash_str);
127 if ((acl_checklist->reply = filter.reply))
128 HTTPMSGLOCK(acl_checklist->reply);
129 acl_checklist->al = filter.al;
130 acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this);
131 return;
132 }
133
134 candidates.shift(); // the rule apparently went away (reconfigure)
135 }
136
137 debugs(93, 4, HERE << "NO candidates left");
138 callBack(NULL);
139 Must(done());
140 }
141
142 void
143 Adaptation::AccessCheck::AccessCheckCallbackWrapper(allow_t answer, void *data)
144 {
145 debugs(93, 8, HERE << "callback answer=" << answer);
146 AccessCheck *ac = (AccessCheck*)data;
147
148 /** \todo AYJ 2008-06-12: If answer == ACCESS_AUTH_REQUIRED
149 * we should be kicking off an authentication before continuing
150 * with this request. see bug 2400 for details.
151 */
152
153 // convert to async call to get async call protections and features
154 typedef UnaryMemFunT<AccessCheck, allow_t> MyDialer;
155 AsyncCall::Pointer call =
156 asyncCall(93,7, "Adaptation::AccessCheck::noteAnswer",
157 MyDialer(ac, &Adaptation::AccessCheck::noteAnswer, answer));
158 ScheduleCallHere(call);
159
160 }
161
162 /// process the results of the ACL check
163 void
164 Adaptation::AccessCheck::noteAnswer(allow_t answer)
165 {
166 Must(!candidates.empty()); // the candidate we were checking must be there
167 debugs(93,5, HERE << topCandidate() << " answer=" << answer);
168
169 if (answer == ACCESS_ALLOWED) { // the rule matched
170 ServiceGroupPointer g = topGroup();
171 if (g != NULL) { // the corresponding group found
172 callBack(g);
173 Must(done());
174 return;
175 }
176 }
177
178 // no match or the group disappeared during reconfiguration
179 candidates.shift();
180 checkCandidates();
181 }
182
183 /// call back with a possibly nil group; the job ends here because all failures
184 /// at this point are fatal to the access check process
185 void
186 Adaptation::AccessCheck::callBack(const ServiceGroupPointer &g)
187 {
188 debugs(93,3, HERE << g);
189 CallJobHere1(93, 5, theInitiator, Adaptation::Initiator,
190 noteAdaptationAclCheckDone, g);
191 mustStop("done"); // called back or will never be able to call back
192 }
193
194 Adaptation::ServiceGroupPointer
195 Adaptation::AccessCheck::topGroup() const
196 {
197 ServiceGroupPointer g;
198 if (candidates.size()) {
199 if (AccessRule *r = FindRule(topCandidate())) {
200 g = FindGroup(r->groupId);
201 debugs(93,5, HERE << "top group for " << r->id << " is " << g);
202 } else {
203 debugs(93,5, HERE << "no rule for " << topCandidate());
204 }
205 } else {
206 debugs(93,5, HERE << "no candidates"); // should not happen
207 }
208
209 return g;
210 }
211
212 /** Returns true iff the rule's service group will be used after ACL matches.
213 Used to detect rules worth ACl-checking. */
214 bool
215 Adaptation::AccessCheck::isCandidate(AccessRule &r)
216 {
217 debugs(93,7,HERE << "checking candidacy of " << r.id << ", group " <<
218 r.groupId);
219
220 ServiceGroupPointer g = FindGroup(r.groupId);
221
222 if (!g) {
223 debugs(93,7,HERE << "lost " << r.groupId << " group in rule" << r.id);
224 return false;
225 }
226
227 const bool wants = g->wants(filter);
228 debugs(93,7,HERE << r.groupId << (wants ? " wants" : " ignores"));
229 return wants;
230 }