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