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