]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/adaptation/ecap/XactionRep.cc
transaction_initiator ACL for detecting various unusual transactions
[thirdparty/squid.git] / src / adaptation / ecap / XactionRep.cc
index 17e686136955945c9130eabdafe265e6d74df764..660eea5c405d8bf294881cca22c2183a869f7d4e 100644 (file)
@@ -1,34 +1,62 @@
 /*
- * DEBUG: section 93    eCAP Interface
+ * 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.
  */
+
+/* DEBUG: section 93    eCAP Interface */
+
 #include "squid.h"
 #include <libecap/common/area.h>
 #include <libecap/common/delay.h>
 #include <libecap/common/named_values.h>
 #include <libecap/common/names.h>
 #include <libecap/adapter/xaction.h>
-#include "HttpRequest.h"
-#include "HttpReply.h"
-#include "SquidTime.h"
-#include "adaptation/ecap/XactionRep.h"
+#include "adaptation/Answer.h"
 #include "adaptation/ecap/Config.h"
+#include "adaptation/ecap/XactionRep.h"
 #include "adaptation/Initiator.h"
+#include "base/AsyncJobCalls.h"
 #include "base/TextException.h"
+#include "format/Format.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+#include "MasterXaction.h"
+#include "SquidTime.h"
 
 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Ecap::XactionRep, XactionRep);
 
+/// a libecap Visitor for converting adapter transaction options to HttpHeader
+class OptionsExtractor: public libecap::NamedValueVisitor
+{
+public:
+    typedef libecap::Name Name;
+    typedef libecap::Area Area;
+
+    OptionsExtractor(HttpHeader &aMeta): meta(aMeta) {}
+
+    // libecap::NamedValueVisitor API
+    virtual void visit(const Name &name, const Area &value) {
+        meta.putExt(name.image().c_str(), value.toString().c_str());
+    }
+
+    HttpHeader &meta; ///< where to put extracted options
+};
 
 Adaptation::Ecap::XactionRep::XactionRep(
-    HttpMsg *virginHeader, HttpRequest *virginCause,
+    Http::Message *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp,
     const Adaptation::ServicePointer &aService):
-        AsyncJob("Adaptation::Ecap::XactionRep"),
-        Adaptation::Initiate("Adaptation::Ecap::XactionRep"),
-        theService(aService),
-        theVirginRep(virginHeader), theCauseRep(NULL),
-        makingVb(opUndecided), proxyingAb(opUndecided),
-        adaptHistoryId(-1),
-        vbProductionFinished(false),
-        abProductionFinished(false), abProductionAtEnd(false)
+    AsyncJob("Adaptation::Ecap::XactionRep"),
+    Adaptation::Initiate("Adaptation::Ecap::XactionRep"),
+    theService(aService),
+    theVirginRep(virginHeader), theCauseRep(NULL),
+    makingVb(opUndecided), proxyingAb(opUndecided),
+    adaptHistoryId(-1),
+    vbProductionFinished(false),
+    abProductionFinished(false), abProductionAtEnd(false),
+    al(alp)
 {
     if (virginCause)
         theCauseRep = new MessageRep(virginCause);
@@ -45,7 +73,7 @@ void
 Adaptation::Ecap::XactionRep::master(const AdapterXaction &x)
 {
     Must(!theMaster);
-    Must(x != NULL);
+    Must(x);
     theMaster = x;
 }
 
@@ -63,20 +91,32 @@ Adaptation::Ecap::XactionRep::option(const libecap::Name &name) const
         return clientIpValue();
     if (name == libecap::metaUserName)
         return usernameValue();
-    // TODO: metaServerIp, metaAuthenticatedUser, metaAuthenticatedGroups, and 
-    // Adaptation::Config::masterx_shared_name
-    return libecap::Area();
+    if (Adaptation::Config::masterx_shared_name && name == Adaptation::Config::masterx_shared_name)
+        return masterxSharedValue(name);
+
+    // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
+
+    // If the name is unknown, metaValue returns an emtpy area
+    return metaValue(name);
 }
 
 void
 Adaptation::Ecap::XactionRep::visitEachOption(libecap::NamedValueVisitor &visitor) const
 {
     if (const libecap::Area value = clientIpValue())
-       visitor.visit(libecap::metaClientIp, value);
+        visitor.visit(libecap::metaClientIp, value);
     if (const libecap::Area value = usernameValue())
-       visitor.visit(libecap::metaUserName, value);
-    // TODO: metaServerIp, metaAuthenticatedUser, metaAuthenticatedGroups, and 
-    // Adaptation::Config::masterx_shared_name
+        visitor.visit(libecap::metaUserName, value);
+
+    if (Adaptation::Config::masterx_shared_name) {
+        const libecap::Name name(Adaptation::Config::masterx_shared_name);
+        if (const libecap::Area value = masterxSharedValue(name))
+            visitor.visit(name, value);
+    }
+
+    visitEachMetaHeader(visitor);
+
+    // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
 }
 
 const libecap::Area
@@ -92,31 +132,95 @@ Adaptation::Ecap::XactionRep::clientIpValue() const
 #if FOLLOW_X_FORWARDED_FOR
         if (TheConfig.use_indirect_client) {
             client_addr = request->indirect_client_addr;
-               } else
+        } else
 #endif
             client_addr = request->client_addr;
-        if (!client_addr.IsAnyAddr() && !client_addr.IsNoAddr()) {
+        if (!client_addr.isAnyAddr() && !client_addr.isNoAddr()) {
             char ntoabuf[MAX_IPSTRLEN] = "";
-            client_addr.NtoA(ntoabuf,MAX_IPSTRLEN);
+            client_addr.toStr(ntoabuf,MAX_IPSTRLEN);
             return libecap::Area::FromTempBuffer(ntoabuf, strlen(ntoabuf));
         }
-       }
+    }
     return libecap::Area();
 }
 
 const libecap::Area
 Adaptation::Ecap::XactionRep::usernameValue() const
 {
+#if USE_AUTH
     const HttpRequest *request = dynamic_cast<const HttpRequest*>(theCauseRep ?
                                  theCauseRep->raw().header : theVirginRep.raw().header);
     Must(request);
     if (request->auth_user_request != NULL) {
         if (char const *name = request->auth_user_request->username())
             return libecap::Area::FromTempBuffer(name, strlen(name));
-       }
+        else if (request->extacl_user.size() > 0)
+            return libecap::Area::FromTempBuffer(request->extacl_user.rawBuf(),
+                                                 request->extacl_user.size());
+    }
+#endif
+    return libecap::Area();
+}
+
+const libecap::Area
+Adaptation::Ecap::XactionRep::masterxSharedValue(const libecap::Name &name) const
+{
+    const HttpRequest *request = dynamic_cast<const HttpRequest*>(theCauseRep ?
+                                 theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+    if (name.known()) { // must check to avoid empty names matching unset cfg
+        Adaptation::History::Pointer ah = request->adaptHistory(false);
+        if (ah != NULL) {
+            String name, value;
+            if (ah->getXxRecord(name, value))
+                return libecap::Area::FromTempBuffer(value.rawBuf(), value.size());
+        }
+    }
+    return libecap::Area();
+}
+
+const libecap::Area
+Adaptation::Ecap::XactionRep::metaValue(const libecap::Name &name) const
+{
+    HttpRequest *request = dynamic_cast<HttpRequest*>(theCauseRep ?
+                           theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+    HttpReply *reply = dynamic_cast<HttpReply*>(theVirginRep.raw().header);
+
+    if (name.known()) { // must check to avoid empty names matching unset cfg
+        typedef Notes::iterator ACAMLI;
+        for (auto h: Adaptation::Config::metaHeaders) {
+            if (name == h->key().toStdString()) {
+                SBuf matched;
+                if (h->match(request, reply, al, matched))
+                    return libecap::Area::FromTempString(matched.toStdString());
+                else
+                    return libecap::Area();
+            }
+        }
+    }
+
     return libecap::Area();
 }
 
+void
+Adaptation::Ecap::XactionRep::visitEachMetaHeader(libecap::NamedValueVisitor &visitor) const
+{
+    HttpRequest *request = dynamic_cast<HttpRequest*>(theCauseRep ?
+                           theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+    HttpReply *reply = dynamic_cast<HttpReply*>(theVirginRep.raw().header);
+
+    for (auto h: Adaptation::Config::metaHeaders) {
+        SBuf matched;
+        if (h->match(request, reply, al, matched)) {
+            const libecap::Name name(h->key().toStdString());
+            const libecap::Area value = libecap::Area::FromTempString(matched.toStdString());
+            visitor.visit(name, value);
+        }
+    }
+}
+
 void
 Adaptation::Ecap::XactionRep::start()
 {
@@ -125,13 +229,25 @@ Adaptation::Ecap::XactionRep::start()
     if (!theVirginRep.raw().body_pipe)
         makingVb = opNever; // there is nothing to deliver
 
-    const HttpRequest *request = dynamic_cast<const HttpRequest*> (theCauseRep ?
-                                 theCauseRep->raw().header : theVirginRep.raw().header);
+    HttpRequest *request = dynamic_cast<HttpRequest*> (theCauseRep ?
+                           theCauseRep->raw().header : theVirginRep.raw().header);
     Must(request);
+
+    HttpReply *reply = dynamic_cast<HttpReply*>(theVirginRep.raw().header);
+
     Adaptation::History::Pointer ah = request->adaptLogHistory();
     if (ah != NULL) {
         // retrying=false because ecap never retries transactions
         adaptHistoryId = ah->recordXactStart(service().cfg().key, current_time, false);
+        SBuf matched;
+        for (auto h: Adaptation::Config::metaHeaders) {
+            if (h->match(request, reply, al, matched)) {
+                if (ah->metaHeaders == NULL)
+                    ah->metaHeaders = new NotePairs();
+                if (!ah->metaHeaders->hasPair(h->key(), matched))
+                    ah->metaHeaders->add(h->key(), matched);
+            }
+        }
     }
 
     theMaster->start();
@@ -143,7 +259,7 @@ Adaptation::Ecap::XactionRep::swanSong()
     // clear body_pipes, if any
     // this code does not maintain proxying* and canAccessVb states; should it?
 
-    if (theAnswerRep != NULL) {
+    if (theAnswerRep) {
         BodyPipe::Pointer body_pipe = answer().body_pipe;
         if (body_pipe != NULL) {
             Must(body_pipe->stillProducing(this));
@@ -167,6 +283,25 @@ Adaptation::Ecap::XactionRep::swanSong()
     Adaptation::Initiate::swanSong();
 }
 
+void
+Adaptation::Ecap::XactionRep::resume()
+{
+    // go async to gain exception protection and done()-based job destruction
+    typedef NullaryMemFunT<Adaptation::Ecap::XactionRep> Dialer;
+    AsyncCall::Pointer call = asyncCall(93, 5, "Adaptation::Ecap::XactionRep::doResume",
+                                        Dialer(this, &Adaptation::Ecap::XactionRep::doResume));
+    ScheduleCallHere(call);
+}
+
+/// the guts of libecap::host::Xaction::resume() API implementation
+/// which just goes async in Adaptation::Ecap::XactionRep::resume().
+void
+Adaptation::Ecap::XactionRep::doResume()
+{
+    Must(theMaster);
+    theMaster->resume();
+}
+
 libecap::Message &
 Adaptation::Ecap::XactionRep::virgin()
 {
@@ -183,7 +318,7 @@ Adaptation::Ecap::XactionRep::cause()
 libecap::Message &
 Adaptation::Ecap::XactionRep::adapted()
 {
-    Must(theAnswerRep != NULL);
+    Must(theAnswerRep);
     return *theAnswerRep;
 }
 
@@ -267,10 +402,11 @@ Adaptation::Ecap::XactionRep::useVirgin()
 
     preserveVb("useVirgin");
 
-    HttpMsg *clone = theVirginRep.raw().header->clone();
+    Http::Message *clone = theVirginRep.raw().header->clone();
     // check that clone() copies the pipe so that we do not have to
     Must(!theVirginRep.raw().header->body_pipe == !clone->body_pipe);
 
+    updateHistory(clone);
     sendAnswer(Answer::Forward(clone));
     Must(done());
 }
@@ -283,9 +419,11 @@ Adaptation::Ecap::XactionRep::useAdapted(const libecap::shared_ptr<libecap::Mess
     theAnswerRep = m;
     Must(proxyingAb == opUndecided);
 
-    HttpMsg *msg = answer().header;
+    Http::Message *msg = answer().header;
+    updateSources(msg);
     if (!theAnswerRep->body()) { // final, bodyless answer
         proxyingAb = opNever;
+        updateHistory(msg);
         sendAnswer(Answer::Forward(msg));
     } else { // got answer headers but need to handle body
         proxyingAb = opOn;
@@ -295,6 +433,7 @@ Adaptation::Ecap::XactionRep::useAdapted(const libecap::shared_ptr<libecap::Mess
         rep->tieBody(this); // sets us as a producer
         Must(msg->body_pipe != NULL); // check tieBody
 
+        updateHistory(msg);
         sendAnswer(Answer::Forward(msg));
 
         debugs(93,4, HERE << "adapter will produce body" << status());
@@ -311,10 +450,62 @@ Adaptation::Ecap::XactionRep::blockVirgin()
 
     sinkVb("blockVirgin");
 
+    updateHistory(NULL);
     sendAnswer(Answer::Block(service().cfg().key));
     Must(done());
 }
 
+/// Called just before sendAnswer() to record adapter meta-information
+/// which may affect answer processing and may be needed for logging.
+void
+Adaptation::Ecap::XactionRep::updateHistory(Http::Message *adapted)
+{
+    if (!theMaster) // all updates rely on being able to query the adapter
+        return;
+
+    const HttpRequest *request = dynamic_cast<const HttpRequest*>(theCauseRep ?
+                                 theCauseRep->raw().header : theVirginRep.raw().header);
+    Must(request);
+
+    // TODO: move common ICAP/eCAP logic to Adaptation::Xaction or similar
+    // TODO: optimize Area-to-String conversion
+
+    // update the cross-transactional database if needed
+    if (const char *xxNameStr = Adaptation::Config::masterx_shared_name) {
+        Adaptation::History::Pointer ah = request->adaptHistory(true);
+        if (ah != NULL) {
+            libecap::Name xxName(xxNameStr); // TODO: optimize?
+            if (const libecap::Area val = theMaster->option(xxName))
+                ah->updateXxRecord(xxNameStr, val.toString().c_str());
+        }
+    }
+
+    // update the adaptation plan if needed
+    if (service().cfg().routing) {
+        String services;
+        if (const libecap::Area services = theMaster->option(libecap::metaNextServices)) {
+            Adaptation::History::Pointer ah = request->adaptHistory(true);
+            if (ah != NULL)
+                ah->updateNextServices(services.toString().c_str());
+        }
+    } // TODO: else warn (occasionally!) if we got libecap::metaNextServices
+
+    // Store received meta headers for adapt::<last_h logformat code use.
+    // If we already have stored headers from a previous adaptation transaction
+    // related to the same master transction, they will be replaced.
+    Adaptation::History::Pointer ah = request->adaptLogHistory();
+    if (ah != NULL) {
+        HttpHeader meta(hoReply);
+        OptionsExtractor extractor(meta);
+        theMaster->visitEachOption(extractor);
+        ah->recordMeta(&meta);
+    }
+
+    // Add just-created history to the adapted/cloned request that lacks it.
+    if (HttpRequest *adaptedReq = dynamic_cast<HttpRequest*>(adapted))
+        adaptedReq->adaptHistoryImport(*request);
+}
+
 void
 Adaptation::Ecap::XactionRep::vbDiscard()
 {
@@ -434,12 +625,6 @@ Adaptation::Ecap::XactionRep::adaptationAborted()
     mustStop("adaptationAborted");
 }
 
-bool
-Adaptation::Ecap::XactionRep::callable() const
-{
-    return !done();
-}
-
 void
 Adaptation::Ecap::XactionRep::noteMoreBodySpaceAvailable(RefCount<BodyPipe> bp)
 {
@@ -515,13 +700,12 @@ Adaptation::Ecap::XactionRep::status() const
     buf.append(" [", 2);
 
     if (makingVb)
-        buf.Printf("M%d", static_cast<int>(makingVb));
+        buf.appendf("M%d", static_cast<int>(makingVb));
 
     const BodyPipePointer &vp = theVirginRep.raw().body_pipe;
     if (!vp)
         buf.append(" !V", 3);
-    else
-    if (vp->stillConsuming(const_cast<XactionRep*>(this)))
+    else if (vp->stillConsuming(const_cast<XactionRep*>(this)))
         buf.append(" Vc", 3);
     else
         buf.append(" V?", 3);
@@ -529,8 +713,7 @@ Adaptation::Ecap::XactionRep::status() const
     if (vbProductionFinished)
         buf.append(".", 1);
 
-
-    buf.Printf(" A%d", static_cast<int>(proxyingAb));
+    buf.appendf(" A%d", static_cast<int>(proxyingAb));
 
     if (proxyingAb == opOn) {
         MessageRep *rep = dynamic_cast<MessageRep*>(theAnswerRep.get());
@@ -544,9 +727,24 @@ Adaptation::Ecap::XactionRep::status() const
             buf.append(" A?", 3);
     }
 
-    buf.Printf(" %s%u]", id.Prefix, id.value);
+    buf.appendf(" %s%u]", id.prefix(), id.value);
 
     buf.terminate();
 
     return buf.content();
 }
+
+void
+Adaptation::Ecap::XactionRep::updateSources(Http::Message *adapted)
+{
+    adapted->sources |= service().cfg().connectionEncryption ? Http::Message::srcEcaps : Http::Message::srcEcap;
+
+    // Update masterXaction object for adapted HTTP requests.
+    if (HttpRequest *adaptedReq = dynamic_cast<HttpRequest*>(adapted)) {
+        HttpRequest *request = dynamic_cast<HttpRequest*> (theCauseRep ?
+                               theCauseRep->raw().header : theVirginRep.raw().header);
+        Must(request);
+        adaptedReq->masterXaction = request->masterXaction;
+    }
+}
+