/* * $Id: external_acl.c,v 1.1.2.28 2003/09/02 07:51:59 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, #if USE_IDENT EXT_ACL_IDENT, #endif EXT_ACL_SRC, EXT_ACL_DST, EXT_ACL_PROTO, EXT_ACL_PORT, EXT_ACL_URI, 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, "children=", 9) == 0) { a->children = atoi(token + 9); } 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; } } else if (strcmp(token, "%LOGIN") == 0) { format->type = EXT_ACL_LOGIN; a->require_auth = 1; } #if USE_IDENT else if (strcmp(token, "%IDENT") == 0) format->type = EXT_ACL_IDENT; #endif 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, "%URI") == 0) format->type = EXT_ACL_URI; 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 */ parse_wordlist(&a->cmdline); 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); #if USE_IDENT DUMP_EXT_ACL_TYPE(IDENT); #endif DUMP_EXT_ACL_TYPE(SRC); DUMP_EXT_ACL_TYPE(DST); DUMP_EXT_ACL_TYPE(PROTO); DUMP_EXT_ACL_TYPE(PORT); DUMP_EXT_ACL_TYPE(URI); 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); cbdataUnlock(p->def); p->def = NULL; } 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 = find_externalAclHelper(token); cbdataLock(data->def); if (!data->def) self_destruct(); while ((token = strtokFile())) { 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 = NULL; external_acl_data *acl = data; const char *key = ""; debug(82, 9) ("aclMatchExternal: acl=\"%s\"\n", acl->def->name); if (ch->extacl_entry) { entry = ch->extacl_entry; if (!cbdataValid(entry)) entry = NULL; cbdataUnlock(ch->extacl_entry); ch->extacl_entry = NULL; } 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); if (!key) { /* Not sufficient data to process */ return -1; } ch->auth_user_request = NULL; if (entry) { if (entry->def != acl->def || strcmp(entry->hash.key, key) != 0) { /* Not ours.. get rid of it */ cbdataUnlock(ch->extacl_entry); ch->extacl_entry = NULL; entry = NULL; } } if (!entry) { 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; } } if (!entry || entry->result == -1) { debug(82, 2) ("aclMatchExternal: %s(\"%s\") = lookup needed\n", acl->def->name, key); if (acl->def->helper->stats.queue_size >= acl->def->helper->n_running) debug(82, 1) ("aclMatchExternal: '%s' queue overload. Request rejected.\n", acl->def->name); else ch->state[ACL_EXTERNAL] = ACL_LOOKUP_NEEDED; return -1; } 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) { xstrncpy(ch->rfc931, entry->user, USER_IDENT_SZ); if (cbdataValid(ch->conn)) 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(ch->auth_user_request); break; #if USE_IDENT case EXT_ACL_IDENT: str = ch->rfc931; if (!str || !*str) { ch->state[ACL_IDENT] = ACL_LOOKUP_NEEDED; return NULL; } break; #endif 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; break; case EXT_ACL_URI: str = urlCanonical(request); break; 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) memBufAppend(&mb, " ", 1); strwordquote(&mb, str); stringClean(&sb); first = 0; } for (arg = acl_data->arguments; arg; arg = arg->next) { if (!first) memBufAppend(&mb, " ", 1); strwordquote(&mb, arg->key); first = 0; } return mb.buf; } 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); cbdataUnlock(state->callback_data); cbdataUnlock(state->def); } /* * 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 = NULL; debug(82, 2) ("externalAclHandleReply: reply=\"%s\"\n", reply); if (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 (cbdataValid(state->def)) { if (reply) entry = external_acl_cache_add(state->def, state->key, result, user, error); else { external_acl_entry *oldentry = hash_lookup(state->def->cache, state->key); if (oldentry) external_acl_cache_delete(state->def, oldentry); } } do { cbdataUnlock(state->def); state->def = NULL; if (cbdataValid(state->callback_data)) state->callback(state->callback_data, entry); cbdataUnlock(state->callback_data); state->callback_data = NULL; 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; external_acl_entry *entry; externalAclState *state; if (acl->def->require_auth) { int ti; /* Make sure the user is authenticated */ if ((ti = aclAuthenticated(ch)) != 1) { debug(82, 1) ("externalAclLookup: %s user authentication failure (%d)\n", acl->def->name, ti); callback(callback_data, NULL); return; } } key = makeExternalAclKey(ch, acl); ch->auth_user_request = NULL; if (!key) { debug(82, 1) ("externalAclLookup: lookup in '%s', prerequisit failure\n", def->name); callback(callback_data, NULL); return; } debug(82, 2) ("externalAclLookup: lookup in '%s' for '%s'\n", def->name, key); entry = hash_lookup(def->cache, key); state = cbdataAlloc(externalAclState); state->def = def; cbdataLock(state->def); state->callback = callback; state->callback_data = callback_data; state->key = xstrdup(key); cbdataLock(state->callback_data); 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); } }