From d9572179194cba721c4dab65f14581e5ad8a1c1c Mon Sep 17 00:00:00 2001 From: hno <> Date: Sun, 23 Jun 2002 19:32:23 +0000 Subject: [PATCH] external acl support. A mechanism where flexible ACL checks can be driven by external helpers. --- CREDITS | 11 +- src/Makefile.am | 3 +- src/acl.cc | 47 ++- src/cf.data.pre | 51 ++- src/enums.h | 3 +- src/external_acl.cc | 875 ++++++++++++++++++++++++++++++++++++++++++++ src/main.cc | 8 +- src/protos.h | 15 +- src/structs.h | 4 +- src/typedefs.h | 5 +- 10 files changed, 1012 insertions(+), 10 deletions(-) create mode 100644 src/external_acl.cc diff --git a/CREDITS b/CREDITS index 7171213e44..ea0eb11e75 100644 --- 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 + +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. diff --git a/src/Makefile.am b/src/Makefile.am index ee23fa0fb9..75e4b14053 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/acl.cc b/src/acl.cc index c5a38f1bf5..ca7da67fd0 100644 --- a/src/acl.cc +++ b/src/acl.cc @@ -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; diff --git a/src/cf.data.pre b/src/cf.data.pre index 34ff795738..3d16e7d79f 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -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 diff --git a/src/enums.h b/src/enums.h index 9eeb94cdf1..76e56e5a87 100644 --- a/src/enums.h +++ b/src/enums.h @@ -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 index 0000000000..8e870e3ce1 --- /dev/null +++ b/src/external_acl.cc @@ -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); + } +} diff --git a/src/main.cc b/src/main.cc index a4254e5d41..837dc94174 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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(); diff --git a/src/protos.h b/src/protos.h index 0787751e80..d9cc73cef4 100644 --- a/src/protos.h +++ b/src/protos.h @@ -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 */ diff --git a/src/structs.h b/src/structs.h index ff4638a3dd..c136d6d7b1 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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 { diff --git a/src/typedefs.h b/src/typedefs.h index 7874d85268..ba4554f8fa 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -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 */ -- 2.47.3