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