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