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