-#include "squid.h"
-#include "structs.h"
+/*
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
-#include "ConfigParser.h"
-#include "ACL.h"
-#include "HttpRequest.h"
-#include "HttpReply.h"
-#include "ACLChecklist.h"
-#include "adaptation/Service.h"
-#include "adaptation/ServiceGroups.h"
+#include "squid.h"
+#include "AccessLogEntry.h"
+#include "acl/FilledChecklist.h"
+#include "adaptation/AccessCheck.h"
#include "adaptation/AccessRule.h"
#include "adaptation/Config.h"
-#include "adaptation/AccessCheck.h"
-
+#include "adaptation/Initiator.h"
+#include "adaptation/Service.h"
+#include "adaptation/ServiceGroups.h"
+#include "base/AsyncJobCalls.h"
+#include "base/TextException.h"
+#include "ConfigParser.h"
+#include "globals.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+/** \cond AUTODOCS_IGNORE */
cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN;
+/** \endcond */
bool
Adaptation::AccessCheck::Start(Method method, VectPoint vp,
- HttpRequest *req, HttpReply *rep, AccessCheckCallback *cb, void *cbdata)
+ HttpRequest *req, HttpReply *rep,
+ AccessLogEntry::Pointer &al, Adaptation::Initiator *initiator)
{
if (Config::Enabled) {
// the new check will call the callback and delete self, eventually
- AccessCheck *check = new AccessCheck(method, vp, req, rep, cb, cbdata);
- check->check();
+ AsyncJob::Start(new AccessCheck( // we do not store so not a CbcPointer
+ ServiceFilter(method, vp, req, rep, al), initiator));
return true;
}
return false;
}
-Adaptation::AccessCheck::AccessCheck(Method aMethod,
- VectPoint aPoint,
- HttpRequest *aReq,
- HttpReply *aRep,
- AccessCheckCallback *aCallback,
- void *aCallbackData): AsyncJob("AccessCheck"), done(FALSE)
+Adaptation::AccessCheck::AccessCheck(const ServiceFilter &aFilter,
+ Adaptation::Initiator *initiator):
+ AsyncJob("AccessCheck"), filter(aFilter),
+ theInitiator(initiator),
+ acl_checklist(NULL)
{
- // TODO: assign these at creation time
-
- method = aMethod;
- point = aPoint;
-
- req = HTTPMSGLOCK(aReq);
- rep = aRep ? HTTPMSGLOCK(aRep) : NULL;
-
- callback = aCallback;
+#if ICAP_CLIENT
+ Adaptation::Icap::History::Pointer h = filter.request->icapHistory();
+ if (h != NULL)
+ h->start("ACL");
+#endif
+
+ debugs(93, 5, HERE << "AccessCheck constructed for " <<
+ methodStr(filter.method) << " " << vectPointStr(filter.point));
+}
- callback_data = cbdataReference(aCallbackData);
+Adaptation::AccessCheck::~AccessCheck()
+{
+#if ICAP_CLIENT
+ Adaptation::Icap::History::Pointer h = filter.request->icapHistory();
+ if (h != NULL)
+ h->stop("ACL");
+#endif
+}
- acl_checklist = NULL;
+void
+Adaptation::AccessCheck::start()
+{
+ AsyncJob::start();
- debugs(93, 5, HERE << "AccessCheck constructed for " << methodStr(method) << " " << vectPointStr(point));
+ if (!usedDynamicRules())
+ check();
}
-Adaptation::AccessCheck::~AccessCheck()
+/// returns true if previous services configured dynamic chaining "rules"
+bool
+Adaptation::AccessCheck::usedDynamicRules()
{
- HTTPMSGUNLOCK(req);
- HTTPMSGUNLOCK(rep);
- if (callback_data)
- cbdataReferenceDone(callback_data);
+ Adaptation::History::Pointer ah = filter.request->adaptHistory();
+ if (!ah)
+ return false; // dynamic rules not enabled or not triggered
+
+ DynamicGroupCfg services;
+ if (!ah->extractFutureServices(services)) { // clears history
+ debugs(85,9, HERE << "no service-proposed rules stored");
+ return false; // earlier service did not plan for the future
+ }
+
+ debugs(85,3, HERE << "using stored service-proposed rules: " << services);
+
+ ServiceGroupPointer g = new DynamicServiceChain(services, filter);
+ callBack(g);
+ Must(done());
+ return true;
}
-/*
- * Walk the access rules list and find all classes that have at least
- * one service with matching method and vectoring point.
- */
+/// Walk the access rules list to find rules with applicable service groups
void
Adaptation::AccessCheck::check()
{
typedef AccessRules::iterator ARI;
for (ARI i = AllRules().begin(); i != AllRules().end(); ++i) {
-
- /*
- * We only find the first matching service because we only need
- * one matching service to justify ACL-checking a class. We might
- * use other services belonging to the class if the first service
- * turns out to be unusable for some reason.
- */
AccessRule *r = *i;
- ServicePointer service = findBestService(*r, false);
- if (service != NULL) {
- debugs(93, 5, HERE << "check: rule '" << r->id << "' has candidate service '" << service->cfg().key << "'");
- candidates += r->id;
+ if (isCandidate(*r)) {
+ debugs(93, 5, HERE << "check: rule '" << r->id << "' is a candidate");
+ candidates.push_back(r->id);
}
}
if (AccessRule *r = FindRule(topCandidate())) {
/* BUG 2526: what to do when r->acl is empty?? */
// XXX: we do not have access to conn->rfc931 here.
- acl_checklist = aclChecklistCreate(r->acl, req, dash_str);
- acl_checklist->reply = rep ? HTTPMSGLOCK(rep) : NULL;
+ acl_checklist = new ACLFilledChecklist(r->acl, filter.request, dash_str);
+ if ((acl_checklist->reply = filter.reply))
+ HTTPMSGLOCK(acl_checklist->reply);
+ acl_checklist->al = filter.al;
acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this);
return;
}
- candidates.shift(); // the rule apparently went away (reconfigure)
+ candidates.erase(candidates.begin()); // the rule apparently went away (reconfigure)
}
- // when there are no canidates, fake answer 1
debugs(93, 4, HERE << "NO candidates left");
- noteAnswer(1);
+ callBack(NULL);
+ Must(done());
}
void
-Adaptation::AccessCheck::AccessCheckCallbackWrapper(int answer, void *data)
+Adaptation::AccessCheck::AccessCheckCallbackWrapper(allow_t answer, void *data)
{
debugs(93, 8, HERE << "callback answer=" << answer);
AccessCheck *ac = (AccessCheck*)data;
- /** \todo AYJ 2008-06-12: If answer == ACCESS_REQ_PROXY_AUTH
+ /** \todo AYJ 2008-06-12: If answer == ACCESS_AUTH_REQUIRED
* we should be kicking off an authentication before continuing
* with this request. see bug 2400 for details.
*/
- ac->noteAnswer(answer==ACCESS_ALLOWED);
+
+ // convert to async call to get async call protections and features
+ typedef UnaryMemFunT<AccessCheck, allow_t> MyDialer;
+ AsyncCall::Pointer call =
+ asyncCall(93,7, "Adaptation::AccessCheck::noteAnswer",
+ MyDialer(ac, &Adaptation::AccessCheck::noteAnswer, answer));
+ ScheduleCallHere(call);
+
}
+/// process the results of the ACL check
void
-Adaptation::AccessCheck::noteAnswer(int answer)
+Adaptation::AccessCheck::noteAnswer(allow_t answer)
{
- debugs(93, 5, HERE << "AccessCheck::noteAnswer " << answer);
- if (candidates.size())
- debugs(93, 5, HERE << "was checking rule" << topCandidate());
-
- if (!answer) {
- candidates.shift(); // the rule did not match
- checkCandidates();
- return;
+ Must(!candidates.empty()); // the candidate we were checking must be there
+ debugs(93,5, HERE << topCandidate() << " answer=" << answer);
+
+ if (answer == ACCESS_ALLOWED) { // the rule matched
+ ServiceGroupPointer g = topGroup();
+ if (g != NULL) { // the corresponding group found
+ callBack(g);
+ Must(done());
+ return;
+ }
}
- /*
- * We use an event here to break deep function call sequences
- */
- // XXX: use AsyncCall for callback and remove
- CallJobHere(93, 5, this, Adaptation::AccessCheck::do_callback);
+ // no match or the group disappeared during reconfiguration
+ candidates.erase(candidates.begin());
+ checkCandidates();
}
+/// call back with a possibly nil group; the job ends here because all failures
+/// at this point are fatal to the access check process
void
-Adaptation::AccessCheck::do_callback()
+Adaptation::AccessCheck::callBack(const ServiceGroupPointer &g)
{
- debugs(93, 3, HERE);
-
- if (candidates.size())
- debugs(93, 3, HERE << "was checking rule" << topCandidate());
-
- void *validated_cbdata;
- if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) {
- debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping");
- return;
- }
+ debugs(93,3, HERE << g);
+ CallJobHere1(93, 5, theInitiator, Adaptation::Initiator,
+ noteAdaptationAclCheckDone, g);
+ mustStop("done"); // called back or will never be able to call back
+}
- ServicePointer service = NULL;
+Adaptation::ServiceGroupPointer
+Adaptation::AccessCheck::topGroup() const
+{
+ ServiceGroupPointer g;
if (candidates.size()) {
if (AccessRule *r = FindRule(topCandidate())) {
- service = findBestService(*r, true);
- if (service != NULL)
- debugs(93,3,HERE << "do_callback: with service " << service->cfg().uri);
- else
- debugs(93,3,HERE << "do_callback: no service for rule" << r->id);
+ g = FindGroup(r->groupId);
+ debugs(93,5, HERE << "top group for " << r->id << " is " << g);
} else {
- debugs(93,3,HERE << "do_callback: no rule" << topCandidate());
+ debugs(93,5, HERE << "no rule for " << topCandidate());
}
- candidates.shift(); // done with topCandidate()
} else {
- debugs(93,3,HERE << "do_callback: no candidate rules");
+ debugs(93,5, HERE << "no candidates"); // should not happen
}
- callback(service, validated_cbdata);
- done = TRUE;
+ return g;
}
-Adaptation::ServicePointer
-Adaptation::AccessCheck::findBestService(AccessRule &r, bool preferUp)
+/** Returns true iff the rule's service group will be used after ACL matches.
+ Used to detect rules worth ACl-checking. */
+bool
+Adaptation::AccessCheck::isCandidate(AccessRule &r)
{
+ debugs(93,7,HERE << "checking candidacy of " << r.id << ", group " <<
+ r.groupId);
- const char *what = preferUp ? "up " : "";
- debugs(93,7,HERE << "looking for the first matching " <<
- what << "service in group " << r.groupId);
-
- ServicePointer secondBest;
-
- ServiceGroup *g = FindGroup(r.groupId);
+ ServiceGroupPointer g = FindGroup(r.groupId);
if (!g) {
- debugs(93,5,HERE << "lost " << r.groupId << " group in rule" << r.id);
- return ServicePointer();
+ debugs(93,7,HERE << "lost " << r.groupId << " group in rule" << r.id);
+ return false;
}
- ServiceGroup::Loop loop(g->initialServices());
- typedef ServiceGroup::iterator SGI;
- for (SGI i = loop.begin; i != loop.end; ++i) {
-
- ServicePointer service = FindService(*i);
-
- if (!service)
- continue;
-
- if (method != service->cfg().method)
- continue;
-
- if (point != service->cfg().point)
- continue;
-
- // sending a message to a broken service is likely to cause errors
- if (service->cfg().bypass && service->broken())
- continue;
-
- if (service->up()) {
- // sending a message to a service that does not want it is useless
- // note that we cannot check wantsUrl for service that is not "up"
- // note that even essential services are skipped on unwanted URLs!
- if (!service->wantsUrl(req->urlpath))
- continue;
- } else {
- if (!secondBest)
- secondBest = service;
- if (preferUp) {
- // the caller asked for an "up" service and we can bypass this one
- if (service->cfg().bypass)
- continue;
- debugs(93,5,HERE << "cannot skip an essential down service");
- what = "down-but-essential ";
- }
- }
-
- debugs(93,5,HERE << "found first matching " <<
- what << "service for " << r.groupId << " group in rule" << r.id <<
- ": " << service->cfg().key);
-
- return service;
- }
-
- if (secondBest != NULL) {
- what = "down ";
- debugs(93,5,HERE << "found first matching " <<
- what << "service for " << r.groupId << " group in rule" << r.id <<
- ": " << secondBest->cfg().key);
- return secondBest;
- }
-
- debugs(93,5,HERE << "found no matching " <<
- what << "services for " << r.groupId << " group in rule" << r.id);
- return ServicePointer();
+ const bool wants = g->wants(filter);
+ debugs(93,7,HERE << r.groupId << (wants ? " wants" : " ignores"));
+ return wants;
}
+