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