]> git.ipfire.org Git - thirdparty/squid.git/blame - src/external_acl.cc
Fix several ACL-related bugs including broken default rules and ACCESS_DUNNO.
[thirdparty/squid.git] / src / external_acl.cc
CommitLineData
d9572179 1
2/*
262a0e14 3 * $Id$
d9572179 4 *
5 * DEBUG: section 82 External ACL
6 * AUTHOR: Henrik Nordstrom, MARA Systems AB
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * The contents of this file is Copyright (C) 2002 by MARA Systems AB,
12 * Sweden, unless otherwise is indicated in the specific function. The
13 * author gives his full permission to include this file into the Squid
14 * software product under the terms of the GNU General Public License as
15 * published by the Free Software Foundation; either version 2 of the
16 * License, or (at your option) any later version.
17 *
18 * Squid is the result of efforts by numerous individuals from
19 * the Internet community; see the CONTRIBUTORS file for full
20 * details. Many organizations have provided support for Squid's
21 * development; see the SPONSORS file for full details. Squid is
22 * Copyrighted (C) 2001 by the Regents of the University of
23 * California; see the COPYRIGHT file for full details. Squid
24 * incorporates software developed and/or copyrighted by other
25 * sources; see the CREDITS file for full details.
26 *
27 * This program is free software; you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation; either version 2 of the License, or
30 * (at your option) any later version.
26ac0430 31 *
d9572179 32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
26ac0430 36 *
d9572179 37 * You should have received a copy of the GNU General Public License
38 * along with this program; if not, write to the Free Software
39 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
40 *
41 */
42
f7f3304a 43#include "squid-old.h"
8822ebee 44#include "mgr/Registration.h"
225b7b10 45#include "ExternalACL.h"
1e5562e3 46#include "ExternalACLEntry.h"
2f1431ea
AJ
47#if USE_AUTH
48#include "auth/Acl.h"
49#include "auth/Gadgets.h"
2d2b0bb7 50#include "auth/UserRequest.h"
2f1431ea 51#endif
985c86bc 52#include "SquidTime.h"
e6ccf245 53#include "Store.h"
ecb04c8c 54#include "fde.h"
c0941a6a
AR
55#include "acl/FilledChecklist.h"
56#include "acl/Acl.h"
3841dd46 57#if USE_IDENT
4daaf3cb 58#include "ident/AclIdent.h"
3841dd46 59#endif
055421ee 60#include "ip/tools.h"
a46d2c0e 61#include "client_side.h"
5c336a3b 62#include "comm/Connection.h"
a2ac85d9 63#include "HttpRequest.h"
7b0ca1e8 64#include "HttpReply.h"
aa839030 65#include "helper.h"
0eb49b6d 66#include "MemBuf.h"
1fa9b1a7 67#include "rfc1738.h"
985c86bc 68#include "URLScheme.h"
d295d770 69#include "wordlist.h"
4db984be
CT
70#if USE_SSL
71#include "ssl/support.h"
72#endif
d9572179 73
74#ifndef DEFAULT_EXTERNAL_ACL_TTL
75#define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
76#endif
d4f2f353 77#ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
78#define DEFAULT_EXTERNAL_ACL_CHILDREN 5
d9572179 79#endif
80
81typedef struct _external_acl_format external_acl_format;
62e76326 82
c0941a6a 83static char *makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data);
d9572179 84static void external_acl_cache_delete(external_acl * def, external_acl_entry * entry);
85static int external_acl_entry_expired(external_acl * def, external_acl_entry * entry);
47b0c1fa 86static int external_acl_grace_expired(external_acl * def, external_acl_entry * entry);
d9572179 87static void external_acl_cache_touch(external_acl * def, external_acl_entry * entry);
1e5562e3 88static external_acl_entry *external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
d9572179 89
90/******************************************************************
91 * external_acl directive
92 */
62e76326 93
1e5562e3 94class external_acl
62e76326 95{
1e5562e3 96
97public:
d9572179 98 external_acl *next;
1e5562e3 99
6ca34f6f 100 void add(ExternalACLEntry *);
1e5562e3 101
102 void trimCache();
103
d9572179 104 int ttl;
1e5562e3 105
d9572179 106 int negative_ttl;
1e5562e3 107
47b0c1fa 108 int grace;
109
d9572179 110 char *name;
1e5562e3 111
d9572179 112 external_acl_format *format;
1e5562e3 113
d9572179 114 wordlist *cmdline;
1e5562e3 115
48d54e4d 116 HelperChildConfig children;
07eca7e0 117
e6ccf245 118 helper *theHelper;
1e5562e3 119
d9572179 120 hash_table *cache;
1e5562e3 121
d9572179 122 dlink_list lru_list;
1e5562e3 123
d9572179 124 int cache_size;
1e5562e3 125
d9572179 126 int cache_entries;
1e5562e3 127
d9572179 128 dlink_list queue;
1e5562e3 129
2f1431ea 130#if USE_AUTH
3265364b
AJ
131 /**
132 * Configuration flag. May only be altered by the configuration parser.
133 *
134 * Indicates that all uses of this external_acl_type helper require authentication
135 * details to be processed. If none are available its a fail match.
136 */
e870b1f8 137 bool require_auth;
2f1431ea 138#endif
dc1af3cf 139
26ac0430 140 enum {
dc1af3cf 141 QUOTE_METHOD_SHELL = 1,
142 QUOTE_METHOD_URL
2fadd50d 143 } quote;
cc192b50 144
b7ac5457 145 Ip::Address local_addr;
d9572179 146};
147
26ac0430 148struct _external_acl_format {
4ecc6631 149 enum format_type {
62e76326 150 EXT_ACL_UNKNOWN,
2f1431ea 151#if USE_AUTH
62e76326 152 EXT_ACL_LOGIN,
2f1431ea 153#endif
7a16973f 154#if USE_IDENT
62e76326 155 EXT_ACL_IDENT,
7a16973f 156#endif
62e76326 157 EXT_ACL_SRC,
47b0c1fa 158 EXT_ACL_SRCPORT,
a98c2da5
AJ
159#if USE_SQUID_EUI
160 EXT_ACL_SRCEUI48,
161 EXT_ACL_SRCEUI64,
162#endif
47b0c1fa 163 EXT_ACL_MYADDR,
164 EXT_ACL_MYPORT,
3cf6e9c5 165 EXT_ACL_URI,
62e76326 166 EXT_ACL_DST,
167 EXT_ACL_PROTO,
168 EXT_ACL_PORT,
169 EXT_ACL_PATH,
170 EXT_ACL_METHOD,
7b0ca1e8
AJ
171
172 EXT_ACL_HEADER_REQUEST,
173 EXT_ACL_HEADER_REQUEST_MEMBER,
174 EXT_ACL_HEADER_REQUEST_ID,
175 EXT_ACL_HEADER_REQUEST_ID_MEMBER,
176
177 EXT_ACL_HEADER_REPLY,
178 EXT_ACL_HEADER_REPLY_MEMBER,
179 EXT_ACL_HEADER_REPLY_ID,
180 EXT_ACL_HEADER_REPLY_ID_MEMBER,
181
a7ad6e4e 182#if USE_SSL
62e76326 183 EXT_ACL_USER_CERT,
184 EXT_ACL_CA_CERT,
4ac9968f 185 EXT_ACL_USER_CERT_RAW,
3d61c476 186 EXT_ACL_USER_CERTCHAIN_RAW,
a7ad6e4e 187#endif
2f1431ea 188#if USE_AUTH
abb929f0 189 EXT_ACL_EXT_USER,
2f1431ea 190#endif
99e4ad67
JB
191 EXT_ACL_EXT_LOG,
192 EXT_ACL_TAG,
0db8942f 193 EXT_ACL_PERCENT,
62e76326 194 EXT_ACL_END
d9572179 195 } type;
196 external_acl_format *next;
197 char *header;
198 char *member;
199 char separator;
200 http_hdr_type header_id;
201};
202
203/* FIXME: These are not really cbdata, but it is an easy way
204 * to get them pooled, refcounted, accounted and freed properly...
205 */
206CBDATA_TYPE(external_acl);
207CBDATA_TYPE(external_acl_format);
208
209static void
210free_external_acl_format(void *data)
211{
e6ccf245 212 external_acl_format *p = static_cast<external_acl_format *>(data);
d9572179 213 safe_free(p->header);
214}
215
216static void
217free_external_acl(void *data)
218{
e6ccf245 219 external_acl *p = static_cast<external_acl *>(data);
d9572179 220 safe_free(p->name);
62e76326 221
d9572179 222 while (p->format) {
62e76326 223 external_acl_format *f = p->format;
224 p->format = f->next;
225 cbdataFree(f);
d9572179 226 }
62e76326 227
d9572179 228 wordlistDestroy(&p->cmdline);
62e76326 229
e6ccf245 230 if (p->theHelper) {
62e76326 231 helperShutdown(p->theHelper);
48d54e4d 232 delete p->theHelper;
62e76326 233 p->theHelper = NULL;
d9572179 234 }
62e76326 235
d9572179 236 while (p->lru_list.tail)
62e76326 237 external_acl_cache_delete(p, static_cast<external_acl_entry *>(p->lru_list.tail->data));
d9572179 238 if (p->cache)
62e76326 239 hashFreeMemory(p->cache);
d9572179 240}
241
7b0ca1e8
AJ
242/**
243 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
244 * request or reply header to external helper.
245 *
246 \param header - the token being parsed (without the identifying prefix)
247 \param type - format enum identifier for this element, pulled from identifying prefix
248 \param format - structure to contain all the info about this format element.
249 */
250void
4ecc6631 251parse_header_token(external_acl_format *format, char *header, const _external_acl_format::format_type type)
7b0ca1e8
AJ
252{
253 /* header format */
254 char *member, *end;
255
256 /** Cut away the closing brace */
257 end = strchr(header, '}');
258 if (end && strlen(end) == 1)
259 *end = '\0';
260 else
261 self_destruct();
262
263 member = strchr(header, ':');
264
265 if (member) {
266 /* Split in header and member */
267 *member++ = '\0';
268
269 if (!xisalnum(*member))
270 format->separator = *member++;
271 else
272 format->separator = ',';
273
274 format->member = xstrdup(member);
275
26ac0430 276 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
7b0ca1e8
AJ
277 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
278 else
279 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
280 } else {
281 format->type = type;
282 }
283
284 format->header = xstrdup(header);
285 format->header_id = httpHeaderIdByNameDef(header, strlen(header));
286
287 if (format->header_id != -1) {
288 if (member) {
26ac0430 289 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
7b0ca1e8
AJ
290 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER;
291 else
292 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER;
293 } else {
26ac0430 294 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
7b0ca1e8
AJ
295 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID;
296 else
297 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID;
298 }
299 }
300}
301
d9572179 302void
303parse_externalAclHelper(external_acl ** list)
304{
305 external_acl *a;
306 char *token;
307 external_acl_format **p;
308
309 CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
310 CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
311
312 a = cbdataAlloc(external_acl);
313
cc192b50 314 /* set defaults */
d9572179 315 a->ttl = DEFAULT_EXTERNAL_ACL_TTL;
316 a->negative_ttl = -1;
84f795a5 317 a->cache_size = 256*1024;
48d54e4d
AJ
318 a->children.n_max = DEFAULT_EXTERNAL_ACL_CHILDREN;
319 a->children.n_startup = a->children.n_max;
404cfda1 320 a->children.n_idle = 1;
cc192b50 321 a->local_addr.SetLocalhost();
322 a->quote = external_acl::QUOTE_METHOD_URL;
323
d9572179 324 token = strtok(NULL, w_space);
62e76326 325
d9572179 326 if (!token)
62e76326 327 self_destruct();
328
d9572179 329 a->name = xstrdup(token);
330
331 token = strtok(NULL, w_space);
62e76326 332
d9572179 333 /* Parse options */
334 while (token) {
62e76326 335 if (strncmp(token, "ttl=", 4) == 0) {
336 a->ttl = atoi(token + 4);
337 } else if (strncmp(token, "negative_ttl=", 13) == 0) {
338 a->negative_ttl = atoi(token + 13);
07eca7e0 339 } else if (strncmp(token, "children=", 9) == 0) {
48d54e4d
AJ
340 a->children.n_max = atoi(token + 9);
341 debugs(0, 0, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
342 } else if (strncmp(token, "children-max=", 13) == 0) {
343 a->children.n_max = atoi(token + 13);
344 } else if (strncmp(token, "children-startup=", 17) == 0) {
345 a->children.n_startup = atoi(token + 17);
346 } else if (strncmp(token, "children-idle=", 14) == 0) {
347 a->children.n_idle = atoi(token + 14);
07eca7e0 348 } else if (strncmp(token, "concurrency=", 12) == 0) {
48d54e4d 349 a->children.concurrency = atoi(token + 12);
62e76326 350 } else if (strncmp(token, "cache=", 6) == 0) {
351 a->cache_size = atoi(token + 6);
47b0c1fa 352 } else if (strncmp(token, "grace=", 6) == 0) {
353 a->grace = atoi(token + 6);
dc1af3cf 354 } else if (strcmp(token, "protocol=2.5") == 0) {
355 a->quote = external_acl::QUOTE_METHOD_SHELL;
356 } else if (strcmp(token, "protocol=3.0") == 0) {
357 a->quote = external_acl::QUOTE_METHOD_URL;
358 } else if (strcmp(token, "quote=url") == 0) {
359 a->quote = external_acl::QUOTE_METHOD_URL;
360 } else if (strcmp(token, "quote=shell") == 0) {
361 a->quote = external_acl::QUOTE_METHOD_SHELL;
cc192b50 362
26ac0430
AJ
363 /* INET6: allow admin to configure some helpers explicitly to
364 bind to IPv4/v6 localhost port. */
cc192b50 365 } else if (strcmp(token, "ipv4") == 0) {
26ac0430 366 if ( !a->local_addr.SetIPv4() ) {
cc192b50 367 debugs(3, 0, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
368 }
369 } else if (strcmp(token, "ipv6") == 0) {
055421ee
AJ
370 if (!Ip::EnableIpv6)
371 debugs(3, 0, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
372 // else nothing to do.
62e76326 373 } else {
374 break;
375 }
376
377 token = strtok(NULL, w_space);
d9572179 378 }
62e76326 379
6f78f0ed 380 /* check that child startup value is sane. */
a3cecd6c
AJ
381 if (a->children.n_startup > a->children.n_max)
382 a->children.n_startup = a->children.n_max;
48d54e4d 383
6f78f0ed 384 /* check that child idle value is sane. */
a3cecd6c
AJ
385 if (a->children.n_idle > a->children.n_max)
386 a->children.n_idle = a->children.n_max;
387 if (a->children.n_idle < 1)
388 a->children.n_idle = 1;
48d54e4d 389
d9572179 390 if (a->negative_ttl == -1)
62e76326 391 a->negative_ttl = a->ttl;
d9572179 392
393 /* Parse format */
394 p = &a->format;
62e76326 395
d9572179 396 while (token) {
62e76326 397 external_acl_format *format;
398
399 /* stop on first non-format token found */
400
401 if (*token != '%')
402 break;
403
404 format = cbdataAlloc(external_acl_format);
405
406 if (strncmp(token, "%{", 2) == 0) {
7b0ca1e8
AJ
407 // deprecated. but assume the old configs all referred to request headers.
408 debugs(82, DBG_IMPORTANT, "WARNING: external_acl_type format %{...} is being replaced by %>{...} for : " << token);
4ecc6631 409 parse_header_token(format, (token+2), _external_acl_format::EXT_ACL_HEADER_REQUEST);
c68c9682 410 } else if (strncmp(token, "%>{", 3) == 0) {
4ecc6631 411 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REQUEST);
c68c9682 412 } else if (strncmp(token, "%<{", 3) == 0) {
4ecc6631 413 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REPLY);
2f1431ea 414#if USE_AUTH
62e76326 415 } else if (strcmp(token, "%LOGIN") == 0) {
416 format->type = _external_acl_format::EXT_ACL_LOGIN;
e870b1f8 417 a->require_auth = true;
2f1431ea 418#endif
62e76326 419 }
420
7a16973f 421#if USE_IDENT
62e76326 422 else if (strcmp(token, "%IDENT") == 0)
423 format->type = _external_acl_format::EXT_ACL_IDENT;
424
7a16973f 425#endif
62e76326 426
427 else if (strcmp(token, "%SRC") == 0)
428 format->type = _external_acl_format::EXT_ACL_SRC;
47b0c1fa 429 else if (strcmp(token, "%SRCPORT") == 0)
430 format->type = _external_acl_format::EXT_ACL_SRCPORT;
a98c2da5
AJ
431#if USE_SQUID_EUI
432 else if (strcmp(token, "%SRCEUI48") == 0)
433 format->type = _external_acl_format::EXT_ACL_SRCEUI48;
434 else if (strcmp(token, "%SRCEUI64") == 0)
435 format->type = _external_acl_format::EXT_ACL_SRCEUI64;
436#endif
47b0c1fa 437 else if (strcmp(token, "%MYADDR") == 0)
438 format->type = _external_acl_format::EXT_ACL_MYADDR;
439 else if (strcmp(token, "%MYPORT") == 0)
440 format->type = _external_acl_format::EXT_ACL_MYPORT;
3cf6e9c5 441 else if (strcmp(token, "%URI") == 0)
442 format->type = _external_acl_format::EXT_ACL_URI;
62e76326 443 else if (strcmp(token, "%DST") == 0)
444 format->type = _external_acl_format::EXT_ACL_DST;
445 else if (strcmp(token, "%PROTO") == 0)
446 format->type = _external_acl_format::EXT_ACL_PROTO;
447 else if (strcmp(token, "%PORT") == 0)
448 format->type = _external_acl_format::EXT_ACL_PORT;
449 else if (strcmp(token, "%PATH") == 0)
450 format->type = _external_acl_format::EXT_ACL_PATH;
451 else if (strcmp(token, "%METHOD") == 0)
452 format->type = _external_acl_format::EXT_ACL_METHOD;
453
a7ad6e4e 454#if USE_SSL
4ac9968f 455 else if (strcmp(token, "%USER_CERT") == 0)
3de409f0 456 format->type = _external_acl_format::EXT_ACL_USER_CERT_RAW;
3d61c476 457 else if (strcmp(token, "%USER_CERTCHAIN") == 0)
3de409f0 458 format->type = _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW;
c68c9682 459 else if (strncmp(token, "%USER_CERT_", 11) == 0) {
62e76326 460 format->type = _external_acl_format::EXT_ACL_USER_CERT;
461 format->header = xstrdup(token + 11);
c68c9682 462 } else if (strncmp(token, "%CA_CERT_", 11) == 0) {
62e76326 463 format->type = _external_acl_format::EXT_ACL_USER_CERT;
464 format->header = xstrdup(token + 11);
465 }
a7ad6e4e 466#endif
2f1431ea 467#if USE_AUTH
abb929f0 468 else if (strcmp(token, "%EXT_USER") == 0)
469 format->type = _external_acl_format::EXT_ACL_EXT_USER;
2f1431ea 470#endif
99e4ad67
JB
471 else if (strcmp(token, "%EXT_LOG") == 0)
472 format->type = _external_acl_format::EXT_ACL_EXT_LOG;
473 else if (strcmp(token, "%TAG") == 0)
474 format->type = _external_acl_format::EXT_ACL_TAG;
0db8942f
AJ
475 else if (strcmp(token, "%%") == 0)
476 format->type = _external_acl_format::EXT_ACL_PERCENT;
62e76326 477 else {
a98c2da5 478 debugs(0,0, "ERROR: Unknown Format token " << token);
62e76326 479 self_destruct();
480 }
481
482 *p = format;
483 p = &format->next;
484 token = strtok(NULL, w_space);
d9572179 485 }
486
487 /* There must be at least one format token */
488 if (!a->format)
62e76326 489 self_destruct();
d9572179 490
491 /* helper */
492 if (!token)
62e76326 493 self_destruct();
494
d9572179 495 wordlistAdd(&a->cmdline, token);
496
497 /* arguments */
a336d130 498 parse_wordlist(&a->cmdline);
d9572179 499
500 while (*list)
62e76326 501 list = &(*list)->next;
502
d9572179 503 *list = a;
504}
505
506void
507dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
508{
509 const external_acl *node;
510 const external_acl_format *format;
511 const wordlist *word;
62e76326 512
d9572179 513 for (node = list; node; node = node->next) {
62e76326 514 storeAppendPrintf(sentry, "%s %s", name, node->name);
515
cc192b50 516 if (!node->local_addr.IsIPv6())
517 storeAppendPrintf(sentry, " ipv4");
518 else
519 storeAppendPrintf(sentry, " ipv6");
520
62e76326 521 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
522 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
523
524 if (node->negative_ttl != node->ttl)
525 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
526
47b0c1fa 527 if (node->grace)
528 storeAppendPrintf(sentry, " grace=%d", node->grace);
529
48d54e4d
AJ
530 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
531 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
62e76326 532
48d54e4d
AJ
533 if (node->children.n_startup != 1)
534 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
62e76326 535
48d54e4d
AJ
536 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
537 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
538
539 if (node->children.concurrency)
540 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
07eca7e0 541
47b0c1fa 542 if (node->cache)
543 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
544
62e76326 545 for (format = node->format; format; format = format->next) {
546 switch (format->type) {
547
4ecc6631 548 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
4ecc6631 549 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
2170db3e 550 storeAppendPrintf(sentry, " %%>{%s}", format->header);
62e76326 551 break;
552
4ecc6631 553 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
4ecc6631 554 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
2170db3e
AJ
555 storeAppendPrintf(sentry, " %%>{%s:%s}", format->header, format->member);
556 break;
557
558 case _external_acl_format::EXT_ACL_HEADER_REPLY:
559 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
560 storeAppendPrintf(sentry, " %%<{%s}", format->header);
561 break;
562
563 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
564 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
565 storeAppendPrintf(sentry, " %%<{%s:%s}", format->header, format->member);
62e76326 566 break;
d9572179 567#define DUMP_EXT_ACL_TYPE(a) \
dc1af3cf 568 case _external_acl_format::EXT_ACL_##a: \
569 storeAppendPrintf(sentry, " %%%s", #a); \
570 break
2f1431ea 571#if USE_AUTH
62e76326 572 DUMP_EXT_ACL_TYPE(LOGIN);
2f1431ea 573#endif
8f45b34c 574#if USE_IDENT
62e76326 575
576 DUMP_EXT_ACL_TYPE(IDENT);
8f45b34c 577#endif
62e76326 578
579 DUMP_EXT_ACL_TYPE(SRC);
47b0c1fa 580 DUMP_EXT_ACL_TYPE(SRCPORT);
a98c2da5
AJ
581#if USE_SQUID_EUI
582 DUMP_EXT_ACL_TYPE(SRCEUI48);
583 DUMP_EXT_ACL_TYPE(SRCEUI64);
584#endif
585
47b0c1fa 586 DUMP_EXT_ACL_TYPE(MYADDR);
587 DUMP_EXT_ACL_TYPE(MYPORT);
3cf6e9c5 588 DUMP_EXT_ACL_TYPE(URI);
62e76326 589 DUMP_EXT_ACL_TYPE(DST);
590 DUMP_EXT_ACL_TYPE(PROTO);
591 DUMP_EXT_ACL_TYPE(PORT);
592 DUMP_EXT_ACL_TYPE(PATH);
593 DUMP_EXT_ACL_TYPE(METHOD);
1f183752 594#if USE_SSL
62e76326 595
4ac9968f 596 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
597 storeAppendPrintf(sentry, " %%USER_CERT");
598 break;
599
3d61c476 600 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
601 storeAppendPrintf(sentry, " %%USER_CERTCHAIN");
602 break;
603
62e76326 604 case _external_acl_format::EXT_ACL_USER_CERT:
605 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
606 break;
607
608 case _external_acl_format::EXT_ACL_CA_CERT:
609 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
610 break;
1f183752 611#endif
2f1431ea 612#if USE_AUTH
abb929f0 613 DUMP_EXT_ACL_TYPE(EXT_USER);
2f1431ea 614#endif
99e4ad67
JB
615 DUMP_EXT_ACL_TYPE(EXT_LOG);
616 DUMP_EXT_ACL_TYPE(TAG);
4ecc6631 617 default:
62e76326 618 fatal("unknown external_acl format error");
619 break;
620 }
621 }
622
623 for (word = node->cmdline; word; word = word->next)
624 storeAppendPrintf(sentry, " %s", word->key);
625
626 storeAppendPrintf(sentry, "\n");
d9572179 627 }
628}
629
630void
631free_externalAclHelper(external_acl ** list)
632{
633 while (*list) {
62e76326 634 external_acl *node = *list;
635 *list = node->next;
636 node->next = NULL;
637 cbdataFree(node);
d9572179 638 }
639}
640
641static external_acl *
642find_externalAclHelper(const char *name)
643{
644 external_acl *node;
645
646 for (node = Config.externalAclHelperList; node; node = node->next) {
62e76326 647 if (strcmp(node->name, name) == 0)
648 return node;
d9572179 649 }
62e76326 650
d9572179 651 return NULL;
652}
653
1e5562e3 654void
6ca34f6f 655external_acl::add(ExternalACLEntry *anEntry)
1e5562e3 656{
657 trimCache();
658 assert (anEntry->def == NULL);
659 anEntry->def = this;
660 hash_join(cache, anEntry);
661 dlinkAdd(anEntry, &anEntry->lru, &lru_list);
662 cache_entries++;
663}
664
665void
666external_acl::trimCache()
667{
668 if (cache_size && cache_entries >= cache_size)
669 external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
670}
671
d9572179 672
673/******************************************************************
674 * external acl type
675 */
676
26ac0430 677struct _external_acl_data {
d9572179 678 external_acl *def;
679 wordlist *arguments;
680};
681
682CBDATA_TYPE(external_acl_data);
683static void
684free_external_acl_data(void *data)
685{
e6ccf245 686 external_acl_data *p = static_cast<external_acl_data *>(data);
d9572179 687 wordlistDestroy(&p->arguments);
688 cbdataReferenceDone(p->def);
689}
690
691void
b0dd28ba 692ACLExternal::parse()
d9572179 693{
d9572179 694 char *token;
62e76326 695
b0dd28ba 696 if (data)
62e76326 697 self_destruct();
698
d9572179 699 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
62e76326 700
d9572179 701 data = cbdataAlloc(external_acl_data);
62e76326 702
d9572179 703 token = strtok(NULL, w_space);
62e76326 704
d9572179 705 if (!token)
62e76326 706 self_destruct();
707
d9572179 708 data->def = cbdataReference(find_externalAclHelper(token));
62e76326 709
d9572179 710 if (!data->def)
62e76326 711 self_destruct();
712
4d091667 713 while ((token = strtokFile())) {
62e76326 714 wordlistAdd(&data->arguments, token);
d9572179 715 }
d9572179 716}
717
4b0f5de8 718bool
719ACLExternal::valid () const
720{
2f1431ea 721#if USE_AUTH
4b0f5de8 722 if (data->def->require_auth) {
723 if (authenticateSchemeCount() == 0) {
bf8fe701 724 debugs(28, 0, "Can't use proxy auth because no authentication schemes were compiled.");
4b0f5de8 725 return false;
726 }
727
728 if (authenticateActiveSchemeCount() == 0) {
bf8fe701 729 debugs(28, 0, "Can't use proxy auth because no authentication schemes are fully configured.");
4b0f5de8 730 return false;
731 }
732 }
2f1431ea 733#endif
4b0f5de8 734
735 return true;
736}
737
738bool
739ACLExternal::empty () const
740{
741 return false;
742}
743
b0dd28ba 744ACLExternal::~ACLExternal()
d9572179 745{
b0dd28ba 746 cbdataFree(data);
747 safe_free (class_);
d9572179 748}
749
1abe0161
AJ
750static void
751copyResultsFromEntry(HttpRequest *req, external_acl_entry *entry)
752{
753 if (req) {
754#if USE_AUTH
755 if (entry->user.size())
756 req->extacl_user = entry->user;
757
758 if (entry->password.size())
759 req->extacl_passwd = entry->password;
760#endif
761 if (!req->tag.size())
762 req->tag = entry->tag;
763
764 if (entry->log.size())
765 req->extacl_log = entry->log;
766
767 if (entry->message.size())
768 req->extacl_message = entry->message;
769 }
770}
771
e5f825ea 772static allow_t
c0941a6a 773aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
d9572179 774{
d9572179 775 const char *key = "";
c69ce640 776 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
e5f825ea 777 external_acl_entry *entry = ch->extacl_entry;
62e76326 778
d9572179 779 if (entry) {
c69ce640 780 if (cbdataReferenceValid(entry) && entry->def == acl->def) {
ad06254e
AJ
781 /* Ours, use it.. if the key matches */
782 key = makeExternalAclKey(ch, acl);
783 if (strcmp(key, (char*)entry->key) != 0) {
784 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
785 // too bad. need a new lookup.
786 cbdataReferenceDone(ch->extacl_entry);
787 entry = NULL;
788 }
62e76326 789 } else {
790 /* Not valid, or not ours.. get rid of it */
c69ce640
AJ
791 debugs(82, 9, HERE << "entry " << entry << " not valid or not ours. Discarded.");
792 if (entry) {
793 debugs(82, 9, HERE << "entry def=" << entry->def << ", our def=" << acl->def);
794 key = makeExternalAclKey(ch, acl);
795 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "'");
796 }
62e76326 797 cbdataReferenceDone(ch->extacl_entry);
798 entry = NULL;
799 }
d9572179 800 }
62e76326 801
4a972fa2 802 external_acl_message = "MISSING REQUIRED INFORMATION";
803
d9572179 804 if (!entry) {
c69ce640 805 debugs(82, 9, HERE << "No helper entry available");
2f1431ea 806#if USE_AUTH
62e76326 807 if (acl->def->require_auth) {
62e76326 808 /* Make sure the user is authenticated */
e5f825ea 809 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
e0f7153c
AR
810 const allow_t ti = AuthenticateAcl(ch);
811 if (ti != ACCESS_ALLOWED) {
e5f825ea 812 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
e0f7153c 813 return ti;
62e76326 814 }
e5f825ea 815 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
62e76326 816 }
2f1431ea 817#endif
62e76326 818 key = makeExternalAclKey(ch, acl);
41218131 819
820 if (!key) {
821 /* Not sufficient data to process */
e5f825ea 822 return ACCESS_DUNNO;
41218131 823 }
824
62e76326 825 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
826
e0f7153c
AR
827 external_acl_entry *staleEntry = entry;
828 if (entry && external_acl_entry_expired(acl->def, entry))
829 entry = NULL;
830
831 if (entry && external_acl_grace_expired(acl->def, entry)) {
832 // refresh in the background
833 ExternalACLLookup::Start(ch, acl, true);
834 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
835 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
836 }
837
838 if (!entry) {
e5f825ea
AJ
839 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
840 debugs(82, 2, HERE << "\"" << key << "\": entry=@" <<
bf8fe701 841 entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
95a83c22 842
48d54e4d 843 if (acl->def->theHelper->stats.queue_size <= (int)acl->def->theHelper->childs.n_active) {
e5f825ea 844 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
dc1839a5 845 ch->changeState(ExternalACLLookup::Instance());
e5f825ea 846 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
e0f7153c 847 return ACCESS_DUNNO; // expired cached or simply absent entry
47b0c1fa 848 } else {
e0f7153c 849 if (!staleEntry) {
e5f825ea 850 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 851 "' queue overload. Request rejected '" << key << "'.");
4a972fa2 852 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
e5f825ea 853 return ACCESS_DUNNO;
47b0c1fa 854 } else {
e5f825ea 855 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 856 "' queue overload. Using stale result. '" << key << "'.");
e0f7153c 857 entry = staleEntry;
47b0c1fa 858 /* Fall thru to processing below */
859 }
860 }
861 }
d9572179 862 }
62e76326 863
e0f7153c
AR
864 debugs(82, 4, HERE << "entry = { date=" <<
865 (long unsigned int) entry->date <<
866 ", result=" << entry->result <<
867 " tag=" << entry->tag <<
868 " log=" << entry->log << " }");
869#if USE_AUTH
870 debugs(82, 4, HERE << "entry user=" << entry->user);
871#endif
872
d9572179 873 external_acl_cache_touch(acl->def, entry);
a7a42b14 874 external_acl_message = entry->message.termedBuf();
4a972fa2 875
e5f825ea 876 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
1abe0161 877 copyResultsFromEntry(ch->request, entry);
e5f825ea 878 return entry->result;
d9572179 879}
880
b0dd28ba 881int
882ACLExternal::match(ACLChecklist *checklist)
883{
e5f825ea 884 allow_t answer = aclMatchExternal(data, Filled(checklist));
e5f825ea
AJ
885
886 // convert to tri-state ACL match 1,0,-1
9b831d57 887 switch (answer) {
e5f825ea 888 case ACCESS_ALLOWED:
e5f825ea
AJ
889 return 1; // match
890
891 case ACCESS_DENIED:
e5f825ea
AJ
892 return 0; // non-match
893
894 case ACCESS_DUNNO:
895 case ACCESS_AUTH_REQUIRED:
896 default:
e0f7153c
AR
897 // If the answer is not allowed or denied (matches/not matches) and
898 // async authentication is not needed (asyncNeeded), then we are done.
899 if (!checklist->asyncNeeded())
900 checklist->markFinished(answer, "aclMatchExternal exception");
e5f825ea
AJ
901 return -1; // other
902 }
b0dd28ba 903}
904
d9572179 905wordlist *
b0dd28ba 906ACLExternal::dump() const
d9572179 907{
b0dd28ba 908 external_acl_data const *acl = data;
d9572179 909 wordlist *result = NULL;
910 wordlist *arg;
911 MemBuf mb;
2fe7eff9 912 mb.init();
913 mb.Printf("%s", acl->def->name);
62e76326 914
d9572179 915 for (arg = acl->arguments; arg; arg = arg->next) {
2fe7eff9 916 mb.Printf(" %s", arg->key);
d9572179 917 }
62e76326 918
d9572179 919 wordlistAdd(&result, mb.buf);
2fe7eff9 920 mb.clean();
d9572179 921 return result;
922}
923
924/******************************************************************
925 * external_acl cache
926 */
927
d9572179 928static void
929external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
930{
c69ce640
AJ
931 // this must not be done when nothing is being cached.
932 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
933 return;
934
d9572179 935 dlinkDelete(&entry->lru, &def->lru_list);
936 dlinkAdd(entry, &entry->lru, &def->lru_list);
937}
938
939static char *
c0941a6a 940makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
d9572179 941{
032785bf 942 static MemBuf mb;
d9572179 943 char buf[256];
944 int first = 1;
945 wordlist *arg;
946 external_acl_format *format;
190154cf 947 HttpRequest *request = ch->request;
7b0ca1e8 948 HttpReply *reply = ch->reply;
2fe7eff9 949 mb.reset();
62e76326 950
d9572179 951 for (format = acl_data->def->format; format; format = format->next) {
62e76326 952 const char *str = NULL;
30abd221 953 String sb;
62e76326 954
955 switch (format->type) {
2f1431ea 956#if USE_AUTH
62e76326 957 case _external_acl_format::EXT_ACL_LOGIN:
d28c476f
AJ
958 // if this ACL line was the cause of credentials fetch
959 // they may not already be in the checklist
960 if (ch->auth_user_request == NULL && ch->request)
961 ch->auth_user_request = ch->request->auth_user_request;
962
963 if (ch->auth_user_request != NULL)
964 str = ch->auth_user_request->username();
62e76326 965 break;
2f1431ea 966#endif
7a16973f 967#if USE_IDENT
62e76326 968 case _external_acl_format::EXT_ACL_IDENT:
969 str = ch->rfc931;
970
41218131 971 if (!str || !*str) {
62e76326 972 ch->changeState(IdentLookup::Instance());
973 return NULL;
974 }
975
976 break;
7a16973f 977#endif
62e76326 978
979 case _external_acl_format::EXT_ACL_SRC:
cc192b50 980 str = ch->src_addr.NtoA(buf,sizeof(buf));
62e76326 981 break;
982
47b0c1fa 983 case _external_acl_format::EXT_ACL_SRCPORT:
cc192b50 984 snprintf(buf, sizeof(buf), "%d", request->client_addr.GetPort());
47b0c1fa 985 str = buf;
986 break;
987
a98c2da5
AJ
988#if USE_SQUID_EUI
989 case _external_acl_format::EXT_ACL_SRCEUI48:
40d34a62
AJ
990 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
991 request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
a98c2da5
AJ
992 str = buf;
993 break;
994
995 case _external_acl_format::EXT_ACL_SRCEUI64:
40d34a62
AJ
996 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
997 request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
a98c2da5
AJ
998 str = buf;
999 break;
1000#endif
1001
47b0c1fa 1002 case _external_acl_format::EXT_ACL_MYADDR:
cc192b50 1003 str = request->my_addr.NtoA(buf, sizeof(buf));
47b0c1fa 1004 break;
1005
1006 case _external_acl_format::EXT_ACL_MYPORT:
cc192b50 1007 snprintf(buf, sizeof(buf), "%d", request->my_addr.GetPort());
47b0c1fa 1008 str = buf;
1009 break;
1010
3cf6e9c5 1011 case _external_acl_format::EXT_ACL_URI:
1012 str = urlCanonical(request);
1013 break;
1014
62e76326 1015 case _external_acl_format::EXT_ACL_DST:
cc192b50 1016 str = request->GetHost();
62e76326 1017 break;
1018
1019 case _external_acl_format::EXT_ACL_PROTO:
0c3d3f65 1020 str = AnyP::ProtocolType_str[request->protocol];
62e76326 1021 break;
1022
1023 case _external_acl_format::EXT_ACL_PORT:
1024 snprintf(buf, sizeof(buf), "%d", request->port);
1025 str = buf;
1026 break;
1027
1028 case _external_acl_format::EXT_ACL_PATH:
a313a9ba 1029 str = request->urlpath.termedBuf();
62e76326 1030 break;
1031
1032 case _external_acl_format::EXT_ACL_METHOD:
60745f24 1033 str = RequestMethodStr(request->method);
62e76326 1034 break;
1035
7b0ca1e8 1036 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
a9925b40 1037 sb = request->header.getByName(format->header);
a313a9ba 1038 str = sb.termedBuf();
62e76326 1039 break;
1040
7b0ca1e8 1041 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
a9925b40 1042 sb = request->header.getStrOrList(format->header_id);
a313a9ba 1043 str = sb.termedBuf();
62e76326 1044 break;
1045
7b0ca1e8 1046 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
a9925b40 1047 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
a313a9ba 1048 str = sb.termedBuf();
62e76326 1049 break;
1050
7b0ca1e8 1051 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
a9925b40 1052 sb = request->header.getListMember(format->header_id, format->member, format->separator);
a313a9ba 1053 str = sb.termedBuf();
62e76326 1054 break;
7b0ca1e8
AJ
1055
1056 case _external_acl_format::EXT_ACL_HEADER_REPLY:
26ac0430 1057 if (reply) {
7b0ca1e8 1058 sb = reply->header.getByName(format->header);
a313a9ba 1059 str = sb.termedBuf();
7b0ca1e8
AJ
1060 }
1061 break;
1062
1063 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
26ac0430 1064 if (reply) {
7b0ca1e8 1065 sb = reply->header.getStrOrList(format->header_id);
a313a9ba 1066 str = sb.termedBuf();
7b0ca1e8
AJ
1067 }
1068 break;
1069
1070 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
26ac0430 1071 if (reply) {
7b0ca1e8 1072 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
a313a9ba 1073 str = sb.termedBuf();
7b0ca1e8
AJ
1074 }
1075 break;
1076
1077 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
26ac0430 1078 if (reply) {
7b0ca1e8 1079 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
a313a9ba 1080 str = sb.termedBuf();
7b0ca1e8
AJ
1081 }
1082 break;
a7ad6e4e 1083#if USE_SSL
62e76326 1084
4ac9968f 1085 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
1086
73c36fd9
AJ
1087 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1088 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
4ac9968f 1089
1090 if (ssl)
1091 str = sslGetUserCertificatePEM(ssl);
1092 }
1093
1094 break;
1095
3d61c476 1096 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
1097
73c36fd9
AJ
1098 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1099 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
3d61c476 1100
1101 if (ssl)
1102 str = sslGetUserCertificateChainPEM(ssl);
1103 }
1104
1105 break;
1106
62e76326 1107 case _external_acl_format::EXT_ACL_USER_CERT:
1108
73c36fd9
AJ
1109 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1110 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
62e76326 1111
1112 if (ssl)
1113 str = sslGetUserAttribute(ssl, format->header);
1114 }
1115
1116 break;
1117
1118 case _external_acl_format::EXT_ACL_CA_CERT:
1119
73c36fd9
AJ
1120 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1121 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
62e76326 1122
1123 if (ssl)
1124 str = sslGetCAAttribute(ssl, format->header);
1125 }
1126
1127 break;
a7ad6e4e 1128#endif
2f1431ea 1129#if USE_AUTH
abb929f0 1130 case _external_acl_format::EXT_ACL_EXT_USER:
a313a9ba 1131 str = request->extacl_user.termedBuf();
abb929f0 1132 break;
2f1431ea 1133#endif
99e4ad67
JB
1134 case _external_acl_format::EXT_ACL_EXT_LOG:
1135 str = request->extacl_log.termedBuf();
1136 break;
1137 case _external_acl_format::EXT_ACL_TAG:
1138 str = request->tag.termedBuf();
1139 break;
0db8942f
AJ
1140 case _external_acl_format::EXT_ACL_PERCENT:
1141 str = "%";
1142 break;
62e76326 1143 case _external_acl_format::EXT_ACL_UNKNOWN:
1144
1145 case _external_acl_format::EXT_ACL_END:
1146 fatal("unknown external_acl format error");
1147 break;
1148 }
1149
1150 if (str)
1151 if (!*str)
1152 str = NULL;
1153
1154 if (!str)
1155 str = "-";
1156
1157 if (!first)
2fe7eff9 1158 mb.append(" ", 1);
62e76326 1159
dc1af3cf 1160 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1161 const char *quoted = rfc1738_escape(str);
2fe7eff9 1162 mb.append(quoted, strlen(quoted));
dc1af3cf 1163 } else {
1164 strwordquote(&mb, str);
1165 }
62e76326 1166
30abd221 1167 sb.clean();
62e76326 1168
1169 first = 0;
d9572179 1170 }
62e76326 1171
d9572179 1172 for (arg = acl_data->arguments; arg; arg = arg->next) {
62e76326 1173 if (!first)
2fe7eff9 1174 mb.append(" ", 1);
62e76326 1175
dc1af3cf 1176 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1177 const char *quoted = rfc1738_escape(arg->key);
2fe7eff9 1178 mb.append(quoted, strlen(quoted));
dc1af3cf 1179 } else {
1180 strwordquote(&mb, arg->key);
1181 }
62e76326 1182
1183 first = 0;
d9572179 1184 }
62e76326 1185
d9572179 1186 return mb.buf;
d9572179 1187}
1188
1189static int
1190external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1191{
c69ce640
AJ
1192 if (def->cache_size <= 0)
1193 return 1;
1194
d9572179 1195 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
62e76326 1196 return 1;
d9572179 1197 else
62e76326 1198 return 0;
d9572179 1199}
62e76326 1200
47b0c1fa 1201static int
1202external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1203{
c69ce640
AJ
1204 if (def->cache_size <= 0)
1205 return 1;
1206
47b0c1fa 1207 int ttl;
1208 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1209 ttl = (ttl * (100 - def->grace)) / 100;
1210
2e1359a0 1211 if (entry->date + ttl <= squid_curtime)
47b0c1fa 1212 return 1;
1213 else
1214 return 0;
1215}
1216
d9572179 1217static external_acl_entry *
1e5562e3 1218external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
d9572179 1219{
c69ce640
AJ
1220 ExternalACLEntry *entry;
1221
1222 // do not bother caching this result if TTL is going to expire it immediately
1223 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
1224 debugs(82,6, HERE);
1225 entry = new ExternalACLEntry;
1226 entry->key = xstrdup(key);
1227 entry->update(data);
1228 entry->def = def;
1229 return entry;
1230 }
1231
1232 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
bf8fe701 1233 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
62e76326 1234
d9572179 1235 if (entry) {
bf8fe701 1236 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
c69ce640 1237 entry->update(data);
62e76326 1238 external_acl_cache_touch(def, entry);
1239
1240 return entry;
d9572179 1241 }
62e76326 1242
1e5562e3 1243 entry = new ExternalACLEntry;
4a8b20e8 1244 entry->key = xstrdup(key);
c69ce640 1245 entry->update(data);
1e5562e3 1246
6ca34f6f 1247 def->add(entry);
1e5562e3 1248
d9572179 1249 return entry;
1250}
1251
1252static void
1253external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1254{
c69ce640 1255 assert(def->cache_size > 0 && entry->def == def);
4a8b20e8 1256 hash_remove_link(def->cache, entry);
d9572179 1257 dlinkDelete(&entry->lru, &def->lru_list);
1258 def->cache_entries -= 1;
00d77d6b 1259 delete entry;
d9572179 1260}
1261
1262/******************************************************************
1263 * external_acl helpers
1264 */
1265
1266typedef struct _externalAclState externalAclState;
62e76326 1267
26ac0430 1268struct _externalAclState {
d9572179 1269 EAH *callback;
1270 void *callback_data;
1271 char *key;
1272 external_acl *def;
1273 dlink_node list;
1274 externalAclState *queue;
1275};
1276
1277CBDATA_TYPE(externalAclState);
1278static void
1279free_externalAclState(void *data)
1280{
e6ccf245 1281 externalAclState *state = static_cast<externalAclState *>(data);
d9572179 1282 safe_free(state->key);
1283 cbdataReferenceDone(state->callback_data);
1284 cbdataReferenceDone(state->def);
1285}
1286
d9572179 1287/*
1288 * The helper program receives queries on stdin, one
07eca7e0 1289 * per line, and must return the result on on stdout
d9572179 1290 *
1291 * General result syntax:
1292 *
1293 * OK/ERR keyword=value ...
1294 *
1295 * Keywords:
1296 *
4a972fa2 1297 * user= The users name (login)
1298 * message= Message describing the reason
1299 * tag= A string tag to be applied to the request that triggered the acl match.
1300 * applies to both OK and ERR responses.
1301 * Won't override existing request tags.
1302 * log= A string to be used in access logging
d9572179 1303 *
1304 * Other keywords may be added to the protocol later
1305 *
26ac0430
AJ
1306 * value needs to be enclosed in quotes if it may contain whitespace, or
1307 * the whitespace escaped using \ (\ escaping obviously also applies to
d9572179 1308 * any " characters)
1309 */
1310
1311static void
1312externalAclHandleReply(void *data, char *reply)
1313{
e6ccf245 1314 externalAclState *state = static_cast<externalAclState *>(data);
d9572179 1315 externalAclState *next;
d9572179 1316 char *status;
1317 char *token;
1318 char *value;
c69ce640 1319 char *t = NULL;
1e5562e3 1320 ExternalACLEntryData entryData;
e5f825ea 1321 entryData.result = ACCESS_DENIED;
466090ac 1322 external_acl_entry *entry = NULL;
d9572179 1323
bf8fe701 1324 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
d9572179 1325
4960475f 1326 if (reply) {
62e76326 1327 status = strwordtok(reply, &t);
1328
1329 if (status && strcmp(status, "OK") == 0)
e5f825ea 1330 entryData.result = ACCESS_ALLOWED;
62e76326 1331
1332 while ((token = strwordtok(NULL, &t))) {
1333 value = strchr(token, '=');
1334
1335 if (value) {
1336 *value++ = '\0'; /* terminate the token, and move up to the value */
1337
dc1af3cf 1338 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
1339 rfc1738_unescape(value);
1340
2f1431ea 1341 if (strcmp(token, "message") == 0)
d7bd27db 1342 entryData.message = value;
62e76326 1343 else if (strcmp(token, "error") == 0)
d7bd27db 1344 entryData.message = value;
abb929f0 1345 else if (strcmp(token, "tag") == 0)
1346 entryData.tag = value;
4a972fa2 1347 else if (strcmp(token, "log") == 0)
1348 entryData.log = value;
2f1431ea
AJ
1349#if USE_AUTH
1350 else if (strcmp(token, "user") == 0)
1351 entryData.user = value;
abb929f0 1352 else if (strcmp(token, "password") == 0)
1353 entryData.password = value;
1354 else if (strcmp(token, "passwd") == 0)
1355 entryData.password = value;
1356 else if (strcmp(token, "login") == 0)
1357 entryData.user = value;
2f1431ea 1358#endif
62e76326 1359 }
1360 }
d9572179 1361 }
62e76326 1362
d9572179 1363 dlinkDelete(&state->list, &state->def->queue);
62e76326 1364
4960475f 1365 if (cbdataReferenceValid(state->def)) {
62e76326 1366 if (reply)
1e5562e3 1367 entry = external_acl_cache_add(state->def, state->key, entryData);
62e76326 1368 else {
2ae2408b 1369 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
62e76326 1370
2ae2408b 1371 if (oldentry)
1372 external_acl_cache_delete(state->def, oldentry);
62e76326 1373 }
466090ac 1374 }
d9572179 1375
1376 do {
62e76326 1377 void *cbdata;
1378 cbdataReferenceDone(state->def);
1379
47b0c1fa 1380 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
62e76326 1381 state->callback(cbdata, entry);
1382
1383 next = state->queue;
d9572179 1384
62e76326 1385 cbdataFree(state);
d9572179 1386
62e76326 1387 state = next;
d9572179 1388 } while (state);
1389}
1390
1391void
e0f7153c 1392ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
d9572179 1393{
e0f7153c
AR
1394 ExternalACLLookup::Start(checklist, me->data, false);
1395}
c8e7608c 1396
e0f7153c
AR
1397void
1398ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
1399{
1400 external_acl *def = acl->def;
1401
c0941a6a 1402 ACLFilledChecklist *ch = Filled(checklist);
c8e7608c 1403 const char *key = makeExternalAclKey(ch, acl);
e0f7153c 1404 assert(key);
62e76326 1405
e0f7153c
AR
1406 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
1407 def->name << "' for '" << key << "'");
62e76326 1408
47b0c1fa 1409 /* Check for a pending lookup to hook into */
c69ce640 1410 // only possible if we are caching results.
e0f7153c 1411 externalAclState *oldstate = NULL;
c69ce640 1412 if (def->cache_size > 0) {
e0f7153c 1413 for (dlink_node *node = def->queue.head; node; node = node->next) {
c69ce640 1414 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
62e76326 1415
c69ce640
AJ
1416 if (strcmp(key, oldstatetmp->key) == 0) {
1417 oldstate = oldstatetmp;
1418 break;
1419 }
47b0c1fa 1420 }
1421 }
62e76326 1422
e0f7153c
AR
1423 // A background refresh has no need to piggiback on a pending request:
1424 // When the pending request completes, the cache will be refreshed anyway.
1425 if (oldstate && inBackground) {
1426 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
47b0c1fa 1427 return;
1428 }
62e76326 1429
e0f7153c 1430 externalAclState *state = cbdataAlloc(externalAclState);
47b0c1fa 1431 state->def = cbdataReference(def);
62e76326 1432
47b0c1fa 1433 state->key = xstrdup(key);
62e76326 1434
e0f7153c
AR
1435 if (!inBackground) {
1436 state->callback = &ExternalACLLookup::LookupDone;
1437 state->callback_data = cbdataReference(checklist);
d9572179 1438 }
62e76326 1439
47b0c1fa 1440 if (oldstate) {
1441 /* Hook into pending lookup */
1442 state->queue = oldstate->queue;
1443 oldstate->queue = state;
1444 } else {
e0f7153c
AR
1445 /* No pending lookup found. Sumbit to helper */
1446
47b0c1fa 1447 /* Check for queue overload */
1448
48d54e4d 1449 if (def->theHelper->stats.queue_size >= (int)def->theHelper->childs.n_running) {
e0f7153c
AR
1450 debugs(82, 7, HERE << "'" << def->name << "' queue is too long");
1451 assert(inBackground); // or the caller should have checked
47b0c1fa 1452 cbdataFree(state);
47b0c1fa 1453 return;
1454 }
1455
1456 /* Send it off to the helper */
e0f7153c 1457 MemBuf buf;
2fe7eff9 1458 buf.init();
62e76326 1459
2fe7eff9 1460 buf.Printf("%s\n", key);
62e76326 1461
bf8fe701 1462 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
ab321f4b 1463
47b0c1fa 1464 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
62e76326 1465
47b0c1fa 1466 dlinkAdd(state, &state->list, &def->queue);
62e76326 1467
2fe7eff9 1468 buf.clean();
47b0c1fa 1469 }
62e76326 1470
bf8fe701 1471 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1472 "' in '" << def->name << "' (ch=" << ch << ").");
d9572179 1473}
1474
1475static void
1476externalAclStats(StoreEntry * sentry)
1477{
1478 external_acl *p;
1479
1480 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1481 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1482 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1483 helperStats(sentry, p->theHelper);
1484 storeAppendPrintf(sentry, "\n");
d9572179 1485 }
1486}
1487
6b7d87bb
FC
1488static void
1489externalAclRegisterWithCacheManager(void)
1490{
8822ebee 1491 Mgr::RegisterAction("external_acl",
d9fc6862
A
1492 "External ACL stats",
1493 externalAclStats, 0, 1);
6b7d87bb
FC
1494}
1495
d9572179 1496void
1497externalAclInit(void)
1498{
1499 static int firstTimeInit = 1;
1500 external_acl *p;
1501
1502 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1503 if (!p->cache)
30abd221 1504 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
62e76326 1505
1506 if (!p->theHelper)
48d54e4d 1507 p->theHelper = new helper(p->name);
62e76326 1508
1509 p->theHelper->cmdline = p->cmdline;
1510
1af735c7 1511 p->theHelper->childs.updateLimits(p->children);
07eca7e0 1512
62e76326 1513 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1514
cc192b50 1515 p->theHelper->addr = p->local_addr;
1516
62e76326 1517 helperOpenServers(p->theHelper);
d9572179 1518 }
62e76326 1519
d9572179 1520 if (firstTimeInit) {
62e76326 1521 firstTimeInit = 0;
62e76326 1522 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
d9572179 1523 }
ea391f18
FC
1524
1525 externalAclRegisterWithCacheManager();
d9572179 1526}
1527
1528void
1529externalAclShutdown(void)
1530{
1531 external_acl *p;
62e76326 1532
d9572179 1533 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1534 helperShutdown(p->theHelper);
d9572179 1535 }
1536}
225b7b10 1537
1538ExternalACLLookup ExternalACLLookup::instance_;
1539ExternalACLLookup *
1540ExternalACLLookup::Instance()
1541{
1542 return &instance_;
1543}
1544
1545void
1546ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1547{
b0dd28ba 1548 /* TODO: optimise this - we probably have a pointer to this
1549 * around somewhere */
97427e90 1550 ACL *acl = ACL::FindByName(AclMatchedName);
ab321f4b 1551 assert(acl);
b0dd28ba 1552 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1553 assert (me);
225b7b10 1554 checklist->asyncInProgress(true);
e0f7153c 1555 ACLExternal::ExternalAclLookup(checklist, me);
225b7b10 1556}
1557
e0f7153c 1558/// Called when an async lookup returns
225b7b10 1559void
1560ExternalACLLookup::LookupDone(void *data, void *result)
1561{
c0941a6a 1562 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
225b7b10 1563 checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
1564 checklist->asyncInProgress(false);
c059ed04 1565 checklist->changeState (ACLChecklist::NullState::Instance());
2efeb0b7 1566 checklist->matchNonBlocking();
225b7b10 1567}
b0dd28ba 1568
1569/* This registers "external" in the registry. To do dynamic definitions
1570 * of external ACL's, rather than a static prototype, have a Prototype instance
1571 * prototype in the class that defines each external acl 'class'.
1572 * Then, then the external acl instance is created, it self registers under
1573 * it's name.
1574 * Be sure that clone is fully functional for that acl class though!
1575 */
1576ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1577
1578ACLExternal ACLExternal::RegistryEntry_("external");
1579
1580ACL *
1581ACLExternal::clone() const
1582{
1583 return new ACLExternal(*this);
1584}
1585
1586ACLExternal::ACLExternal (char const *theClass) : data (NULL), class_ (xstrdup (theClass))
1587{}
1588
1589ACLExternal::ACLExternal (ACLExternal const & old) : data (NULL), class_ (old.class_ ? xstrdup (old.class_) : NULL)
1590{
1591 /* we don't have copy constructors for the data yet */
1592 assert (!old.data);
1593}
1594
b0dd28ba 1595char const *
1596ACLExternal::typeString() const
1597{
1598 return class_;
1599}
1600
e870b1f8 1601bool
1602ACLExternal::isProxyAuth() const
1603{
2f1431ea 1604#if USE_AUTH
e870b1f8 1605 return data->def->require_auth;
2f1431ea
AJ
1606#else
1607 return false;
1608#endif
e870b1f8 1609}