]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
external acl support. A mechanism where flexible ACL checks can be driven
authorhno <>
Sun, 23 Jun 2002 19:32:23 +0000 (19:32 +0000)
committerhno <>
Sun, 23 Jun 2002 19:32:23 +0000 (19:32 +0000)
by external helpers.

CREDITS
src/Makefile.am
src/acl.cc
src/cf.data.pre
src/enums.h
src/external_acl.cc [new file with mode: 0644]
src/main.cc
src/protos.h
src/structs.h
src/typedefs.h

diff --git a/CREDITS b/CREDITS
index 7171213e4496b0d32137464a995e3411c327f962..ea0eb11e756816383220bf8d4943240a64df25fa 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -1,4 +1,4 @@
-$Id: CREDITS,v 1.5 1999/04/15 06:14:50 wessels Exp $
+$Id: CREDITS,v 1.6 2002/06/23 13:32:23 hno Exp $
 
 ==============================================================================
 
@@ -293,3 +293,12 @@ lib/inet_ntoa.c:
 static char sccsid[] = "@(#)inet_ntoa.c        5.6 (Berkeley) 2/24/91";
 
 ==============================================================================
+
+src/external_acl.c
+
+Copyright (C) 2002 MARA Systems AB, Sweden <info@marasystems.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
index ee23fa0fb990a931d77d79c02ffa38a24b1fb614..75e4b1405350ba287e73edc17bc90b2e38641227 100644 (file)
@@ -1,7 +1,7 @@
 #
 #  Makefile for the Squid Object Cache server
 #
-#  $Id: Makefile.am,v 1.23 2002/04/14 15:57:01 hno Exp $
+#  $Id: Makefile.am,v 1.24 2002/06/23 13:32:23 hno Exp $
 #
 #  Uncomment and customize the following to suit your needs:
 #
@@ -124,6 +124,7 @@ squid_SOURCES = \
        errorpage.c \
        ETag.c \
        event.c \
+       external_acl.c \
        fd.c \
        filemap.c \
        forward.c \
index c5a38f1bf5eeec9290f19744fbfcab4e5a2e1eef..ca7da67fd0a8f90c9240b80677d6947caf2f2a27 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: acl.cc,v 1.279 2002/06/16 19:56:08 hno Exp $
+ * $Id: acl.cc,v 1.280 2002/06/23 13:32:23 hno Exp $
  *
  * DEBUG: section 28    Access Control
  * AUTHOR: Duane Wessels
@@ -80,6 +80,7 @@ static IPH aclLookupDstIPDone;
 static IPH aclLookupDstIPforASNDone;
 static FQDNH aclLookupSrcFQDNDone;
 static FQDNH aclLookupDstFQDNDone;
+static EAH aclLookupExternalDone;
 static wordlist *aclDumpIpList(void *);
 static wordlist *aclDumpDomainList(void *data);
 static wordlist *aclDumpTimeSpecList(acl_time_data *);
@@ -229,6 +230,8 @@ aclStrToType(const char *s)
        return ACL_REP_MIME_TYPE;
     if (!strcmp(s, "max_user_ip"))
        return ACL_MAX_USER_IP;
+    if (!strcmp(s, "external"))
+       return ACL_EXTERNAL;
     return ACL_NONE;
 }
 
@@ -301,6 +304,8 @@ aclTypeToStr(squid_acl type)
        return "rep_mime_type";
     if (type == ACL_MAX_USER_IP)
        return "max_user_ip";
+    if (type == ACL_EXTERNAL)
+       return "external";
     return "ERROR";
 }
 
@@ -858,6 +863,9 @@ because no authentication schemes are fully configured.\n", A->cfgline);
        aclParseArpList(&A->data);
        break;
 #endif
+    case ACL_EXTERNAL:
+       aclParseExternal(&A->data);
+       break;
     case ACL_NONE:
     case ACL_ENUM_MAX:
        fatal("Bad ACL type");
@@ -1713,6 +1721,9 @@ aclMatchAcl(acl * ae, aclCheck_t * checklist)
            header = "";
        return aclMatchRegex(ae->data, header);
        /* NOTREACHED */
+    case ACL_EXTERNAL:
+       return aclMatchExternal(ae->data, checklist);
+       /* NOTREACHED */
     case ACL_NONE:
     case ACL_ENUM_MAX:
        break;
@@ -1739,6 +1750,15 @@ aclMatchAclList(const acl_list * list, aclCheck_t * checklist)
     return 1;
 }
 
+static void
+aclCheckCleanup(aclCheck_t * checklist)
+{
+    /* Cleanup temporary stuff used by the ACL checking */
+    if (checklist->extacl_entry) {
+       cbdataReferenceDone(checklist->extacl_entry);
+    }
+}
+
 int
 aclCheckFast(const acl_access * A, aclCheck_t * checklist)
 {
@@ -1746,11 +1766,14 @@ aclCheckFast(const acl_access * A, aclCheck_t * checklist)
     debug(28, 5) ("aclCheckFast: list: %p\n", A);
     while (A) {
        allow = A->allow;
-       if (aclMatchAclList(A->acl_list, checklist))
+       if (aclMatchAclList(A->acl_list, checklist)) {
+           aclCheckCleanup(checklist);
            return allow == ACCESS_ALLOWED;
+       }
        A = A->next;
     }
     debug(28, 5) ("aclCheckFast: no matches, returning: %d\n", allow == ACCESS_DENIED);
+    aclCheckCleanup(checklist);
     return allow == ACCESS_DENIED;
 }
 
@@ -1834,6 +1857,11 @@ aclCheck(aclCheck_t * checklist)
            }
        }
 #endif
+       else if (checklist->state[ACL_EXTERNAL] == ACL_LOOKUP_NEEDED) {
+           acl *acl = aclFindByName(AclMatchedName);
+           externalAclLookup(checklist, acl->data, aclLookupExternalDone, checklist);
+           return;
+       }
        /*
         * We are done with this _acl_access entry.  Either the request
         * is allowed, denied, requires authentication, or we move on to
@@ -1862,6 +1890,7 @@ aclChecklistFree(aclCheck_t * checklist)
        requestUnlink(checklist->request);
     checklist->request = NULL;
     cbdataReferenceDone(checklist->conn);
+    aclCheckCleanup(checklist);
     cbdataFree(checklist);
 }
 
@@ -1964,6 +1993,15 @@ aclLookupProxyAuthDone(void *data, char *result)
     aclCheck(checklist);
 }
 
+static void
+aclLookupExternalDone(void *data, void *result)
+{
+    aclCheck_t *checklist = data;
+    checklist->state[ACL_EXTERNAL] = ACL_LOOKUP_DONE;
+    checklist->extacl_entry = cbdataReference(result);
+    aclCheck(checklist);
+}
+
 aclCheck_t *
 aclChecklistCreate(const acl_access * A, request_t * request, const char *ident)
 {
@@ -2111,6 +2149,9 @@ aclDestroyAcls(acl ** head)
        case ACL_MY_PORT:
            aclDestroyIntRange(a->data);
            break;
+       case ACL_EXTERNAL:
+           aclDestroyExternal(&a->data);
+           break;
        case ACL_NONE:
        case ACL_ENUM_MAX:
            debug(28, 1) ("aclDestroyAcls: no case for ACL type %d\n", a->type);
@@ -2522,6 +2563,8 @@ aclDumpGeneric(const acl * a)
     case ACL_SRC_ARP:
        return aclDumpArpList(a->data);
 #endif
+    case ACL_EXTERNAL:
+       return aclDumpExternal(a->data);
     case ACL_NONE:
     case ACL_ENUM_MAX:
        break;
index 34ff79573864f17607f476f92a44bd548353181b..3d16e7d79f7720f0a4b13f4b09232f6f560f1a5c 100644 (file)
@@ -1,6 +1,6 @@
 
 #
-# $Id: cf.data.pre,v 1.263 2002/06/21 12:58:20 hno Exp $
+# $Id: cf.data.pre,v 1.264 2002/06/23 13:32:24 hno Exp $
 #
 #
 # SQUID Web Proxy Cache          http://www.squid-cache.org/
@@ -1445,6 +1445,52 @@ DOC_START
        Squid versions.
 DOC_END
 
+NAME: external_acl_type
+TYPE: externalAclHelper
+LOC: Config.externalAclHelperList
+DEFAULT: none
+DOC_START
+       This option defines external acl classes using a helper program
+       to look up the status
+       
+         external_acl_type name [options] FORMAT.. /path/to/helper [helper arguments..]
+       
+       Options:
+
+         ttl=n         TTL in seconds for cached results (defaults to 3600
+                       for 1 hour)
+         negative_ttl=n
+                       TTL for cached negative lookups (default same
+                       as ttl)
+         concurrenty=n Concurrency level / number of processes spawn
+                       to service external acl lookups of this type.
+         cache=n       result cache size, 0 is unbounded (default)
+       
+       The helper receives lines per the FORMAT specification, and returns
+       OK or ERR indicating the validity of the request.
+
+       FORMAT specifications
+
+         %LOGIN        Authenticated user login name
+         %IDENT        Ident user name
+         %SRC          Client IP
+         %DST          Requested host
+         %PROTO        Requested protocol
+         %PORT         Requested port
+         %METHOD       Request method
+         %{Header}     HTTP request header
+         %{Hdr:member} HTTP request header list member
+         %{Hdr:;member}
+                       HTTP request header list member using ; as
+                       list separator. ; can be any non-alphanumeric
+                       character.
+
+       In addition, any string specified in the referencing acl will also be
+       included in the helper request line, after the specified formats
+       (see the acl directive)
+
+DOC_END
+
 COMMENT_START
  OPTIONS FOR TUNING THE CACHE
  -----------------------------------------------------------------------------
@@ -1979,6 +2025,9 @@ DOC_START
          # effect in rules that affect the reply data stream such as
          # http_reply_access.
 
+       acl acl_name external class_name [arguments...]
+         # external ACL lookup via a helper class defined by the
+         # external_acl_type directive.
 
 Examples:
 acl myexample dst_as 1241
index 9eeb94cdf195c723986675eefdc47685809e794f..76e56e5a8702606a23cceb74b30042203254d592 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: enums.h,v 1.207 2002/04/30 07:59:49 hno Exp $
+ * $Id: enums.h,v 1.208 2002/06/23 13:32:24 hno Exp $
  *
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
@@ -135,6 +135,7 @@ typedef enum {
     ACL_REQ_MIME_TYPE,
     ACL_REP_MIME_TYPE,
     ACL_MAX_USER_IP,
+    ACL_EXTERNAL,
     ACL_ENUM_MAX
 } squid_acl;
 
diff --git a/src/external_acl.cc b/src/external_acl.cc
new file mode 100644 (file)
index 0000000..8e870e3
--- /dev/null
@@ -0,0 +1,875 @@
+
+/*
+ * $Id: external_acl.cc,v 1.1 2002/06/23 13:32:24 hno Exp $
+ *
+ * DEBUG: section 82    External ACL
+ * AUTHOR: Henrik Nordstrom, MARA Systems AB
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  The contents of this file is Copyright (C) 2002 by MARA Systems AB,
+ *  Sweden, unless otherwise is indicated in the specific function. The
+ *  author gives his full permission to include this file into the Squid
+ *  software product under the terms of the GNU General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+#include "squid.h"
+
+#ifndef DEFAULT_EXTERNAL_ACL_TTL
+#define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
+#endif
+#ifndef DEFAULT_EXTERNAL_ACL_CONCURRENCY
+#define DEFAULT_EXTERNAL_ACL_CONCURRENCY 5
+#endif
+
+typedef struct _external_acl_format external_acl_format;
+typedef struct _external_acl_data external_acl_data;
+
+static char *makeExternalAclKey(aclCheck_t * ch, external_acl_data * acl_data);
+static void external_acl_cache_delete(external_acl * def, external_acl_entry * entry);
+static int external_acl_entry_expired(external_acl * def, external_acl_entry * entry);
+static void external_acl_cache_touch(external_acl * def, external_acl_entry * entry);
+
+/*******************************************************************
+ * external_acl cache entry
+ * Used opaqueue in the interface
+ */
+struct _external_acl_entry {
+    hash_link hash;
+    dlink_node lru;
+    int result;
+    time_t date;
+    char *user;
+    char *error;
+    external_acl *def;
+};
+
+/******************************************************************
+ * external_acl directive
+ */
+struct _external_acl {
+    external_acl *next;
+    int ttl;
+    int negative_ttl;
+    char *name;
+    external_acl_format *format;
+    wordlist *cmdline;
+    int children;
+    helper *helper;
+    hash_table *cache;
+    dlink_list lru_list;
+    int cache_size;
+    int cache_entries;
+    dlink_list queue;
+    int require_auth;
+};
+
+struct _external_acl_format {
+    enum {
+       EXT_ACL_LOGIN = 1,
+       EXT_ACL_IDENT,
+       EXT_ACL_SRC,
+       EXT_ACL_DST,
+       EXT_ACL_PROTO,
+       EXT_ACL_PORT,
+       EXT_ACL_METHOD,
+       EXT_ACL_HEADER,
+       EXT_ACL_HEADER_MEMBER,
+       EXT_ACL_HEADER_ID,
+       EXT_ACL_HEADER_ID_MEMBER,
+    } type;
+    external_acl_format *next;
+    char *header;
+    char *member;
+    char separator;
+    http_hdr_type header_id;
+};
+
+/* FIXME: These are not really cbdata, but it is an easy way
+ * to get them pooled, refcounted, accounted and freed properly...
+ */
+CBDATA_TYPE(external_acl);
+CBDATA_TYPE(external_acl_format);
+
+static void
+free_external_acl_format(void *data)
+{
+    external_acl_format *p = data;
+    safe_free(p->header);
+}
+
+static void
+free_external_acl(void *data)
+{
+    external_acl *p = data;
+    safe_free(p->name);
+    while (p->format) {
+       external_acl_format *f = p->format;
+       p->format = f->next;
+       cbdataFree(f);
+    }
+    wordlistDestroy(&p->cmdline);
+    if (p->helper) {
+       helperShutdown(p->helper);
+       helperFree(p->helper);
+       p->helper = NULL;
+    }
+    while (p->lru_list.tail)
+       external_acl_cache_delete(p, p->lru_list.tail->data);
+    if (p->cache)
+       hashFreeMemory(p->cache);
+}
+
+void
+parse_externalAclHelper(external_acl ** list)
+{
+    external_acl *a;
+    char *token;
+    external_acl_format **p;
+
+    CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
+    CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
+
+    a = cbdataAlloc(external_acl);
+
+    a->ttl = DEFAULT_EXTERNAL_ACL_TTL;
+    a->negative_ttl = -1;
+    a->children = DEFAULT_EXTERNAL_ACL_CONCURRENCY;
+
+    token = strtok(NULL, w_space);
+    if (!token)
+       self_destruct();
+    a->name = xstrdup(token);
+
+    token = strtok(NULL, w_space);
+    /* Parse options */
+    while (token) {
+       if (strncmp(token, "ttl=", 4) == 0) {
+           a->ttl = atoi(token + 4);
+       } else if (strncmp(token, "negative_ttl=", 13) == 0) {
+           a->negative_ttl = atoi(token + 13);
+       } else if (strncmp(token, "concurrency=", 12) == 0) {
+           a->children = atoi(token + 12);
+       } else if (strncmp(token, "cache=", 6) == 0) {
+           a->cache_size = atoi(token + 6);
+       } else {
+           break;
+       }
+
+       token = strtok(NULL, w_space);
+    }
+    if (a->negative_ttl == -1)
+       a->negative_ttl = a->ttl;
+
+    /* Parse format */
+    p = &a->format;
+    while (token) {
+       external_acl_format *format;
+
+       /* stop on first non-format token found */
+       if (*token != '%')
+           break;
+
+       format = cbdataAlloc(external_acl_format);
+
+       if (strncmp(token, "%{", 2) == 0) {
+           /* header format */
+           char *header, *member, *end;
+           header = token + 2;
+           end = strchr(header, '}');
+           /* cut away the terminating } */
+           if (end && strlen(end) == 1)
+               *end = '\0';
+           else
+               self_destruct();
+
+           member = strchr(header, ':');
+           if (member) {
+               /* Split in header and member */
+               *member++ = '\0';
+               if (!isalnum(*member))
+                   format->separator = *member++;
+               else
+                   format->separator = ',';
+               format->member = xstrdup(member);
+               format->type = EXT_ACL_HEADER_MEMBER;
+           } else {
+               format->type = EXT_ACL_HEADER;
+           }
+           format->header = xstrdup(header);
+           format->header_id = httpHeaderIdByNameDef(header, strlen(header));
+           if (format->header_id != -1) {
+               if (member)
+                   format->type = EXT_ACL_HEADER_ID_MEMBER;
+               else
+                   format->type = EXT_ACL_HEADER_ID;
+           }
+       }
+       if (strcmp(token, "%LOGIN") == 0) {
+           format->type = EXT_ACL_LOGIN;
+           a->require_auth = 1;
+       } else if (strcmp(token, "%IDENT") == 0)
+           format->type = EXT_ACL_IDENT;
+       else if (strcmp(token, "%SRC") == 0)
+           format->type = EXT_ACL_SRC;
+       else if (strcmp(token, "%DST") == 0)
+           format->type = EXT_ACL_DST;
+       else if (strcmp(token, "%PROTO") == 0)
+           format->type = EXT_ACL_PROTO;
+       else if (strcmp(token, "%PORT") == 0)
+           format->type = EXT_ACL_PORT;
+       else if (strcmp(token, "%METHOD") == 0)
+           format->type = EXT_ACL_METHOD;
+       else {
+           self_destruct();
+       }
+       *p = format;
+       p = &format->next;
+       token = strtok(NULL, w_space);
+    }
+
+    /* There must be at least one format token */
+    if (!a->format)
+       self_destruct();
+
+    /* helper */
+    if (!token)
+       self_destruct();
+    wordlistAdd(&a->cmdline, token);
+
+    /* arguments */
+    while ((token = strtok(NULL, w_space)) != NULL)
+       wordlistAdd(&a->cmdline, token);
+
+    while (*list)
+       list = &(*list)->next;
+    *list = a;
+}
+
+void
+dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
+{
+    const external_acl *node;
+    const external_acl_format *format;
+    const wordlist *word;
+    for (node = list; node; node = node->next) {
+       storeAppendPrintf(sentry, "%s %s", name, node->name);
+       if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
+           storeAppendPrintf(sentry, " ttl=%d", node->ttl);
+       if (node->negative_ttl != node->ttl)
+           storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
+       if (node->children != DEFAULT_EXTERNAL_ACL_CONCURRENCY)
+           storeAppendPrintf(sentry, " concurrency=%d", node->children);
+       for (format = node->format; format; format = format->next) {
+           switch (format->type) {
+           case EXT_ACL_HEADER:
+           case EXT_ACL_HEADER_ID:
+               storeAppendPrintf(sentry, " %%{%s}", format->header);
+               break;
+           case EXT_ACL_HEADER_MEMBER:
+           case EXT_ACL_HEADER_ID_MEMBER:
+               storeAppendPrintf(sentry, " %%{%s:%s}", format->header, format->member);
+               break;
+#define DUMP_EXT_ACL_TYPE(a) \
+           case EXT_ACL_##a: \
+               storeAppendPrintf(sentry, " %%%s", #a); \
+               break
+               DUMP_EXT_ACL_TYPE(LOGIN);
+               DUMP_EXT_ACL_TYPE(IDENT);
+               DUMP_EXT_ACL_TYPE(SRC);
+               DUMP_EXT_ACL_TYPE(DST);
+               DUMP_EXT_ACL_TYPE(PROTO);
+               DUMP_EXT_ACL_TYPE(PORT);
+               DUMP_EXT_ACL_TYPE(METHOD);
+           }
+       }
+       for (word = node->cmdline; word; word = word->next)
+           storeAppendPrintf(sentry, " %s", word->key);
+       storeAppendPrintf(sentry, "\n");
+    }
+}
+
+void
+free_externalAclHelper(external_acl ** list)
+{
+    while (*list) {
+       external_acl *node = *list;
+       *list = node->next;
+       node->next = NULL;
+       cbdataFree(node);
+    }
+}
+
+static external_acl *
+find_externalAclHelper(const char *name)
+{
+    external_acl *node;
+
+    for (node = Config.externalAclHelperList; node; node = node->next) {
+       if (strcmp(node->name, name) == 0)
+           return node;
+    }
+    return NULL;
+}
+
+
+/******************************************************************
+ * external acl type
+ */
+
+struct _external_acl_data {
+    external_acl *def;
+    wordlist *arguments;
+};
+
+CBDATA_TYPE(external_acl_data);
+static void
+free_external_acl_data(void *data)
+{
+    external_acl_data *p = data;
+    wordlistDestroy(&p->arguments);
+    cbdataReferenceDone(p->def);
+}
+
+void
+aclParseExternal(void *dataptr)
+{
+    external_acl_data **datap = dataptr;
+    external_acl_data *data;
+    char *token;
+    if (*datap)
+       self_destruct();
+    CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
+    data = cbdataAlloc(external_acl_data);
+    token = strtok(NULL, w_space);
+    if (!token)
+       self_destruct();
+    data->def = cbdataReference(find_externalAclHelper(token));
+    if (!data->def)
+       self_destruct();
+    while ((token = strtok(NULL, w_space))) {
+       wordlistAdd(&data->arguments, token);
+    }
+    *datap = data;
+}
+
+void
+aclDestroyExternal(void **dataptr)
+{
+    cbdataFree(*dataptr);
+}
+
+int
+aclMatchExternal(void *data, aclCheck_t * ch)
+{
+    int result;
+    external_acl_entry *entry;
+    external_acl_data *acl = data;
+    const char *key = "";
+    debug(82, 9) ("aclMatchExternal: acl=\"%s\"\n", acl->def->name);
+    entry = ch->extacl_entry;
+    if (entry) {
+       if (cbdataReferenceValid(entry) && entry->def == acl->def &&
+           strcmp(entry->hash.key, key) == 0) {
+           /* Ours, use it.. */
+       } else {
+           /* Not valid, or not ours.. get rid of it */
+           cbdataReferenceDone(ch->extacl_entry);
+           entry = NULL;
+       }
+    }
+    if (!entry) {
+       if (acl->def->require_auth) {
+           int ti;
+           /* Make sure the user is authenticated */
+           if ((ti = aclAuthenticated(ch)) != 1) {
+               debug(82, 2) ("aclMatchExternal: %s user not authenticated (%d)\n", acl->def->name, ti);
+               return ti;
+           }
+       }
+       key = makeExternalAclKey(ch, acl);
+       entry = hash_lookup(acl->def->cache, key);
+       if (entry && external_acl_entry_expired(acl->def, entry)) {
+           /* Expired entry, ignore */
+           debug(82, 2) ("external_acl_cache_lookup: '%s' = expired\n", key);
+           entry = NULL;
+       }
+       ch->auth_user_request = NULL;
+    }
+    if (!entry) {
+       debug(82, 2) ("aclMatchExternal: %s(\"%s\") = lookup needed\n", acl->def->name, key);
+       ch->state[ACL_EXTERNAL] = ACL_LOOKUP_NEEDED;
+       return 0;
+    }
+    external_acl_cache_touch(acl->def, entry);
+    result = entry->result;
+    debug(82, 2) ("aclMatchExternal: %s = %d\n", acl->def->name, result);
+    /* FIXME: This should allocate it's own storage in the request. This
+     * piggy backs on ident, and may fail if there is child proxies..
+     * Register the username for logging purposes
+     */
+    if (entry->user && cbdataReferenceValid(ch->conn) && !ch->conn->rfc931[0])
+       xstrncpy(ch->conn->rfc931, entry->user, USER_IDENT_SZ);
+    return result;
+}
+
+wordlist *
+aclDumpExternal(void *data)
+{
+    external_acl_data *acl = data;
+    wordlist *result = NULL;
+    wordlist *arg;
+    MemBuf mb;
+    memBufDefInit(&mb);
+    memBufPrintf(&mb, "%s", acl->def->name);
+    for (arg = acl->arguments; arg; arg = arg->next) {
+       memBufPrintf(&mb, " %s", arg->key);
+    }
+    wordlistAdd(&result, mb.buf);
+    memBufClean(&mb);
+    return result;
+}
+
+/******************************************************************
+ * external_acl cache
+ */
+
+CBDATA_TYPE(external_acl_entry);
+
+static void
+external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
+{
+    dlinkDelete(&entry->lru, &def->lru_list);
+    dlinkAdd(entry, &entry->lru, &def->lru_list);
+}
+
+static char *
+makeExternalAclKey(aclCheck_t * ch, external_acl_data * acl_data)
+{
+    static MemBuf mb = MemBufNULL;
+    char buf[256];
+    int first = 1;
+    wordlist *arg;
+    external_acl_format *format;
+    request_t *request = ch->request;
+    String sb = StringNull;
+    memBufReset(&mb);
+    for (format = acl_data->def->format; format; format = format->next) {
+       const char *str = NULL;
+       switch (format->type) {
+       case EXT_ACL_LOGIN:
+           str = authenticateUserRequestUsername(request->auth_user_request);
+           break;
+       case EXT_ACL_IDENT:
+           str = ch->rfc931;
+           if (!str) {
+               ch->state[ACL_IDENT] = ACL_LOOKUP_NEEDED;
+               goto error;
+           }
+           break;
+       case EXT_ACL_SRC:
+           str = inet_ntoa(ch->src_addr);
+           break;
+       case EXT_ACL_DST:
+           str = request->host;
+           break;
+       case EXT_ACL_PROTO:
+           str = ProtocolStr[request->protocol];
+           break;
+       case EXT_ACL_PORT:
+           snprintf(buf, sizeof(buf), "%d", request->port);
+           str = buf;
+       case EXT_ACL_METHOD:
+           str = RequestMethodStr[request->method];
+           break;
+       case EXT_ACL_HEADER:
+           sb = httpHeaderGetByName(&request->header, format->header);
+           str = strBuf(sb);
+           break;
+       case EXT_ACL_HEADER_ID:
+           sb = httpHeaderGetStrOrList(&request->header, format->header_id);
+           str = strBuf(sb);
+           break;
+       case EXT_ACL_HEADER_MEMBER:
+           sb = httpHeaderGetByNameListMember(&request->header, format->header, format->member, format->separator);
+           str = strBuf(sb);
+           break;
+       case EXT_ACL_HEADER_ID_MEMBER:
+           sb = httpHeaderGetListMember(&request->header, format->header_id, format->member, format->separator);
+           str = strBuf(sb);
+           break;
+       }
+       if (str)
+           if (!*str)
+               str = NULL;
+       if (!str)
+           str = "-";
+       if (first) {
+           char *s = log_quote(str);
+           memBufPrintf(&mb, "%s", s);
+           safe_free(s);
+       } else {
+           char *s = log_quote(str);
+           memBufPrintf(&mb, " %s", s);
+           safe_free(s);
+       }
+       stringClean(&sb);
+       first = 0;
+    }
+    for (arg = acl_data->arguments; arg; arg = arg->next) {
+       if (first) {
+           char *s = log_quote(arg->key);
+           memBufPrintf(&mb, "%s", s);
+           safe_free(s);
+       } else {
+           char *s = log_quote(arg->key);
+           memBufPrintf(&mb, " %s", s);
+           safe_free(s);
+       }
+       first = 0;
+    }
+    return mb.buf;
+  error:
+    return NULL;
+}
+
+static int
+external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
+{
+    if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
+       return 1;
+    else
+       return 0;
+}
+
+static void
+free_external_acl_entry(void *data)
+{
+    external_acl_entry *entry = data;
+    safe_free(entry->hash.key);
+    safe_free(entry->user);
+    safe_free(entry->error);
+}
+
+static external_acl_entry *
+external_acl_cache_add(external_acl * def, const char *key, int result, char *user, char *error)
+{
+    external_acl_entry *entry = hash_lookup(def->cache, key);
+    debug(82, 2) ("external_acl_cache_add: Adding '%s' = %d\n", key, result);
+    if (entry) {
+       debug(82, 3) ("external_acl_cache_add: updating existing entry\n");
+       entry->date = squid_curtime;
+       entry->result = result;
+       safe_free(entry->user);
+       safe_free(entry->error);
+       if (user)
+           entry->user = xstrdup(user);
+       if (error)
+           entry->error = xstrdup(error);
+       external_acl_cache_touch(def, entry);
+       return entry;
+    }
+    CBDATA_INIT_TYPE_FREECB(external_acl_entry, free_external_acl_entry);
+    /* Maintain cache size */
+    if (def->cache_size && def->cache_entries >= def->cache_size)
+       external_acl_cache_delete(def, def->lru_list.tail->data);
+    entry = cbdataAlloc(external_acl_entry);
+    entry->hash.key = xstrdup(key);
+    entry->date = squid_curtime;
+    entry->result = result;
+    if (user)
+       entry->user = xstrdup(user);
+    if (error)
+       entry->error = xstrdup(error);
+    entry->def = def;
+    hash_join(def->cache, &entry->hash);
+    dlinkAdd(entry, &entry->lru, &def->lru_list);
+    def->cache_entries += 1;
+    return entry;
+}
+
+static void
+external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
+{
+    hash_remove_link(def->cache, &entry->hash);
+    dlinkDelete(&entry->lru, &def->lru_list);
+    def->cache_entries -= 1;
+    cbdataFree(entry);
+}
+
+/******************************************************************
+ * external_acl helpers
+ */
+
+typedef struct _externalAclState externalAclState;
+struct _externalAclState {
+    EAH *callback;
+    void *callback_data;
+    char *key;
+    external_acl *def;
+    dlink_node list;
+    externalAclState *queue;
+};
+
+CBDATA_TYPE(externalAclState);
+static void
+free_externalAclState(void *data)
+{
+    externalAclState *state = data;
+    safe_free(state->key);
+    cbdataReferenceDone(state->callback_data);
+    cbdataReferenceDone(state->def);
+}
+
+/* FIXME: This should be moved to tools.c */
+static char *
+strwordtok(char *buf, char **t)
+{
+    unsigned char *word = NULL;
+    unsigned char *p = (unsigned char *) buf;
+    unsigned char *d;
+    unsigned char ch;
+    int quoted = 0;
+    if (!p)
+       p = (unsigned char *) *t;
+    if (!p)
+       goto error;
+    while (*p && isspace(*p))
+       p++;
+    if (!*p)
+       goto error;
+    word = d = p;
+    while ((ch = *p)) {
+       switch (ch) {
+       case '\\':
+           p++;
+           *d++ = ch = *p;
+           if (ch)
+               p++;
+           break;
+       case '"':
+           quoted = !quoted;
+           p++;
+       default:
+           if (!quoted && isspace(*p)) {
+               p++;
+               goto done;
+           }
+           *d++ = *p++;
+           break;
+       }
+    }
+  done:
+    *d++ = '\0';
+  error:
+    *t = p;
+    return word;
+}
+
+/*
+ * The helper program receives queries on stdin, one
+ * per line, and must return the result on on stdout as
+ *   OK user="Users login name"
+ * on success, and
+ *   ERR error="Description of the error"
+ * on error (the user/error options are optional)
+ *
+ * General result syntax:
+ *
+ *   OK/ERR keyword=value ...
+ *
+ * Keywords:
+ *
+ *   user=        The users name (login)
+ *   error=       Error description (only defined for ERR results)
+ *
+ * Other keywords may be added to the protocol later
+ *
+ * value needs to be enclosed in quotes if it may contain whitespace, or 
+ * the whitespace escaped using \ (\ escaping obviously also applies to  
+ * any " characters)
+ */
+
+static void
+externalAclHandleReply(void *data, char *reply)
+{
+    externalAclState *state = data;
+    externalAclState *next;
+    int result = 0;
+    char *status;
+    char *token;
+    char *value;
+    char *t;
+    char *user = NULL;
+    char *error = NULL;
+    external_acl_entry *entry;
+
+    debug(82, 2) ("externalAclHandleReply: reply=\"%s\"\n", reply);
+
+    status = strwordtok(reply, &t);
+    if (status && strcmp(status, "OK") == 0)
+       result = 1;
+
+    while ((token = strwordtok(NULL, &t))) {
+       value = strchr(token, '=');
+       if (value) {
+           *value++ = '\0';    /* terminate the token, and move up to the value */
+           if (strcmp(token, "user") == 0)
+               user = value;
+           else if (strcmp(token, "error") == 0)
+               error = value;
+       }
+    }
+
+    dlinkDelete(&state->list, &state->def->queue);
+    if (cbdataReferenceValid(state->def))
+       entry = external_acl_cache_add(state->def, state->key, result, user, error);
+    else
+       entry = NULL;
+
+    do {
+       void *cbdata;
+       cbdataReferenceDone(state->def);
+
+       if (cbdataReferenceValidDone(state->callback_data, &cbdata))
+           state->callback(cbdata, entry);
+
+       next = state->queue;
+       cbdataFree(state);
+       state = next;
+    } while (state);
+}
+
+void
+externalAclLookup(aclCheck_t * ch, void *acl_data, EAH * callback, void *callback_data)
+{
+    MemBuf buf;
+    external_acl_data *acl = acl_data;
+    external_acl *def = acl->def;
+    const char *key = makeExternalAclKey(ch, acl);
+    external_acl_entry *entry = hash_lookup(def->cache, key);
+    externalAclState *state;
+    debug(82, 2) ("externalAclLookup: lookup in '%s' for '%s'\n", def->name, key);
+    if (!key) {
+       debug(82, 1) ("externalAclLookup: lookup in '%s', prerequisit failure\n", def->name);
+       callback(callback_data, NULL);
+       return;
+    }
+    state = cbdataAlloc(externalAclState);
+    state->def = cbdataReference(def);
+    state->callback = callback;
+    state->callback_data = cbdataReference(callback_data);
+    state->key = xstrdup(key);
+    if (entry && !external_acl_entry_expired(def, entry)) {
+       if (entry->result == -1) {
+           /* There is a pending lookup. Hook into it */
+           dlink_node *node;
+           for (node = def->queue.head; node; node = node->next) {
+               externalAclState *oldstate = node->data;
+               if (strcmp(state->key, oldstate->key) == 0) {
+                   state->queue = oldstate->queue;
+                   oldstate->queue = state;
+                   return;
+               }
+           }
+       } else {
+           /* There is a cached valid result.. use it */
+           /* This should not really happen, but what the heck.. */
+           callback(callback_data, entry);
+           cbdataFree(state);
+           return;
+       }
+    }
+    /* Check for queue overload */
+    if (def->helper->stats.queue_size >= def->helper->n_running) {
+       int result = -1;
+       external_acl_entry *entry = hash_lookup(def->cache, key);
+       debug(82, 1) ("externalAclLookup: '%s' queue overload\n", def->name);
+       if (entry)
+           result = entry->result;
+       cbdataFree(state);
+       callback(callback_data, entry);
+       return;
+    }
+    /* Send it off to the helper */
+    memBufDefInit(&buf);
+    memBufPrintf(&buf, "%s\n", key);
+    helperSubmit(def->helper, buf.buf, externalAclHandleReply, state);
+    external_acl_cache_add(def, key, -1, NULL, NULL);
+    dlinkAdd(state, &state->list, &def->queue);
+    memBufClean(&buf);
+}
+
+static void
+externalAclStats(StoreEntry * sentry)
+{
+    external_acl *p;
+
+    for (p = Config.externalAclHelperList; p; p = p->next) {
+       storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
+       storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
+       helperStats(sentry, p->helper);
+       storeAppendPrintf(sentry, "\n");
+    }
+}
+
+void
+externalAclInit(void)
+{
+    static int firstTimeInit = 1;
+    external_acl *p;
+
+    for (p = Config.externalAclHelperList; p; p = p->next) {
+       if (!p->cache)
+           p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
+       if (!p->helper)
+           p->helper = helperCreate(p->name);
+       p->helper->cmdline = p->cmdline;
+       p->helper->n_to_start = p->children;
+       p->helper->ipc_type = IPC_TCP_SOCKET;
+       helperOpenServers(p->helper);
+    }
+    if (firstTimeInit) {
+       firstTimeInit = 0;
+       cachemgrRegister("external_acl",
+           "External ACL stats",
+           externalAclStats, 0, 1);
+       CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
+    }
+}
+
+void
+externalAclShutdown(void)
+{
+    external_acl *p;
+    for (p = Config.externalAclHelperList; p; p = p->next) {
+       helperShutdown(p->helper);
+    }
+}
index a4254e5d4147aca7c7c72a813b2bd38a324223a4..837dc941749acea980a680677f11e7f6bf240823 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: main.cc,v 1.350 2002/05/22 20:48:14 wessels Exp $
+ * $Id: main.cc,v 1.351 2002/06/23 13:32:24 hno Exp $
  *
  * DEBUG: section 1     Startup and Main Loop
  * AUTHOR: Harvest Derived
@@ -344,6 +344,7 @@ mainReconfigure(void)
 #endif
     redirectShutdown();
     authenticateShutdown();
+    externalAclShutdown();
     storeDirCloseSwapLogs();
     errorClean();
     enter_suid();              /* root to read config file */
@@ -362,6 +363,7 @@ mainReconfigure(void)
 #endif
     redirectInit();
     authenticateInit(&Config.authConfig);
+    externalAclInit();
 #if USE_WCCP
     wccpInit();
 #endif
@@ -388,6 +390,7 @@ mainRotate(void)
 #endif
     redirectShutdown();
     authenticateShutdown();
+    externalAclShutdown();
     _db_rotate_log();          /* cache.log */
     storeDirWriteCleanLogs(1);
     storeLogRotate();          /* store.log */
@@ -403,6 +406,7 @@ mainRotate(void)
 #endif
     redirectInit();
     authenticateInit(&Config.authConfig);
+    externalAclInit();
 }
 
 static void
@@ -484,6 +488,7 @@ mainInitialize(void)
 #endif
     redirectInit();
     authenticateInit(&Config.authConfig);
+    externalAclInit();
     useragentOpenLog();
     refererOpenLog();
     httpHeaderInitModule();    /* must go before any header processing (e.g. the one in errorInitialize) */
@@ -715,6 +720,7 @@ main(int argc, char **argv)
            idnsShutdown();
 #endif
            redirectShutdown();
+           externalAclShutdown();
            eventAdd("SquidShutdown", SquidShutdown, NULL, (double) (wait + 1), 1);
        }
        eventRun();
index 0787751e8018ab7e2878161942da4653daa200de..d9cc73cef41f284ee36ef5da13fbed4120c92cf8 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: protos.h,v 1.439 2002/06/16 20:05:53 hno Exp $
+ * $Id: protos.h,v 1.440 2002/06/23 13:32:24 hno Exp $
  *
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
@@ -1321,4 +1321,17 @@ extern int WIN32_Subsystem_Init(void);
 extern void WIN32_Exit(void);
 #endif
 
+/* external_acl.c */
+extern void parse_externalAclHelper(external_acl **);
+extern void dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl *);
+extern void free_externalAclHelper(external_acl **);
+extern void aclParseExternal(void *curlist);
+extern void aclDestroyExternal(void **curlust);
+extern int aclMatchExternal(void *dataptr, aclCheck_t * ch);
+extern wordlist *aclDumpExternal(void *dataptr);
+typedef void EAH(void *data, void *result);
+extern void externalAclLookup(aclCheck_t * ch, void *acl_data, EAH * handler, void *data);
+extern void externalAclInit(void);
+extern void externalAclShutdown(void);
+
 #endif /* SQUID_PROTOS_H */
index ff4638a3dd53956e9e06628d656e0f837dd4148c..c136d6d7b11ff7968e9cf297b6c82a3477a35501 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: structs.h,v 1.420 2002/06/21 12:58:20 hno Exp $
+ * $Id: structs.h,v 1.421 2002/06/23 13:32:25 hno Exp $
  *
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
@@ -290,6 +290,7 @@ struct _aclCheck_t {
 #endif
     PF *callback;
     void *callback_data;
+    external_acl_entry *extacl_entry;
 };
 
 struct _wordlist {
@@ -686,6 +687,7 @@ struct _SquidConfig {
     } warnings;
     char *store_dir_select_algorithm;
     int sleep_after_fork;      /* microseconds */
+    external_acl *externalAclHelperList;
 };
 
 struct _SquidConfig2 {
index 7874d85268ee9248b800d2f50f0efc622a2781bd..ba4554f8faca248ba88c1b5599cc882ca14973e6 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * $Id: typedefs.h,v 1.133 2002/04/06 08:49:28 adrian Exp $
+ * $Id: typedefs.h,v 1.134 2002/06/23 13:32:25 hno Exp $
  *
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
@@ -351,4 +351,7 @@ typedef RemovalPolicy *REMOVALPOLICYCREATE(wordlist * args);
 
 typedef int STDIRSELECT(const StoreEntry *);
 
+typedef struct _external_acl external_acl;
+typedef struct _external_acl_entry external_acl_entry;
+
 #endif /* SQUID_TYPEDEFS_H */