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