]> git.ipfire.org Git - thirdparty/squid.git/blame - src/external_acl.cc
Update Notes API to support key=pair additions and lookup
[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"
4d5904f7 58#include "SquidConfig.h"
f9b6ff6e 59#include "SquidString.h"
582c2af2
FC
60#include "SquidTime.h"
61#include "Store.h"
4e540555 62#include "tools.h"
b1bd952a 63#include "URL.h"
f9b6ff6e 64#include "URLScheme.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 * external acl type
674 */
675
26ac0430 676struct _external_acl_data {
d9572179 677 external_acl *def;
678 wordlist *arguments;
679};
680
681CBDATA_TYPE(external_acl_data);
682static void
683free_external_acl_data(void *data)
684{
e6ccf245 685 external_acl_data *p = static_cast<external_acl_data *>(data);
d9572179 686 wordlistDestroy(&p->arguments);
687 cbdataReferenceDone(p->def);
688}
689
690void
b0dd28ba 691ACLExternal::parse()
d9572179 692{
d9572179 693 char *token;
62e76326 694
b0dd28ba 695 if (data)
62e76326 696 self_destruct();
697
d9572179 698 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
62e76326 699
d9572179 700 data = cbdataAlloc(external_acl_data);
62e76326 701
d9572179 702 token = strtok(NULL, w_space);
62e76326 703
d9572179 704 if (!token)
62e76326 705 self_destruct();
706
d9572179 707 data->def = cbdataReference(find_externalAclHelper(token));
62e76326 708
d9572179 709 if (!data->def)
62e76326 710 self_destruct();
711
4d091667 712 while ((token = strtokFile())) {
62e76326 713 wordlistAdd(&data->arguments, token);
d9572179 714 }
d9572179 715}
716
4b0f5de8 717bool
718ACLExternal::valid () const
719{
2f1431ea 720#if USE_AUTH
4b0f5de8 721 if (data->def->require_auth) {
722 if (authenticateSchemeCount() == 0) {
fa84c01d 723 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
4b0f5de8 724 return false;
725 }
726
727 if (authenticateActiveSchemeCount() == 0) {
fa84c01d 728 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
4b0f5de8 729 return false;
730 }
731 }
2f1431ea 732#endif
4b0f5de8 733
734 return true;
735}
736
737bool
738ACLExternal::empty () const
739{
740 return false;
741}
742
b0dd28ba 743ACLExternal::~ACLExternal()
d9572179 744{
b0dd28ba 745 cbdataFree(data);
746 safe_free (class_);
d9572179 747}
748
1abe0161
AJ
749static void
750copyResultsFromEntry(HttpRequest *req, external_acl_entry *entry)
751{
752 if (req) {
753#if USE_AUTH
754 if (entry->user.size())
755 req->extacl_user = entry->user;
756
757 if (entry->password.size())
758 req->extacl_passwd = entry->password;
759#endif
760 if (!req->tag.size())
761 req->tag = entry->tag;
762
763 if (entry->log.size())
764 req->extacl_log = entry->log;
765
766 if (entry->message.size())
767 req->extacl_message = entry->message;
768 }
769}
770
e5f825ea 771static allow_t
c0941a6a 772aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
d9572179 773{
d9572179 774 const char *key = "";
c69ce640 775 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
e5f825ea 776 external_acl_entry *entry = ch->extacl_entry;
62e76326 777
d9572179 778 if (entry) {
c69ce640 779 if (cbdataReferenceValid(entry) && entry->def == acl->def) {
ad06254e
AJ
780 /* Ours, use it.. if the key matches */
781 key = makeExternalAclKey(ch, acl);
782 if (strcmp(key, (char*)entry->key) != 0) {
783 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
784 // too bad. need a new lookup.
785 cbdataReferenceDone(ch->extacl_entry);
786 entry = NULL;
787 }
62e76326 788 } else {
789 /* Not valid, or not ours.. get rid of it */
c69ce640
AJ
790 debugs(82, 9, HERE << "entry " << entry << " not valid or not ours. Discarded.");
791 if (entry) {
792 debugs(82, 9, HERE << "entry def=" << entry->def << ", our def=" << acl->def);
793 key = makeExternalAclKey(ch, acl);
794 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "'");
795 }
62e76326 796 cbdataReferenceDone(ch->extacl_entry);
797 entry = NULL;
798 }
d9572179 799 }
62e76326 800
4a972fa2 801 external_acl_message = "MISSING REQUIRED INFORMATION";
802
d9572179 803 if (!entry) {
c69ce640 804 debugs(82, 9, HERE << "No helper entry available");
2f1431ea 805#if USE_AUTH
62e76326 806 if (acl->def->require_auth) {
62e76326 807 /* Make sure the user is authenticated */
e5f825ea 808 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
e0f7153c
AR
809 const allow_t ti = AuthenticateAcl(ch);
810 if (ti != ACCESS_ALLOWED) {
e5f825ea 811 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
e0f7153c 812 return ti;
62e76326 813 }
e5f825ea 814 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
62e76326 815 }
2f1431ea 816#endif
62e76326 817 key = makeExternalAclKey(ch, acl);
41218131 818
819 if (!key) {
820 /* Not sufficient data to process */
e5f825ea 821 return ACCESS_DUNNO;
41218131 822 }
823
62e76326 824 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
825
e0f7153c
AR
826 external_acl_entry *staleEntry = entry;
827 if (entry && external_acl_entry_expired(acl->def, entry))
828 entry = NULL;
829
830 if (entry && external_acl_grace_expired(acl->def, entry)) {
831 // refresh in the background
832 ExternalACLLookup::Start(ch, acl, true);
833 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
834 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
835 }
836
837 if (!entry) {
e5f825ea
AJ
838 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
839 debugs(82, 2, HERE << "\"" << key << "\": entry=@" <<
bf8fe701 840 entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
95a83c22 841
48d54e4d 842 if (acl->def->theHelper->stats.queue_size <= (int)acl->def->theHelper->childs.n_active) {
e5f825ea 843 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
dc1839a5 844 ch->changeState(ExternalACLLookup::Instance());
e5f825ea 845 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
e0f7153c 846 return ACCESS_DUNNO; // expired cached or simply absent entry
47b0c1fa 847 } else {
e0f7153c 848 if (!staleEntry) {
e5f825ea 849 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 850 "' queue overload. Request rejected '" << key << "'.");
4a972fa2 851 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
e5f825ea 852 return ACCESS_DUNNO;
47b0c1fa 853 } else {
e5f825ea 854 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 855 "' queue overload. Using stale result. '" << key << "'.");
e0f7153c 856 entry = staleEntry;
47b0c1fa 857 /* Fall thru to processing below */
858 }
859 }
860 }
d9572179 861 }
62e76326 862
e0f7153c
AR
863 debugs(82, 4, HERE << "entry = { date=" <<
864 (long unsigned int) entry->date <<
865 ", result=" << entry->result <<
866 " tag=" << entry->tag <<
867 " log=" << entry->log << " }");
868#if USE_AUTH
869 debugs(82, 4, HERE << "entry user=" << entry->user);
870#endif
871
d9572179 872 external_acl_cache_touch(acl->def, entry);
a7a42b14 873 external_acl_message = entry->message.termedBuf();
4a972fa2 874
e5f825ea 875 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
1abe0161 876 copyResultsFromEntry(ch->request, entry);
e5f825ea 877 return entry->result;
d9572179 878}
879
b0dd28ba 880int
881ACLExternal::match(ACLChecklist *checklist)
882{
e5f825ea 883 allow_t answer = aclMatchExternal(data, Filled(checklist));
e5f825ea
AJ
884
885 // convert to tri-state ACL match 1,0,-1
9b831d57 886 switch (answer) {
e5f825ea 887 case ACCESS_ALLOWED:
e5f825ea
AJ
888 return 1; // match
889
890 case ACCESS_DENIED:
e5f825ea
AJ
891 return 0; // non-match
892
893 case ACCESS_DUNNO:
894 case ACCESS_AUTH_REQUIRED:
895 default:
e0f7153c
AR
896 // If the answer is not allowed or denied (matches/not matches) and
897 // async authentication is not needed (asyncNeeded), then we are done.
898 if (!checklist->asyncNeeded())
899 checklist->markFinished(answer, "aclMatchExternal exception");
e5f825ea
AJ
900 return -1; // other
901 }
b0dd28ba 902}
903
d9572179 904wordlist *
b0dd28ba 905ACLExternal::dump() const
d9572179 906{
b0dd28ba 907 external_acl_data const *acl = data;
d9572179 908 wordlist *result = NULL;
909 wordlist *arg;
910 MemBuf mb;
2fe7eff9 911 mb.init();
912 mb.Printf("%s", acl->def->name);
62e76326 913
d9572179 914 for (arg = acl->arguments; arg; arg = arg->next) {
2fe7eff9 915 mb.Printf(" %s", arg->key);
d9572179 916 }
62e76326 917
d9572179 918 wordlistAdd(&result, mb.buf);
2fe7eff9 919 mb.clean();
d9572179 920 return result;
921}
922
923/******************************************************************
924 * external_acl cache
925 */
926
d9572179 927static void
928external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
929{
c69ce640
AJ
930 // this must not be done when nothing is being cached.
931 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
932 return;
933
d9572179 934 dlinkDelete(&entry->lru, &def->lru_list);
935 dlinkAdd(entry, &entry->lru, &def->lru_list);
936}
937
938static char *
c0941a6a 939makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
d9572179 940{
032785bf 941 static MemBuf mb;
d9572179 942 char buf[256];
943 int first = 1;
944 wordlist *arg;
945 external_acl_format *format;
190154cf 946 HttpRequest *request = ch->request;
7b0ca1e8 947 HttpReply *reply = ch->reply;
2fe7eff9 948 mb.reset();
62e76326 949
d9572179 950 for (format = acl_data->def->format; format; format = format->next) {
62e76326 951 const char *str = NULL;
30abd221 952 String sb;
62e76326 953
954 switch (format->type) {
2f1431ea 955#if USE_AUTH
62e76326 956 case _external_acl_format::EXT_ACL_LOGIN:
d28c476f
AJ
957 // if this ACL line was the cause of credentials fetch
958 // they may not already be in the checklist
959 if (ch->auth_user_request == NULL && ch->request)
960 ch->auth_user_request = ch->request->auth_user_request;
961
962 if (ch->auth_user_request != NULL)
963 str = ch->auth_user_request->username();
62e76326 964 break;
2f1431ea 965#endif
7a16973f 966#if USE_IDENT
62e76326 967 case _external_acl_format::EXT_ACL_IDENT:
968 str = ch->rfc931;
969
41218131 970 if (!str || !*str) {
62e76326 971 ch->changeState(IdentLookup::Instance());
972 return NULL;
973 }
974
975 break;
7a16973f 976#endif
62e76326 977
978 case _external_acl_format::EXT_ACL_SRC:
cc192b50 979 str = ch->src_addr.NtoA(buf,sizeof(buf));
62e76326 980 break;
981
47b0c1fa 982 case _external_acl_format::EXT_ACL_SRCPORT:
cc192b50 983 snprintf(buf, sizeof(buf), "%d", request->client_addr.GetPort());
47b0c1fa 984 str = buf;
985 break;
986
a98c2da5
AJ
987#if USE_SQUID_EUI
988 case _external_acl_format::EXT_ACL_SRCEUI48:
40d34a62
AJ
989 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
990 request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
a98c2da5
AJ
991 str = buf;
992 break;
993
994 case _external_acl_format::EXT_ACL_SRCEUI64:
40d34a62
AJ
995 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
996 request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
a98c2da5
AJ
997 str = buf;
998 break;
999#endif
1000
47b0c1fa 1001 case _external_acl_format::EXT_ACL_MYADDR:
cc192b50 1002 str = request->my_addr.NtoA(buf, sizeof(buf));
47b0c1fa 1003 break;
1004
1005 case _external_acl_format::EXT_ACL_MYPORT:
cc192b50 1006 snprintf(buf, sizeof(buf), "%d", request->my_addr.GetPort());
47b0c1fa 1007 str = buf;
1008 break;
1009
3cf6e9c5 1010 case _external_acl_format::EXT_ACL_URI:
1011 str = urlCanonical(request);
1012 break;
1013
62e76326 1014 case _external_acl_format::EXT_ACL_DST:
cc192b50 1015 str = request->GetHost();
62e76326 1016 break;
1017
1018 case _external_acl_format::EXT_ACL_PROTO:
0c3d3f65 1019 str = AnyP::ProtocolType_str[request->protocol];
62e76326 1020 break;
1021
1022 case _external_acl_format::EXT_ACL_PORT:
1023 snprintf(buf, sizeof(buf), "%d", request->port);
1024 str = buf;
1025 break;
1026
1027 case _external_acl_format::EXT_ACL_PATH:
a313a9ba 1028 str = request->urlpath.termedBuf();
62e76326 1029 break;
1030
1031 case _external_acl_format::EXT_ACL_METHOD:
60745f24 1032 str = RequestMethodStr(request->method);
62e76326 1033 break;
1034
7b0ca1e8 1035 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
a9925b40 1036 sb = request->header.getByName(format->header);
a313a9ba 1037 str = sb.termedBuf();
62e76326 1038 break;
1039
7b0ca1e8 1040 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
a9925b40 1041 sb = request->header.getStrOrList(format->header_id);
a313a9ba 1042 str = sb.termedBuf();
62e76326 1043 break;
1044
7b0ca1e8 1045 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
a9925b40 1046 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
a313a9ba 1047 str = sb.termedBuf();
62e76326 1048 break;
1049
7b0ca1e8 1050 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
a9925b40 1051 sb = request->header.getListMember(format->header_id, format->member, format->separator);
a313a9ba 1052 str = sb.termedBuf();
62e76326 1053 break;
7b0ca1e8
AJ
1054
1055 case _external_acl_format::EXT_ACL_HEADER_REPLY:
26ac0430 1056 if (reply) {
7b0ca1e8 1057 sb = reply->header.getByName(format->header);
a313a9ba 1058 str = sb.termedBuf();
7b0ca1e8
AJ
1059 }
1060 break;
1061
1062 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
26ac0430 1063 if (reply) {
7b0ca1e8 1064 sb = reply->header.getStrOrList(format->header_id);
a313a9ba 1065 str = sb.termedBuf();
7b0ca1e8
AJ
1066 }
1067 break;
1068
1069 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
26ac0430 1070 if (reply) {
7b0ca1e8 1071 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
a313a9ba 1072 str = sb.termedBuf();
7b0ca1e8
AJ
1073 }
1074 break;
1075
1076 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
26ac0430 1077 if (reply) {
7b0ca1e8 1078 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
a313a9ba 1079 str = sb.termedBuf();
7b0ca1e8
AJ
1080 }
1081 break;
a7ad6e4e 1082#if USE_SSL
62e76326 1083
4ac9968f 1084 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
1085
73c36fd9
AJ
1086 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1087 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
4ac9968f 1088
1089 if (ssl)
1090 str = sslGetUserCertificatePEM(ssl);
1091 }
1092
1093 break;
1094
3d61c476 1095 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
1096
73c36fd9
AJ
1097 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1098 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
3d61c476 1099
1100 if (ssl)
1101 str = sslGetUserCertificateChainPEM(ssl);
1102 }
1103
1104 break;
1105
62e76326 1106 case _external_acl_format::EXT_ACL_USER_CERT:
1107
73c36fd9
AJ
1108 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1109 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
62e76326 1110
1111 if (ssl)
1112 str = sslGetUserAttribute(ssl, format->header);
1113 }
1114
1115 break;
1116
1117 case _external_acl_format::EXT_ACL_CA_CERT:
1118
73c36fd9
AJ
1119 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1120 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
62e76326 1121
1122 if (ssl)
1123 str = sslGetCAAttribute(ssl, format->header);
1124 }
1125
1126 break;
a7ad6e4e 1127#endif
2f1431ea 1128#if USE_AUTH
abb929f0 1129 case _external_acl_format::EXT_ACL_EXT_USER:
a313a9ba 1130 str = request->extacl_user.termedBuf();
abb929f0 1131 break;
2f1431ea 1132#endif
99e4ad67
JB
1133 case _external_acl_format::EXT_ACL_EXT_LOG:
1134 str = request->extacl_log.termedBuf();
1135 break;
1136 case _external_acl_format::EXT_ACL_TAG:
1137 str = request->tag.termedBuf();
1138 break;
0db8942f
AJ
1139 case _external_acl_format::EXT_ACL_PERCENT:
1140 str = "%";
1141 break;
62e76326 1142 case _external_acl_format::EXT_ACL_UNKNOWN:
1143
1144 case _external_acl_format::EXT_ACL_END:
1145 fatal("unknown external_acl format error");
1146 break;
1147 }
1148
1149 if (str)
1150 if (!*str)
1151 str = NULL;
1152
1153 if (!str)
1154 str = "-";
1155
1156 if (!first)
2fe7eff9 1157 mb.append(" ", 1);
62e76326 1158
dc1af3cf 1159 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1160 const char *quoted = rfc1738_escape(str);
2fe7eff9 1161 mb.append(quoted, strlen(quoted));
dc1af3cf 1162 } else {
1163 strwordquote(&mb, str);
1164 }
62e76326 1165
30abd221 1166 sb.clean();
62e76326 1167
1168 first = 0;
d9572179 1169 }
62e76326 1170
d9572179 1171 for (arg = acl_data->arguments; arg; arg = arg->next) {
62e76326 1172 if (!first)
2fe7eff9 1173 mb.append(" ", 1);
62e76326 1174
dc1af3cf 1175 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1176 const char *quoted = rfc1738_escape(arg->key);
2fe7eff9 1177 mb.append(quoted, strlen(quoted));
dc1af3cf 1178 } else {
1179 strwordquote(&mb, arg->key);
1180 }
62e76326 1181
1182 first = 0;
d9572179 1183 }
62e76326 1184
d9572179 1185 return mb.buf;
d9572179 1186}
1187
1188static int
1189external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1190{
c69ce640
AJ
1191 if (def->cache_size <= 0)
1192 return 1;
1193
d9572179 1194 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
62e76326 1195 return 1;
d9572179 1196 else
62e76326 1197 return 0;
d9572179 1198}
62e76326 1199
47b0c1fa 1200static int
1201external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1202{
c69ce640
AJ
1203 if (def->cache_size <= 0)
1204 return 1;
1205
47b0c1fa 1206 int ttl;
1207 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1208 ttl = (ttl * (100 - def->grace)) / 100;
1209
2e1359a0 1210 if (entry->date + ttl <= squid_curtime)
47b0c1fa 1211 return 1;
1212 else
1213 return 0;
1214}
1215
d9572179 1216static external_acl_entry *
1e5562e3 1217external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
d9572179 1218{
c69ce640
AJ
1219 ExternalACLEntry *entry;
1220
1221 // do not bother caching this result if TTL is going to expire it immediately
1222 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
1223 debugs(82,6, HERE);
1224 entry = new ExternalACLEntry;
1225 entry->key = xstrdup(key);
1226 entry->update(data);
1227 entry->def = def;
1228 return entry;
1229 }
1230
1231 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
bf8fe701 1232 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
62e76326 1233
d9572179 1234 if (entry) {
bf8fe701 1235 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
c69ce640 1236 entry->update(data);
62e76326 1237 external_acl_cache_touch(def, entry);
1238
1239 return entry;
d9572179 1240 }
62e76326 1241
1e5562e3 1242 entry = new ExternalACLEntry;
4a8b20e8 1243 entry->key = xstrdup(key);
c69ce640 1244 entry->update(data);
1e5562e3 1245
6ca34f6f 1246 def->add(entry);
1e5562e3 1247
d9572179 1248 return entry;
1249}
1250
1251static void
1252external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1253{
c69ce640 1254 assert(def->cache_size > 0 && entry->def == def);
4a8b20e8 1255 hash_remove_link(def->cache, entry);
d9572179 1256 dlinkDelete(&entry->lru, &def->lru_list);
1257 def->cache_entries -= 1;
00d77d6b 1258 delete entry;
d9572179 1259}
1260
1261/******************************************************************
1262 * external_acl helpers
1263 */
1264
1265typedef struct _externalAclState externalAclState;
62e76326 1266
26ac0430 1267struct _externalAclState {
d9572179 1268 EAH *callback;
1269 void *callback_data;
1270 char *key;
1271 external_acl *def;
1272 dlink_node list;
1273 externalAclState *queue;
1274};
1275
1276CBDATA_TYPE(externalAclState);
1277static void
1278free_externalAclState(void *data)
1279{
e6ccf245 1280 externalAclState *state = static_cast<externalAclState *>(data);
d9572179 1281 safe_free(state->key);
1282 cbdataReferenceDone(state->callback_data);
1283 cbdataReferenceDone(state->def);
1284}
1285
d9572179 1286/*
1287 * The helper program receives queries on stdin, one
07eca7e0 1288 * per line, and must return the result on on stdout
d9572179 1289 *
1290 * General result syntax:
1291 *
1292 * OK/ERR keyword=value ...
1293 *
1294 * Keywords:
1295 *
4a972fa2 1296 * user= The users name (login)
1297 * message= Message describing the reason
1298 * tag= A string tag to be applied to the request that triggered the acl match.
1299 * applies to both OK and ERR responses.
1300 * Won't override existing request tags.
1301 * log= A string to be used in access logging
d9572179 1302 *
1303 * Other keywords may be added to the protocol later
1304 *
26ac0430
AJ
1305 * value needs to be enclosed in quotes if it may contain whitespace, or
1306 * the whitespace escaped using \ (\ escaping obviously also applies to
d9572179 1307 * any " characters)
1308 */
d9572179 1309static void
0272dd08 1310externalAclHandleReply(void *data, const HelperReply &reply)
d9572179 1311{
e6ccf245 1312 externalAclState *state = static_cast<externalAclState *>(data);
d9572179 1313 externalAclState *next;
c69ce640 1314 char *t = NULL;
1e5562e3 1315 ExternalACLEntryData entryData;
e5f825ea 1316 entryData.result = ACCESS_DENIED;
466090ac 1317 external_acl_entry *entry = NULL;
d9572179 1318
0272dd08 1319 debugs(82, 2, HERE << "reply=" << reply);
d9572179 1320
0272dd08
AJ
1321 if (reply.result == HelperReply::Okay)
1322 entryData.result = ACCESS_ALLOWED;
1323 // XXX: handle other non-DENIED results better
62e76326 1324
ab332e27
AJ
1325 if (reply.tag.hasContent())
1326 entryData.tag = reply.tag;
1327 if (reply.message.hasContent())
1328 entryData.message = reply.message;
1329 if (reply.log.hasContent())
1330 entryData.log = reply.log;
1331#if USE_AUTH
1332 if (reply.user.hasContent())
1333 entryData.user = reply.user;
1334 if (reply.password.hasContent())
1335 entryData.password = reply.password;
1336#endif
1337
1338 // legacy reply parser
0272dd08
AJ
1339 if (reply.other().hasContent()) {
1340 char *temp = reply.modifiableOther().content();
1341 char *token = strwordtok(temp, &t);
62e76326 1342
1343 while ((token = strwordtok(NULL, &t))) {
ab332e27 1344 debugs(82, DBG_IMPORTANT, "WARNING: key '" << token << "' is not supported by this Squid.");
0272dd08 1345 char *value = strchr(token, '=');
62e76326 1346
1347 if (value) {
a38ec4b1
FC
1348 *value = '\0'; /* terminate the token, and move up to the value */
1349 ++value;
62e76326 1350
dc1af3cf 1351 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
1352 rfc1738_unescape(value);
1353
ab332e27 1354 if (strcmp(token, "error") == 0) {
d7bd27db 1355 entryData.message = value;
2f1431ea 1356#if USE_AUTH
ab332e27 1357 } else if (strcmp(token, "passwd") == 0) {
abb929f0 1358 entryData.password = value;
ab332e27 1359 } else if (strcmp(token, "login") == 0) {
abb929f0 1360 entryData.user = value;
2f1431ea 1361#endif
ab332e27 1362 }
62e76326 1363 }
1364 }
d9572179 1365 }
62e76326 1366
d9572179 1367 dlinkDelete(&state->list, &state->def->queue);
62e76326 1368
4960475f 1369 if (cbdataReferenceValid(state->def)) {
0272dd08
AJ
1370 // only cache OK and ERR results.
1371 if (reply.result == HelperReply::Okay || reply.result == HelperReply::Error)
1e5562e3 1372 entry = external_acl_cache_add(state->def, state->key, entryData);
62e76326 1373 else {
2ae2408b 1374 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
62e76326 1375
2ae2408b 1376 if (oldentry)
1377 external_acl_cache_delete(state->def, oldentry);
62e76326 1378 }
466090ac 1379 }
d9572179 1380
1381 do {
62e76326 1382 void *cbdata;
1383 cbdataReferenceDone(state->def);
1384
47b0c1fa 1385 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
62e76326 1386 state->callback(cbdata, entry);
1387
1388 next = state->queue;
d9572179 1389
62e76326 1390 cbdataFree(state);
d9572179 1391
62e76326 1392 state = next;
d9572179 1393 } while (state);
1394}
1395
1396void
e0f7153c 1397ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
d9572179 1398{
e0f7153c
AR
1399 ExternalACLLookup::Start(checklist, me->data, false);
1400}
c8e7608c 1401
e0f7153c
AR
1402void
1403ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
1404{
1405 external_acl *def = acl->def;
c0f81932 1406
c0941a6a 1407 ACLFilledChecklist *ch = Filled(checklist);
c8e7608c 1408 const char *key = makeExternalAclKey(ch, acl);
e0f7153c 1409 assert(key);
62e76326 1410
e0f7153c
AR
1411 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
1412 def->name << "' for '" << key << "'");
62e76326 1413
47b0c1fa 1414 /* Check for a pending lookup to hook into */
c69ce640 1415 // only possible if we are caching results.
e0f7153c 1416 externalAclState *oldstate = NULL;
c69ce640 1417 if (def->cache_size > 0) {
e0f7153c 1418 for (dlink_node *node = def->queue.head; node; node = node->next) {
c69ce640 1419 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
62e76326 1420
c69ce640
AJ
1421 if (strcmp(key, oldstatetmp->key) == 0) {
1422 oldstate = oldstatetmp;
1423 break;
1424 }
47b0c1fa 1425 }
1426 }
62e76326 1427
e0f7153c
AR
1428 // A background refresh has no need to piggiback on a pending request:
1429 // When the pending request completes, the cache will be refreshed anyway.
1430 if (oldstate && inBackground) {
1431 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
47b0c1fa 1432 return;
1433 }
62e76326 1434
e0f7153c 1435 externalAclState *state = cbdataAlloc(externalAclState);
47b0c1fa 1436 state->def = cbdataReference(def);
62e76326 1437
47b0c1fa 1438 state->key = xstrdup(key);
62e76326 1439
e0f7153c
AR
1440 if (!inBackground) {
1441 state->callback = &ExternalACLLookup::LookupDone;
1442 state->callback_data = cbdataReference(checklist);
d9572179 1443 }
62e76326 1444
47b0c1fa 1445 if (oldstate) {
1446 /* Hook into pending lookup */
1447 state->queue = oldstate->queue;
1448 oldstate->queue = state;
1449 } else {
e0f7153c
AR
1450 /* No pending lookup found. Sumbit to helper */
1451
47b0c1fa 1452 /* Check for queue overload */
1453
48d54e4d 1454 if (def->theHelper->stats.queue_size >= (int)def->theHelper->childs.n_running) {
e0f7153c
AR
1455 debugs(82, 7, HERE << "'" << def->name << "' queue is too long");
1456 assert(inBackground); // or the caller should have checked
47b0c1fa 1457 cbdataFree(state);
47b0c1fa 1458 return;
1459 }
1460
1461 /* Send it off to the helper */
e0f7153c 1462 MemBuf buf;
2fe7eff9 1463 buf.init();
62e76326 1464
2fe7eff9 1465 buf.Printf("%s\n", key);
62e76326 1466
bf8fe701 1467 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
ab321f4b 1468
47b0c1fa 1469 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
62e76326 1470
47b0c1fa 1471 dlinkAdd(state, &state->list, &def->queue);
62e76326 1472
2fe7eff9 1473 buf.clean();
47b0c1fa 1474 }
62e76326 1475
bf8fe701 1476 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1477 "' in '" << def->name << "' (ch=" << ch << ").");
d9572179 1478}
1479
1480static void
1481externalAclStats(StoreEntry * sentry)
1482{
1483 external_acl *p;
1484
1485 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1486 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1487 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1488 helperStats(sentry, p->theHelper);
1489 storeAppendPrintf(sentry, "\n");
d9572179 1490 }
1491}
1492
6b7d87bb
FC
1493static void
1494externalAclRegisterWithCacheManager(void)
1495{
8822ebee 1496 Mgr::RegisterAction("external_acl",
d9fc6862
A
1497 "External ACL stats",
1498 externalAclStats, 0, 1);
6b7d87bb
FC
1499}
1500
d9572179 1501void
1502externalAclInit(void)
1503{
1504 static int firstTimeInit = 1;
1505 external_acl *p;
1506
1507 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1508 if (!p->cache)
30abd221 1509 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
62e76326 1510
1511 if (!p->theHelper)
48d54e4d 1512 p->theHelper = new helper(p->name);
62e76326 1513
1514 p->theHelper->cmdline = p->cmdline;
1515
1af735c7 1516 p->theHelper->childs.updateLimits(p->children);
07eca7e0 1517
62e76326 1518 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1519
cc192b50 1520 p->theHelper->addr = p->local_addr;
1521
ab332e27
AJ
1522 if (p->quote == external_acl::QUOTE_METHOD_URL)
1523 p->theHelper->url_quoting = true;
1524
62e76326 1525 helperOpenServers(p->theHelper);
d9572179 1526 }
62e76326 1527
d9572179 1528 if (firstTimeInit) {
62e76326 1529 firstTimeInit = 0;
62e76326 1530 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
d9572179 1531 }
ea391f18
FC
1532
1533 externalAclRegisterWithCacheManager();
d9572179 1534}
1535
1536void
1537externalAclShutdown(void)
1538{
1539 external_acl *p;
62e76326 1540
d9572179 1541 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1542 helperShutdown(p->theHelper);
d9572179 1543 }
1544}
225b7b10 1545
1546ExternalACLLookup ExternalACLLookup::instance_;
1547ExternalACLLookup *
1548ExternalACLLookup::Instance()
1549{
1550 return &instance_;
1551}
1552
1553void
1554ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1555{
b0dd28ba 1556 /* TODO: optimise this - we probably have a pointer to this
1557 * around somewhere */
97427e90 1558 ACL *acl = ACL::FindByName(AclMatchedName);
ab321f4b 1559 assert(acl);
b0dd28ba 1560 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1561 assert (me);
225b7b10 1562 checklist->asyncInProgress(true);
e0f7153c 1563 ACLExternal::ExternalAclLookup(checklist, me);
225b7b10 1564}
1565
e0f7153c 1566/// Called when an async lookup returns
225b7b10 1567void
1568ExternalACLLookup::LookupDone(void *data, void *result)
1569{
c0941a6a 1570 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
225b7b10 1571 checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
1572 checklist->asyncInProgress(false);
c059ed04 1573 checklist->changeState (ACLChecklist::NullState::Instance());
2efeb0b7 1574 checklist->matchNonBlocking();
225b7b10 1575}
b0dd28ba 1576
1577/* This registers "external" in the registry. To do dynamic definitions
1578 * of external ACL's, rather than a static prototype, have a Prototype instance
1579 * prototype in the class that defines each external acl 'class'.
1580 * Then, then the external acl instance is created, it self registers under
1581 * it's name.
1582 * Be sure that clone is fully functional for that acl class though!
1583 */
1584ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1585
1586ACLExternal ACLExternal::RegistryEntry_("external");
1587
1588ACL *
1589ACLExternal::clone() const
1590{
1591 return new ACLExternal(*this);
1592}
1593
1594ACLExternal::ACLExternal (char const *theClass) : data (NULL), class_ (xstrdup (theClass))
1595{}
1596
1597ACLExternal::ACLExternal (ACLExternal const & old) : data (NULL), class_ (old.class_ ? xstrdup (old.class_) : NULL)
1598{
1599 /* we don't have copy constructors for the data yet */
1600 assert (!old.data);
1601}
1602
b0dd28ba 1603char const *
1604ACLExternal::typeString() const
1605{
1606 return class_;
1607}
1608
e870b1f8 1609bool
1610ACLExternal::isProxyAuth() const
1611{
2f1431ea 1612#if USE_AUTH
e870b1f8 1613 return data->def->require_auth;
2f1431ea
AJ
1614#else
1615 return false;
1616#endif
e870b1f8 1617}