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