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