]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/adaptation/AccessCheck.cc
Maintenance: Remove FIXME and \todo labels (#647)
[thirdparty/squid.git] / src / adaptation / AccessCheck.cc
index 96bc2d9f4758cca3e2e6ce9c20d76e34ed0d467b..896f07560dee7e9f4a39628078cee61aec0c2e3a 100644 (file)
@@ -1,30 +1,41 @@
-#include "squid.h"
-#include "structs.h"
+/*
+ * Copyright (C) 1996-2020 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 "HttpRequest.h"
-#include "HttpReply.h"
+#include "squid.h"
+#include "AccessLogEntry.h"
 #include "acl/FilledChecklist.h"
-#include "adaptation/Service.h"
-#include "adaptation/ServiceGroups.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 */
+/** \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;
     }
 
@@ -32,42 +43,63 @@ Adaptation::AccessCheck::Start(Method method, VectPoint vp,
     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()
 {
@@ -75,18 +107,10 @@ 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);
         }
     }
 
@@ -107,157 +131,110 @@ Adaptation::AccessCheck::checkCandidates()
         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 = new ACLFilledChecklist(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->syncAle(filter.request, nullptr);
             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(Acl::Answer 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, Acl::Answer> 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(Acl::Answer 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.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;
 }
+