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