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