]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
adaptation_meta option
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 28 Oct 2011 19:43:45 +0000 (22:43 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 28 Oct 2011 19:43:45 +0000 (22:43 +0300)
This option allows Squid administrator to add custom ICAP request
headers or eCAP options to Squid ICAP requests or eCAP transactions.
Use it to pass custom authentication tokens and other
transaction-state related meta information to an ICAP/eCAP service.

The addition of a meta header is ACL-driven:
        adaptation_meta name value [!]aclname ...

Processing for a given header name stops after the first ACL list match.
Thus, it is impossible to add two headers with the same name. If no ACL
lists match for a given header name, no such header is added. For example:

        # do not debug transactions except for those that need debugging
        adaptation_meta X-Debug 1 needs_debugging

        # log all transactions except for those that must remain secret
        adaptation_meta X-Log 1 !keep_secret

        # mark transactions from users in the "G 1" group
        adaptation_meta X-Authenticated-Groups "G 1" authed_as_G1

The "value" parameter may be a regular squid.conf token or a "double
quoted string". Within the quoted string, use backslash (\) to escape
any character, which is currently only useful for escaping backslashes
and double quotes. For example,
    "this string has one backslash (\\) and two \"quotes\""

This is a Measurement Factory project

12 files changed:
src/ConfigParser.cc
src/ConfigParser.h
src/Makefile.am
src/acl/Gadgets.h
src/adaptation/Config.cc
src/adaptation/Config.h
src/adaptation/ecap/XactionRep.cc
src/adaptation/ecap/XactionRep.h
src/adaptation/icap/ModXact.cc
src/cache_cf.cc
src/cf.data.depend
src/cf.data.pre

index 45084b4edd64de4cf7f578f5ada2f17d2e4570dc..ec602c11ad4869584654b9b368a91b07b8561495 100644 (file)
@@ -114,3 +114,69 @@ ConfigParser::strtokFile(void)
 
     return t;
 }
+
+void
+ConfigParser::ParseQuotedString(char **var)
+{
+    String sVar;
+    ParseQuotedString(&sVar);
+    *var = xstrdup(sVar.termedBuf());
+}
+
+void
+ConfigParser::ParseQuotedString(String *var)
+{
+    // Get all of the remaining string
+    char *token = strtok(NULL, "");
+    if (token == NULL)
+        self_destruct();
+
+    if (*token != '"') {
+        token = strtok(token, w_space);
+        var->reset(token);
+        return;
+    }
+
+    char  *s = token + 1;
+    /* scan until the end of the quoted string, unescaping " and \  */
+    while (*s && *s != '"') {
+        if (*s == '\\') {
+            const char * next = s+1; // may point to 0
+            memmove(s, next, strlen(next) + 1);
+        }
+        s++;
+    }
+
+    if (*s != '"') {
+        debugs(3, DBG_CRITICAL, "ParseQuotedString: missing '\"' at the end of quoted string" );
+        self_destruct();
+    }
+    strtok(s-1, "\""); /*Reset the strtok to point after the "  */
+    *s = '\0';
+
+    var->reset(token+1);
+}
+
+const char *
+ConfigParser::QuoteString(String &var)
+{
+    static String quotedStr;
+    const char *s = var.termedBuf();
+    bool  needQuote = false;
+
+    for (const char *l = s; !needQuote &&  *l != '\0'; l++  )
+            needQuote = !isalnum(*l);
+
+    if (!needQuote)
+        return s;
+    
+    quotedStr.clean();
+    quotedStr.append('"');
+    for (; *s != '\0'; s++) {
+        if(*s == '"' || *s == '\\')
+            quotedStr.append('\\');
+        quotedStr.append(*s);
+    }
+    quotedStr.append('"');
+    return quotedStr.termedBuf();
+}
index 3d47369d1fb47d6b991432fb48c394e6f46b84d2..99fd894a073f12bce4e112b93b3c122954444cae 100644 (file)
@@ -67,6 +67,9 @@ public:
     static void ParseBool(bool *var);
     static void ParseString(char **var);
     static void ParseString(String *var);
+    static void ParseQuotedString(char **var);
+    static void ParseQuotedString(String *var);
+    static const char *QuoteString(String &var);
     static void ParseWordList(wordlist **list);
     static char * strtokFile();
 };
index bca748665e35ac6dce33f1247e02992e615a003b..4f213bbd31da629c6ec1924c641c5e26d18b2465 100644 (file)
@@ -1004,6 +1004,7 @@ check_PROGRAMS+=\
        tests/testStore \
        tests/testString \
        tests/testURL \
+       tests/testConfigParser \
        $(STORE_TESTS)
 
 ## NP: required to run the above list. check_PROGRAMS only builds the binaries...
@@ -3233,6 +3234,36 @@ tests_testURL_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
 
+tests_testConfigParser_SOURCES = \
+       ClientInfo.h \
+       mem.cc \
+       MemBuf.cc \
+       String.cc \
+       ConfigParser.cc \
+       tests/testMain.cc \
+       tests/testConfigParser.cc \
+       tests/testConfigParser.h \
+       tests/stub_cache_cf.cc \
+       tests/stub_cache_manager.cc \
+       tests/stub_debug.cc \
+       tests/stub_HelperChildConfig.cc \
+       time.cc \
+       wordlist.cc
+nodist_tests_testConfigParser_SOURCES = \
+       $(TESTSOURCES)
+tests_testConfigParser_LDADD = \
+       base/libbase.la \
+       libsquid.la \
+       ip/libip.la \
+       $(top_builddir)/lib/libmiscutil.la \
+       $(REGEXLIB) \
+       $(SQUID_CPPUNIT_LIBS) \
+       $(SSLLIB) \
+       $(COMPAT_LIB) \
+       $(XTRA_LIBS)
+tests_testConfigParser_LDFLAGS = $(LIBADD_DL)
+tests_testConfigParser_DEPENDENCIES = \
+       $(SQUID_CPPUNIT_LA)
 
 TESTS += testHeaders
 
index deef46b8b63d9345e324e87251a2ae9673faa42c..38214f546d6f7c1b148ce38b6f4b4502227992b0 100644 (file)
@@ -36,5 +36,7 @@ extern wordlist *aclDumpGeneric(const ACL *);
 extern void aclCacheMatchFlush(dlink_list * cache);
 /// \ingroup ACLAPI
 extern void dump_acl_access(StoreEntry * entry, const char *name, acl_access * head);
+/// \ingroup ACLAPI
+extern void dump_acl_list(StoreEntry * entry, ACLList * head);
 
 #endif /* SQUID_ACL_GADGETS_H */
index 8bc68130cb697214a5e322645cd5b5bb68561926..4810fd37f285f5b427ba864a492f71397abf205b 100644 (file)
 #include "acl/Gadgets.h"
 #include "Store.h"
 #include "Array.h"    // really Vector
+#include "acl/FilledChecklist.h"
 #include "adaptation/Config.h"
 #include "adaptation/Service.h"
 #include "adaptation/AccessRule.h"
 #include "adaptation/ServiceGroups.h"
 #include "adaptation/History.h"
+#include "HttpRequest.h"
 
 
 bool Adaptation::Config::Enabled = false;
@@ -50,6 +52,54 @@ int Adaptation::Config::service_iteration_limit = 16;
 int Adaptation::Config::send_client_ip = false;
 int Adaptation::Config::send_username = false;
 int Adaptation::Config::use_indirect_client = true;
+Adaptation::Config::MetaHeaders Adaptation::Config::metaHeaders;
+
+
+Adaptation::Config::MetaHeader::Value::~Value()
+{
+    aclDestroyAclList(&aclList);
+}
+
+Adaptation::Config::MetaHeader::Value::Pointer 
+Adaptation::Config::MetaHeader::addValue(const String &value)
+{
+    Value::Pointer v = new Value(value);
+    values.push_back(v);
+    return v;
+}
+
+const char *
+Adaptation::Config::MetaHeader::match(HttpRequest *request, HttpReply *reply)
+{
+
+    typedef Values::iterator VLI;
+    ACLFilledChecklist ch(NULL, request, NULL);
+    if (reply)
+        ch.reply = HTTPMSGLOCK(reply);
+
+    for (VLI i = values.begin(); i != values.end(); ++i ) {
+        const int ret= ch.fastCheck((*i)->aclList); 
+        debugs(93, 5, HERE << "Check for header name: " << name << ": " << (*i)->value
+               <<", HttpRequest: " << request << " HttpReply: " << reply << " matched: " << ret);
+        if (ret == ACCESS_ALLOWED)
+            return (*i)->value.termedBuf();
+    }
+    return NULL;
+}
+
+Adaptation::Config::MetaHeader::Pointer 
+Adaptation::Config::addMetaHeader(const String &headerName)
+{
+    typedef MetaHeaders::iterator AMLI;
+    for(AMLI i = metaHeaders.begin(); i != metaHeaders.end(); ++i) {
+        if ((*i)->name == headerName)
+            return (*i);
+    }
+
+    MetaHeader::Pointer meta = new MetaHeader(headerName);
+    metaHeaders.push_back(meta);
+    return meta;
+}
 
 
 Adaptation::ServiceConfig*
@@ -135,6 +185,8 @@ Adaptation::Config::freeService()
     DetachServices();
 
     serviceConfigs.clean();
+
+    FreeMetaHeader();
 }
 
 void
@@ -208,6 +260,64 @@ Adaptation::Config::Finalize(bool enabled)
     FinalizeEach(AllRules(), "message adaptation access rules");
 }
 
+void
+Adaptation::Config::ParseMetaHeader(ConfigParser &parser)
+{
+    String name, value;
+    const char *warnFor[] = {
+        "Methods",
+        "Service",
+        "ISTag",
+        "Encapsulated",
+        "Opt-body-type",
+        "Max-Connections",
+        "Options-TTL",
+        "Date",
+        "Service-ID",
+        "Allow",
+        "Preview",
+        "Transfer-Preview",
+        "Transfer-Ignore",
+        "Transfer-Complete",
+        NULL
+    };
+    ConfigParser::ParseString(&name);
+    ConfigParser::ParseQuotedString(&value);
+
+    // TODO: Find a way to move this check to ICAP
+    for (int i = 0; warnFor[i] != NULL; i++) {
+        if (name.caseCmp(warnFor[i]) == 0) {
+            fatalf("%s:%d: meta name \"%s\" is a reserved ICAP header name",
+                   cfg_filename, config_lineno, name.termedBuf());
+        }
+    }
+
+    MetaHeader::Pointer meta = addMetaHeader(name);
+    MetaHeader::Value::Pointer headValue = meta->addValue(value);
+    aclParseAclList(parser, &headValue->aclList);
+}
+
+void
+Adaptation::Config::DumpMetaHeader(StoreEntry *entry, const char *name)
+{
+    typedef MetaHeaders::iterator AMLI;
+    for(AMLI m = metaHeaders.begin(); m != metaHeaders.end(); ++m) {
+        typedef MetaHeader::Values::iterator VLI;
+        for (VLI v =(*m)->values.begin(); v != (*m)->values.end(); ++v ) {
+            storeAppendPrintf(entry, "%s " SQUIDSTRINGPH " %s", 
+                              name, SQUIDSTRINGPRINT((*m)->name), ConfigParser::QuoteString((*v)->value));
+            dump_acl_list(entry, (*v)->aclList);
+            storeAppendPrintf(entry, "\n");
+         }
+    }        
+}
+
+void 
+Adaptation::Config::FreeMetaHeader()
+{
+    metaHeaders.clean();
+}
+
 void
 Adaptation::Config::ParseServiceSet()
 {
index 68558647bf23d4428c882c3d292dabc67972ac29..76d12d6de3066d6c79a28cf3e02c02e407836309 100644 (file)
@@ -2,6 +2,7 @@
 #define SQUID_ADAPTATION__CONFIG_H
 
 #include "event.h"
+#include "acl/Gadgets.h"
 #include "base/AsyncCall.h"
 #include "adaptation/forward.h"
 #include "adaptation/Elements.h"
@@ -19,6 +20,9 @@ public:
 
     static void ParseServiceSet(void);
     static void ParseServiceChain(void);
+    static void ParseMetaHeader(ConfigParser &parser);
+    static void FreeMetaHeader();
+    static void DumpMetaHeader(StoreEntry *, const char *);
 
     static void ParseAccess(ConfigParser &parser);
     static void FreeAccess(void);
@@ -43,6 +47,52 @@ public:
     time_t oldest_service_failure;
     int service_revival_delay;
 
+    /**
+     * Used to store meta headers. The meta headers are custom
+     * ICAP request headers or ECAP options used to pass custom 
+     * transaction-state related meta information to a service.
+     */
+    class MetaHeader: public RefCountable {
+    public:
+        typedef RefCount<MetaHeader> Pointer;
+        /// Stores a value for the meta header.
+        class Value: public RefCountable {
+        public:
+            typedef RefCount<Value> Pointer;
+            String value; ///< a header value
+            ACLList *aclList; ///< The access list used to determine if this value is valid for a request
+            explicit Value(const String &aVal) : value(aVal), aclList(NULL) {}
+            ~Value();
+        };
+        typedef Vector<Value::Pointer> Values;
+
+        explicit MetaHeader(const String &aName): name(aName) {}
+        
+        /**
+         * Adds a value to the meta header and returns a  pointer to the
+         * related Value object.
+         */
+        Value::Pointer addValue(const String &value);
+
+       /**
+        * Walks through the  possible values list of the  meta and selects 
+        * the first value which matches the given HttpRequest and HttpReply
+        * or NULL if none matches.
+        */
+        const char *match(HttpRequest *request, HttpReply *reply);
+        String name; ///< The meta header name
+        Values values; ///< The possible values list for the meta header
+    };
+    typedef Vector<MetaHeader::Pointer> MetaHeaders; 
+    static MetaHeaders metaHeaders; ///< The list of configured meta headers
+
+    /**
+     * Adds a header to the meta headers list and returns a pointer to the
+     * related metaHeaders object. If the header name already exists in list,
+     * returns a pointer to the existing object.
+     */
+    static MetaHeader::Pointer addMetaHeader(const String &header);
+
     typedef Vector<ServiceConfigPointer> ServiceConfigs;
     ServiceConfigs serviceConfigs;
 
index 7e6888e041aa79514f6535c2bd81e9ae5ea1b3e0..0e090f8d001298cb4465b4ee33f68e88d0fffffd 100644 (file)
@@ -81,11 +81,13 @@ Adaptation::Ecap::XactionRep::option(const libecap::Name &name) const
         return clientIpValue();
     if (name == libecap::metaUserName)
         return usernameValue();
-    if (name == Adaptation::Config::masterx_shared_name)
+    if (Adaptation::Config::masterx_shared_name && name == Adaptation::Config::masterx_shared_name)
         return masterxSharedValue(name);
 
     // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
-    return libecap::Area();
+
+    // If the name is unknown, metaValue returns an emtpy area
+    return metaValue(name);
 }
 
 void
@@ -101,6 +103,8 @@ Adaptation::Ecap::XactionRep::visitEachOption(libecap::NamedValueVisitor &visito
         if (const libecap::Area value = masterxSharedValue(name))
             visitor.visit(name, value);
     }
+    
+    visitEachMetaHeader(visitor);
 
     // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups
 }
@@ -162,6 +166,48 @@ Adaptation::Ecap::XactionRep::masterxSharedValue(const libecap::Name &name) cons
     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 Adaptation::Config::MetaHeaders::iterator ACAMLI;
+        for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+            if (name == (*i)->name.termedBuf()) {
+                if (const char *value = (*i)->match(request, reply))
+                    return libecap::Area::FromTempString(value);
+                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);
+    
+    typedef Adaptation::Config::MetaHeaders::iterator ACAMLI;
+    for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+        const char *v;
+        if (v = (*i)->match(request, reply)) {
+            const libecap::Name name((*i)->name.termedBuf());
+            const libecap::Area value = libecap::Area::FromTempString(v);
+            visitor.visit(name, value);
+        }
+    }
+}
+
 void
 Adaptation::Ecap::XactionRep::start()
 {
index 027c921b10647e9b089e291af4e6f4e9d0f090b4..cb7e0f2cf091b369cc089d807851ed860dacbc36 100644 (file)
@@ -93,6 +93,10 @@ protected:
     const libecap::Area clientIpValue() const;
     const libecap::Area usernameValue() const;
     const libecap::Area masterxSharedValue(const libecap::Name &name) const;
+    /// Return the adaptation meta header value for the given header "name"
+    const libecap::Area metaValue(const libecap::Name &name) const;
+    /// Return the adaptation meta headers and their values
+    void visitEachMetaHeader(libecap::NamedValueVisitor &visitor) const;
 
 private:
     AdapterXaction theMaster; // the actual adaptation xaction we represent
index 603e0955784abb624fd3ab041c26c998b307b5f0..c12605ef20abf7de7b6b2f3ad5dd6c9671f2b9fa 100644 (file)
@@ -1414,6 +1414,19 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
     if (TheConfig.send_username && request)
         makeUsernameHeader(request, buf);
 
+    // Adaptation::Config::metaHeaders
+    typedef Adaptation::Config::MetaHeaders::iterator ACAMLI;
+    for(ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+        HttpRequest *r = virgin.cause ?
+            virgin.cause : dynamic_cast<HttpRequest*>(virgin.header);
+        Must(r);
+
+        HttpReply *reply = dynamic_cast<HttpReply*>(virgin.header);
+
+        if (const char *value = (*i)->match(r, reply))
+            buf.Printf("%s: %s\r\n", (*i)->name.termedBuf(), value);
+    }
+
     // fprintf(stderr, "%s\n", buf.content());
 
     buf.append(ICAP::crlf, 2); // terminate ICAP header
index 4710020de9d0b6e7ce60c0e7b65e40c09acac5ac..610ab87120dd1c6418aa7f9631be7c6082b41dd4 100644 (file)
@@ -98,6 +98,9 @@
 static void parse_adaptation_service_set_type();
 static void parse_adaptation_service_chain_type();
 static void parse_adaptation_access_type();
+static void parse_adaptation_meta_type(Adaptation::Config::MetaHeaders *);
+static void dump_adaptation_meta_type(StoreEntry *, const char *, Adaptation::Config::MetaHeaders &);
+static void free_adaptation_meta_type(Adaptation::Config::MetaHeaders *);
 #endif
 
 #if ICAP_CLIENT
@@ -4373,6 +4376,23 @@ parse_adaptation_access_type()
     Adaptation::Config::ParseAccess(LegacyParser);
 }
 
+static void
+parse_adaptation_meta_type(Adaptation::Config::MetaHeaders *)
+{
+    Adaptation::Config::ParseMetaHeader(LegacyParser);
+}
+
+static void
+dump_adaptation_meta_type(StoreEntry *entry, const char *name, Adaptation::Config::MetaHeaders &)
+{
+    Adaptation::Config::DumpMetaHeader(entry, name);
+}
+
+static void
+free_adaptation_meta_type(Adaptation::Config::MetaHeaders *)
+{
+    // Nothing to do, it is released inside Adaptation::Config::freeService()
+}
 #endif /* USE_ADAPTATION */
 
 
index 0c22b1f61ab2f3cc8eafaf532649ebc028f34226..9409d54712ffc8cb9f775bbdcae9b4cf8962eca2 100644 (file)
@@ -36,6 +36,7 @@ https_port_list
 adaptation_access_type adaptation_service_set adaptation_service_chain acl icap_service icap_class
 adaptation_service_set_type    icap_service ecap_service
 adaptation_service_chain_type  icap_service ecap_service
+adaptation_meta_type   acl
 icap_access_type       icap_class acl
 icap_class_type                icap_service
 icap_service_type
index a1ac046e6d5c9233d3f01da59e28b8072c42467b..672a942b7a0b03be499c850a30b0a529a4e021af 100644 (file)
@@ -7016,6 +7016,41 @@ Example:
 adaptation_masterx_shared_names X-Subscriber-ID
 DOC_END
 
+NAME: adaptation_meta
+TYPE: adaptation_meta_type
+IFDEF: USE_ADAPTATION
+LOC: Adaptation::Config::metaHeaders
+DEFAULT: none
+DOC_START
+       This option allows Squid administrator to add custom ICAP request
+       headers or eCAP options to Squid ICAP requests or eCAP transactions.
+       Use it to pass custom authentication tokens and other
+       transaction-state related meta information to an ICAP/eCAP service.
+       
+       The addition of a meta header is ACL-driven:
+               adaptation_meta name value [!]aclname ...
+       
+       Processing for a given header name stops after the first ACL list match.
+       Thus, it is impossible to add two headers with the same name. If no ACL
+       lists match for a given header name, no such header is added. For 
+       example:
+       
+               # do not debug transactions except for those that need debugging
+               adaptation_meta X-Debug 1 needs_debugging
+       
+               # log all transactions except for those that must remain secret
+               adaptation_meta X-Log 1 !keep_secret
+       
+               # mark transactions from users in the "G 1" group
+               adaptation_meta X-Authenticated-Groups "G 1" authed_as_G1
+       
+       The "value" parameter may be a regular squid.conf token or a "double
+       quoted string". Within the quoted string, use backslash (\) to escape
+       any character, which is currently only useful for escaping backslashes
+       and double quotes. For example,
+           "this string has one backslash (\\) and two \"quotes\""
+DOC_END
+
 NAME: icap_retry
 TYPE: acl_access
 IFDEF: ICAP_CLIENT