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