]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
Cleanup: remove some minor memory leaks in external ACL config parsing
[thirdparty/squid.git] / src / external_acl.cc
1 /*
2 * Copyright (C) 1996-2016 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 "client_side_request.h"
17 #include "comm/Connection.h"
18 #include "ConfigParser.h"
19 #include "ExternalACL.h"
20 #include "ExternalACLEntry.h"
21 #include "fde.h"
22 #include "format/Token.h"
23 #include "helper.h"
24 #include "helper/Reply.h"
25 #include "http/Stream.h"
26 #include "HttpHeaderTools.h"
27 #include "HttpReply.h"
28 #include "HttpRequest.h"
29 #include "ip/tools.h"
30 #include "MemBuf.h"
31 #include "mgr/Registration.h"
32 #include "rfc1738.h"
33 #include "SquidConfig.h"
34 #include "SquidString.h"
35 #include "SquidTime.h"
36 #include "Store.h"
37 #include "tools.h"
38 #include "URL.h"
39 #include "wordlist.h"
40 #if USE_OPENSSL
41 #include "ssl/ServerBump.h"
42 #include "ssl/support.h"
43 #endif
44 #if USE_AUTH
45 #include "auth/Acl.h"
46 #include "auth/Gadgets.h"
47 #include "auth/UserRequest.h"
48 #endif
49 #if USE_IDENT
50 #include "ident/AclIdent.h"
51 #endif
52
53 #ifndef DEFAULT_EXTERNAL_ACL_TTL
54 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
55 #endif
56 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
57 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
58 #endif
59
60 static char *makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data);
61 static void external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry);
62 static int external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry);
63 static int external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry);
64 static void external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry);
65 static ExternalACLEntryPointer external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
66
67 /******************************************************************
68 * external_acl directive
69 */
70
71 class external_acl
72 {
73 /* FIXME: These are not really cbdata, but it is an easy way
74 * to get them pooled, refcounted, accounted and freed properly...
75 */
76 CBDATA_CLASS(external_acl);
77
78 public:
79 external_acl();
80 ~external_acl();
81
82 external_acl *next;
83
84 void add(const ExternalACLEntryPointer &);
85
86 void trimCache();
87
88 int ttl;
89
90 int negative_ttl;
91
92 int grace;
93
94 char *name;
95
96 Format::Format format;
97
98 wordlist *cmdline;
99
100 Helper::ChildConfig children;
101
102 helper *theHelper;
103
104 hash_table *cache;
105
106 dlink_list lru_list;
107
108 int cache_size;
109
110 int cache_entries;
111
112 dlink_list queue;
113
114 #if USE_AUTH
115 /**
116 * Configuration flag. May only be altered by the configuration parser.
117 *
118 * Indicates that all uses of this external_acl_type helper require authentication
119 * details to be processed. If none are available its a fail match.
120 */
121 bool require_auth;
122 #endif
123
124 Format::Quoting quote; // default quoting to use, set by protocol= parameter
125
126 Ip::Address local_addr;
127 };
128
129 CBDATA_CLASS_INIT(external_acl);
130
131 external_acl::external_acl() :
132 next(NULL),
133 ttl(DEFAULT_EXTERNAL_ACL_TTL),
134 negative_ttl(-1),
135 grace(1),
136 name(NULL),
137 format("external_acl_type"),
138 cmdline(NULL),
139 children(DEFAULT_EXTERNAL_ACL_CHILDREN),
140 theHelper(NULL),
141 cache(NULL),
142 cache_size(256*1024),
143 cache_entries(0),
144 #if USE_AUTH
145 require_auth(0),
146 #endif
147 quote(Format::LOG_QUOTE_URL)
148 {
149 local_addr.setLocalhost();
150 }
151
152 external_acl::~external_acl()
153 {
154 xfree(name);
155 wordlistDestroy(&cmdline);
156
157 if (theHelper) {
158 helperShutdown(theHelper);
159 delete theHelper;
160 theHelper = NULL;
161 }
162
163 while (lru_list.tail) {
164 ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
165 external_acl_cache_delete(this, e);
166 }
167 if (cache)
168 hashFreeMemory(cache);
169
170 while (next) {
171 external_acl *node = next;
172 next = node->next;
173 node->next = NULL; // prevent recursion
174 delete node;
175 }
176 }
177
178 void
179 parse_externalAclHelper(external_acl ** list)
180 {
181 char *token = ConfigParser::NextToken();
182
183 if (!token) {
184 self_destruct();
185 return;
186 }
187
188 external_acl *a = new external_acl;
189 a->name = xstrdup(token);
190
191 // Allow supported %macros inside quoted tokens
192 ConfigParser::EnableMacros();
193 token = ConfigParser::NextToken();
194
195 /* Parse options */
196 while (token) {
197 if (strncmp(token, "ttl=", 4) == 0) {
198 a->ttl = atoi(token + 4);
199 } else if (strncmp(token, "negative_ttl=", 13) == 0) {
200 a->negative_ttl = atoi(token + 13);
201 } else if (strncmp(token, "children=", 9) == 0) {
202 a->children.n_max = atoi(token + 9);
203 debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
204 } else if (strncmp(token, "children-max=", 13) == 0) {
205 a->children.n_max = atoi(token + 13);
206 } else if (strncmp(token, "children-startup=", 17) == 0) {
207 a->children.n_startup = atoi(token + 17);
208 } else if (strncmp(token, "children-idle=", 14) == 0) {
209 a->children.n_idle = atoi(token + 14);
210 } else if (strncmp(token, "concurrency=", 12) == 0) {
211 a->children.concurrency = atoi(token + 12);
212 } else if (strncmp(token, "queue-size=", 11) == 0) {
213 a->children.queue_size = atoi(token + 11);
214 a->children.defaultQueueSize = false;
215 } else if (strncmp(token, "cache=", 6) == 0) {
216 a->cache_size = atoi(token + 6);
217 } else if (strncmp(token, "grace=", 6) == 0) {
218 a->grace = atoi(token + 6);
219 } else if (strcmp(token, "protocol=2.5") == 0) {
220 a->quote = Format::LOG_QUOTE_SHELL;
221 } else if (strcmp(token, "protocol=3.0") == 0) {
222 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option protocol=3.0 is deprecated. Remove this from your config.");
223 a->quote = Format::LOG_QUOTE_URL;
224 } else if (strcmp(token, "quote=url") == 0) {
225 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
226 a->quote = Format::LOG_QUOTE_URL;
227 } else if (strcmp(token, "quote=shell") == 0) {
228 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
229 a->quote = Format::LOG_QUOTE_SHELL;
230
231 /* INET6: allow admin to configure some helpers explicitly to
232 bind to IPv4/v6 localhost port. */
233 } else if (strcmp(token, "ipv4") == 0) {
234 if ( !a->local_addr.setIPv4() ) {
235 debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
236 }
237 } else if (strcmp(token, "ipv6") == 0) {
238 if (!Ip::EnableIpv6)
239 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
240 // else nothing to do.
241 } else {
242 break;
243 }
244
245 token = ConfigParser::NextToken();
246 }
247 ConfigParser::DisableMacros();
248
249 /* check that child startup value is sane. */
250 if (a->children.n_startup > a->children.n_max)
251 a->children.n_startup = a->children.n_max;
252
253 /* check that child idle value is sane. */
254 if (a->children.n_idle > a->children.n_max)
255 a->children.n_idle = a->children.n_max;
256 if (a->children.n_idle < 1)
257 a->children.n_idle = 1;
258
259 if (a->negative_ttl == -1)
260 a->negative_ttl = a->ttl;
261
262 if (a->children.defaultQueueSize)
263 a->children.queue_size = 2 * a->children.n_max;
264
265 /* Legacy external_acl_type format parser.
266 * Handles a series of %... tokens where any non-% means
267 * the start of another parameter field (ie the path to binary).
268 */
269 enum Format::Quoting quote = Format::LOG_QUOTE_NONE;
270 Format::Token **fmt = &a->format.format;
271 bool data_used = false;
272 while (token) {
273 /* stop on first non-% token found */
274 if (*token != '%')
275 break;
276
277 *fmt = new Format::Token;
278 // these tokens are whitespace delimited
279 (*fmt)->space = true;
280
281 // set the default encoding to match the protocol= config
282 // this will be overridden by explicit %macro attributes
283 (*fmt)->quote = a->quote;
284
285 // compatibility for old tokens incompatible with Format::Token syntax
286 #if USE_OPENSSL // dont bother if we dont have to.
287 if (strncmp(token, "%USER_CERT_", 11) == 0) {
288 (*fmt)->type = Format::LFT_EXT_ACL_USER_CERT;
289 (*fmt)->data.string = xstrdup(token + 11);
290 (*fmt)->data.header.header = (*fmt)->data.string;
291 } else if (strncmp(token, "%USER_CA_CERT_", 14) == 0) {
292 (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
293 (*fmt)->data.string = xstrdup(token + 14);
294 (*fmt)->data.header.header = (*fmt)->data.string;
295 } else if (strncmp(token, "%CA_CERT_", 9) == 0) {
296 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead");
297 (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
298 (*fmt)->data.string = xstrdup(token + 9);
299 (*fmt)->data.header.header = (*fmt)->data.string;
300 } else
301 #endif
302 {
303 // we can use the Format::Token::parse() method since it
304 // only pulls off one token. Since we already checked
305 // for '%' prefix above this is guaranteed to be a token.
306 const size_t len = (*fmt)->parse(token, &quote);
307 assert(len == strlen(token));
308 }
309
310 // process special token-specific actions (only if necessary)
311 #if USE_AUTH
312 if ((*fmt)->type == Format::LFT_USER_LOGIN)
313 a->require_auth = true;
314 #endif
315
316 if ((*fmt)->type == Format::LFT_EXT_ACL_DATA)
317 data_used = true;
318
319 fmt = &((*fmt)->next);
320 token = ConfigParser::NextToken();
321 }
322
323 /* There must be at least one format token */
324 if (!a->format.format) {
325 delete a;
326 self_destruct();
327 return;
328 }
329
330 // format has implicit %DATA on the end if not used explicitly
331 if (!data_used) {
332 *fmt = new Format::Token;
333 (*fmt)->type = Format::LFT_EXT_ACL_DATA;
334 (*fmt)->quote = Format::LOG_QUOTE_NONE;
335 }
336
337 /* helper */
338 if (!token) {
339 delete a;
340 self_destruct();
341 return;
342 }
343
344 wordlistAdd(&a->cmdline, token);
345
346 /* arguments */
347 parse_wordlist(&a->cmdline);
348
349 while (*list)
350 list = &(*list)->next;
351
352 *list = a;
353 }
354
355 void
356 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
357 {
358 const external_acl *node;
359 const wordlist *word;
360
361 for (node = list; node; node = node->next) {
362 storeAppendPrintf(sentry, "%s %s", name, node->name);
363
364 if (!node->local_addr.isIPv6())
365 storeAppendPrintf(sentry, " ipv4");
366 else
367 storeAppendPrintf(sentry, " ipv6");
368
369 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
370 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
371
372 if (node->negative_ttl != node->ttl)
373 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
374
375 if (node->grace)
376 storeAppendPrintf(sentry, " grace=%d", node->grace);
377
378 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
379 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
380
381 if (node->children.n_startup != 1)
382 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
383
384 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
385 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
386
387 if (node->children.concurrency)
388 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
389
390 if (node->cache)
391 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
392
393 if (node->quote == Format::LOG_QUOTE_SHELL)
394 storeAppendPrintf(sentry, " protocol=2.5");
395
396 node->format.dump(sentry, NULL, false);
397
398 for (word = node->cmdline; word; word = word->next)
399 storeAppendPrintf(sentry, " %s", word->key);
400
401 storeAppendPrintf(sentry, "\n");
402 }
403 }
404
405 void
406 free_externalAclHelper(external_acl ** list)
407 {
408 delete *list;
409 *list = NULL;
410 }
411
412 static external_acl *
413 find_externalAclHelper(const char *name)
414 {
415 external_acl *node;
416
417 for (node = Config.externalAclHelperList; node; node = node->next) {
418 if (strcmp(node->name, name) == 0)
419 return node;
420 }
421
422 return NULL;
423 }
424
425 void
426 external_acl::add(const ExternalACLEntryPointer &anEntry)
427 {
428 trimCache();
429 assert(anEntry != NULL);
430 assert (anEntry->def == NULL);
431 anEntry->def = this;
432 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(anEntry.getRaw()); // XXX: make hash a std::map of Pointer.
433 hash_join(cache, e);
434 dlinkAdd(e, &e->lru, &lru_list);
435 e->lock(); //cbdataReference(e); // lock it on behalf of the hash
436 ++cache_entries;
437 }
438
439 void
440 external_acl::trimCache()
441 {
442 if (cache_size && cache_entries >= cache_size) {
443 ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
444 external_acl_cache_delete(this, e);
445 }
446 }
447
448 /******************************************************************
449 * external acl type
450 */
451
452 class external_acl_data
453 {
454 CBDATA_CLASS(external_acl_data);
455
456 public:
457 explicit external_acl_data(external_acl *aDef) : def(cbdataReference(aDef)), name(NULL), arguments(NULL) {}
458 ~external_acl_data();
459
460 external_acl *def;
461 const char *name;
462 wordlist *arguments;
463 };
464
465 CBDATA_CLASS_INIT(external_acl_data);
466
467 external_acl_data::~external_acl_data()
468 {
469 xfree(name);
470 wordlistDestroy(&arguments);
471 cbdataReferenceDone(def);
472 }
473
474 void
475 ACLExternal::parse()
476 {
477 if (data) {
478 self_destruct();
479 return;
480 }
481
482 char *token = ConfigParser::strtokFile();
483
484 if (!token) {
485 self_destruct();
486 return;
487 }
488
489 data = new external_acl_data(find_externalAclHelper(token));
490
491 if (!data->def) {
492 delete data;
493 self_destruct();
494 return;
495 }
496
497 // def->name is the name of the external_acl_type.
498 // this is the name of the 'acl' directive being tested
499 data->name = xstrdup(AclMatchedName);
500
501 while ((token = ConfigParser::strtokFile())) {
502 wordlistAdd(&data->arguments, token);
503 }
504 }
505
506 bool
507 ACLExternal::valid () const
508 {
509 #if USE_AUTH
510 if (data->def->require_auth) {
511 if (authenticateSchemeCount() == 0) {
512 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
513 return false;
514 }
515
516 if (authenticateActiveSchemeCount() == 0) {
517 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
518 return false;
519 }
520 }
521 #endif
522
523 return true;
524 }
525
526 bool
527 ACLExternal::empty () const
528 {
529 return false;
530 }
531
532 ACLExternal::~ACLExternal()
533 {
534 delete data;
535 xfree(class_);
536 }
537
538 static void
539 copyResultsFromEntry(HttpRequest *req, const ExternalACLEntryPointer &entry)
540 {
541 if (req) {
542 #if USE_AUTH
543 if (entry->user.size())
544 req->extacl_user = entry->user;
545
546 if (entry->password.size())
547 req->extacl_passwd = entry->password;
548 #endif
549 if (!req->tag.size())
550 req->tag = entry->tag;
551
552 if (entry->log.size())
553 req->extacl_log = entry->log;
554
555 if (entry->message.size())
556 req->extacl_message = entry->message;
557
558 // attach the helper kv-pair to the transaction
559 UpdateRequestNotes(req->clientConnectionManager.get(), *req, entry->notes);
560 }
561 }
562
563 static allow_t
564 aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
565 {
566 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
567 ExternalACLEntryPointer entry = ch->extacl_entry;
568
569 external_acl_message = "MISSING REQUIRED INFORMATION";
570
571 if (entry != NULL) {
572 if (entry->def == acl->def) {
573 /* Ours, use it.. if the key matches */
574 const char *key = makeExternalAclKey(ch, acl);
575 if (!key)
576 return ACCESS_DUNNO; // insufficent data to continue
577 if (strcmp(key, (char*)entry->key) != 0) {
578 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
579 // too bad. need a new lookup.
580 entry = ch->extacl_entry = NULL;
581 }
582 } else {
583 /* Not ours.. get rid of it */
584 debugs(82, 9, "entry " << entry << " not valid or not ours. Discarded.");
585 if (entry != NULL) {
586 debugs(82, 9, "entry def=" << entry->def << ", our def=" << acl->def);
587 const char *key = makeExternalAclKey(ch, acl); // may be nil
588 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "'");
589 }
590 entry = ch->extacl_entry = NULL;
591 }
592 }
593
594 if (!entry) {
595 debugs(82, 9, HERE << "No helper entry available");
596 #if USE_AUTH
597 if (acl->def->require_auth) {
598 /* Make sure the user is authenticated */
599 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
600 const allow_t ti = AuthenticateAcl(ch);
601 if (ti != ACCESS_ALLOWED) {
602 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
603 return ti;
604 }
605 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
606 }
607 #endif
608 const char *key = makeExternalAclKey(ch, acl);
609
610 if (!key) {
611 /* Not sufficient data to process */
612 return ACCESS_DUNNO;
613 }
614
615 entry = static_cast<ExternalACLEntry *>(hash_lookup(acl->def->cache, key));
616
617 const ExternalACLEntryPointer staleEntry = entry;
618 if (entry != NULL && external_acl_entry_expired(acl->def, entry))
619 entry = NULL;
620
621 if (entry != NULL && external_acl_grace_expired(acl->def, entry)) {
622 // refresh in the background
623 ExternalACLLookup::Start(ch, acl, true);
624 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
625 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
626 }
627
628 if (!entry) {
629 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
630
631 if (!acl->def->theHelper->queueFull()) {
632 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
633 if (!ch->goAsync(ExternalACLLookup::Instance()))
634 debugs(82, 2, "\"" << key << "\": no async support!");
635 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
636 return ACCESS_DUNNO; // expired cached or simply absent entry
637 } else {
638 if (!staleEntry) {
639 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
640 "' queue overload. Request rejected '" << key << "'.");
641 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
642 return ACCESS_DUNNO;
643 } else {
644 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
645 "' queue overload. Using stale result. '" << key << "'.");
646 entry = staleEntry;
647 /* Fall thru to processing below */
648 }
649 }
650 }
651 }
652
653 debugs(82, 4, HERE << "entry = { date=" <<
654 (long unsigned int) entry->date <<
655 ", result=" << entry->result <<
656 " tag=" << entry->tag <<
657 " log=" << entry->log << " }");
658 #if USE_AUTH
659 debugs(82, 4, HERE << "entry user=" << entry->user);
660 #endif
661
662 external_acl_cache_touch(acl->def, entry);
663 external_acl_message = entry->message.termedBuf();
664
665 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
666 copyResultsFromEntry(ch->request, entry);
667 return entry->result;
668 }
669
670 int
671 ACLExternal::match(ACLChecklist *checklist)
672 {
673 allow_t answer = aclMatchExternal(data, Filled(checklist));
674
675 // convert to tri-state ACL match 1,0,-1
676 switch (answer) {
677 case ACCESS_ALLOWED:
678 return 1; // match
679
680 case ACCESS_DENIED:
681 return 0; // non-match
682
683 case ACCESS_DUNNO:
684 case ACCESS_AUTH_REQUIRED:
685 default:
686 // If the answer is not allowed or denied (matches/not matches) and
687 // async authentication is not in progress, then we are done.
688 if (checklist->keepMatching())
689 checklist->markFinished(answer, "aclMatchExternal exception");
690 return -1; // other
691 }
692 }
693
694 SBufList
695 ACLExternal::dump() const
696 {
697 external_acl_data const *acl = data;
698 SBufList rv;
699 rv.push_back(SBuf(acl->def->name));
700
701 for (wordlist *arg = acl->arguments; arg; arg = arg->next) {
702 SBuf s;
703 s.Printf(" %s", arg->key);
704 rv.push_back(s);
705 }
706
707 return rv;
708 }
709
710 /******************************************************************
711 * external_acl cache
712 */
713
714 static void
715 external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry)
716 {
717 // this must not be done when nothing is being cached.
718 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
719 return;
720
721 dlinkDelete(&entry->lru, &def->lru_list);
722 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
723 dlinkAdd(e, &entry->lru, &def->lru_list);
724 }
725
726 static char *
727 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
728 {
729 static MemBuf mb;
730 mb.reset();
731
732 // check for special case tokens in the format
733 for (Format::Token *t = acl_data->def->format.format; t ; t = t->next) {
734
735 if (t->type == Format::LFT_EXT_ACL_NAME) {
736 // setup for %ACL
737 safe_free(ch->al->lastAclName);
738 ch->al->lastAclName = xstrdup(acl_data->name);
739 }
740
741 if (t->type == Format::LFT_EXT_ACL_DATA) {
742 // setup string for %DATA
743 SBuf sb;
744 for (auto arg = acl_data->arguments; arg; arg = arg->next) {
745 if (sb.length())
746 sb.append(" ", 1);
747
748 if (acl_data->def->quote == Format::LOG_QUOTE_URL) {
749 const char *quoted = rfc1738_escape(arg->key);
750 sb.append(quoted, strlen(quoted));
751 } else {
752 static MemBuf mb2;
753 mb2.init();
754 strwordquote(&mb2, arg->key);
755 sb.append(mb2.buf, mb2.size);
756 mb2.clean();
757 }
758 }
759
760 ch->al->lastAclData = sb;
761 }
762
763 #if USE_IDENT
764 if (t->type == Format::LFT_USER_IDENT) {
765 if (!*ch->rfc931) {
766 // if we fail to go async, we still return NULL and the caller
767 // will detect the failure in ACLExternal::match().
768 (void)ch->goAsync(IdentLookup::Instance());
769 return NULL;
770 }
771 }
772 #endif
773 }
774
775 // assemble the full helper lookup string
776 acl_data->def->format.assemble(mb, ch->al, 0);
777
778 return mb.buf;
779 }
780
781 static int
782 external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry)
783 {
784 if (def->cache_size <= 0)
785 return 1;
786
787 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
788 return 1;
789 else
790 return 0;
791 }
792
793 static int
794 external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry)
795 {
796 if (def->cache_size <= 0)
797 return 1;
798
799 int ttl;
800 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
801 ttl = (ttl * (100 - def->grace)) / 100;
802
803 if (entry->date + ttl <= squid_curtime)
804 return 1;
805 else
806 return 0;
807 }
808
809 static ExternalACLEntryPointer
810 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
811 {
812 ExternalACLEntryPointer entry;
813
814 // do not bother caching this result if TTL is going to expire it immediately
815 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
816 debugs(82,6, HERE);
817 entry = new ExternalACLEntry;
818 entry->key = xstrdup(key);
819 entry->update(data);
820 entry->def = def;
821 return entry;
822 }
823
824 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
825 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
826
827 if (entry != NULL) {
828 debugs(82, 3, "updating existing entry");
829 entry->update(data);
830 external_acl_cache_touch(def, entry);
831 return entry;
832 }
833
834 entry = new ExternalACLEntry;
835 entry->key = xstrdup(key);
836 entry->update(data);
837
838 def->add(entry);
839
840 return entry;
841 }
842
843 static void
844 external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry)
845 {
846 assert(entry != NULL);
847 assert(def->cache_size > 0 && entry->def == def);
848 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
849 hash_remove_link(def->cache, e);
850 dlinkDelete(&e->lru, &def->lru_list);
851 e->unlock(); // unlock on behalf of the hash
852 def->cache_entries -= 1;
853 }
854
855 /******************************************************************
856 * external_acl helpers
857 */
858
859 class externalAclState
860 {
861 CBDATA_CLASS(externalAclState);
862
863 public:
864 externalAclState(external_acl* aDef, const char *aKey) :
865 callback(NULL),
866 callback_data(NULL),
867 key(xstrdup(aKey)),
868 def(cbdataReference(aDef)),
869 queue(NULL)
870 {}
871 ~externalAclState();
872
873 EAH *callback;
874 void *callback_data;
875 char *key;
876 external_acl *def;
877 dlink_node list;
878 externalAclState *queue;
879 };
880
881 CBDATA_CLASS_INIT(externalAclState);
882
883 externalAclState::~externalAclState()
884 {
885 xfree(key);
886 cbdataReferenceDone(callback_data);
887 cbdataReferenceDone(def);
888 }
889
890 /*
891 * The helper program receives queries on stdin, one
892 * per line, and must return the result on on stdout
893 *
894 * General result syntax:
895 *
896 * OK/ERR keyword=value ...
897 *
898 * Keywords:
899 *
900 * user= The users name (login)
901 * message= Message describing the reason
902 * tag= A string tag to be applied to the request that triggered the acl match.
903 * applies to both OK and ERR responses.
904 * Won't override existing request tags.
905 * log= A string to be used in access logging
906 *
907 * Other keywords may be added to the protocol later
908 *
909 * value needs to be URL-encoded or enclosed in double quotes (")
910 * with \-escaping on any whitespace, quotes, or slashes (\).
911 */
912 static void
913 externalAclHandleReply(void *data, const Helper::Reply &reply)
914 {
915 externalAclState *state = static_cast<externalAclState *>(data);
916 externalAclState *next;
917 ExternalACLEntryData entryData;
918 entryData.result = ACCESS_DENIED;
919
920 debugs(82, 2, HERE << "reply=" << reply);
921
922 if (reply.result == Helper::Okay)
923 entryData.result = ACCESS_ALLOWED;
924 // XXX: handle other non-DENIED results better
925
926 // XXX: make entryData store a proper Helper::Reply object instead of copying.
927
928 entryData.notes.append(&reply.notes);
929
930 const char *label = reply.notes.findFirst("tag");
931 if (label != NULL && *label != '\0')
932 entryData.tag = label;
933
934 label = reply.notes.findFirst("message");
935 if (label != NULL && *label != '\0')
936 entryData.message = label;
937
938 label = reply.notes.findFirst("log");
939 if (label != NULL && *label != '\0')
940 entryData.log = label;
941
942 #if USE_AUTH
943 label = reply.notes.findFirst("user");
944 if (label != NULL && *label != '\0')
945 entryData.user = label;
946
947 label = reply.notes.findFirst("password");
948 if (label != NULL && *label != '\0')
949 entryData.password = label;
950 #endif
951
952 dlinkDelete(&state->list, &state->def->queue);
953
954 ExternalACLEntryPointer entry;
955 if (cbdataReferenceValid(state->def)) {
956 // only cache OK and ERR results.
957 if (reply.result == Helper::Okay || reply.result == Helper::Error)
958 entry = external_acl_cache_add(state->def, state->key, entryData);
959 else {
960 const ExternalACLEntryPointer oldentry = static_cast<ExternalACLEntry *>(hash_lookup(state->def->cache, state->key));
961
962 if (oldentry != NULL)
963 external_acl_cache_delete(state->def, oldentry);
964 }
965 }
966
967 do {
968 void *cbdata;
969 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
970 state->callback(cbdata, entry);
971
972 next = state->queue;
973 state->queue = NULL;
974
975 delete state;
976
977 state = next;
978 } while (state);
979 }
980
981 void
982 ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
983 {
984 ExternalACLLookup::Start(checklist, me->data, false);
985 }
986
987 void
988 ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
989 {
990 external_acl *def = acl->def;
991
992 ACLFilledChecklist *ch = Filled(checklist);
993 const char *key = makeExternalAclKey(ch, acl);
994 assert(key); // XXX: will fail if EXT_ACL_IDENT case needs an async lookup
995
996 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
997 def->name << "' for '" << key << "'");
998
999 /* Check for a pending lookup to hook into */
1000 // only possible if we are caching results.
1001 externalAclState *oldstate = NULL;
1002 if (def->cache_size > 0) {
1003 for (dlink_node *node = def->queue.head; node; node = node->next) {
1004 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1005
1006 if (strcmp(key, oldstatetmp->key) == 0) {
1007 oldstate = oldstatetmp;
1008 break;
1009 }
1010 }
1011 }
1012
1013 // A background refresh has no need to piggiback on a pending request:
1014 // When the pending request completes, the cache will be refreshed anyway.
1015 if (oldstate && inBackground) {
1016 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
1017 return;
1018 }
1019
1020 externalAclState *state = new externalAclState(def, key);
1021
1022 if (!inBackground) {
1023 state->callback = &ExternalACLLookup::LookupDone;
1024 state->callback_data = cbdataReference(checklist);
1025 }
1026
1027 if (oldstate) {
1028 /* Hook into pending lookup */
1029 state->queue = oldstate->queue;
1030 oldstate->queue = state;
1031 } else {
1032 /* No pending lookup found. Sumbit to helper */
1033
1034 MemBuf buf;
1035 buf.init();
1036 buf.appendf("%s\n", key);
1037 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1038
1039 if (!def->theHelper->trySubmit(buf.buf, externalAclHandleReply, state)) {
1040 debugs(82, 7, HERE << "'" << def->name << "' submit to helper failed");
1041 assert(inBackground); // or the caller should have checked
1042 delete state;
1043 return;
1044 }
1045
1046 dlinkAdd(state, &state->list, &def->queue);
1047
1048 buf.clean();
1049 }
1050
1051 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1052 "' in '" << def->name << "' (ch=" << ch << ").");
1053 }
1054
1055 static void
1056 externalAclStats(StoreEntry * sentry)
1057 {
1058 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1059 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1060 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1061 assert(p->theHelper);
1062 p->theHelper->packStatsInto(sentry);
1063 storeAppendPrintf(sentry, "\n");
1064 }
1065 }
1066
1067 static void
1068 externalAclRegisterWithCacheManager(void)
1069 {
1070 Mgr::RegisterAction("external_acl",
1071 "External ACL stats",
1072 externalAclStats, 0, 1);
1073 }
1074
1075 void
1076 externalAclInit(void)
1077 {
1078 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1079 if (!p->cache)
1080 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1081
1082 if (!p->theHelper)
1083 p->theHelper = new helper(p->name);
1084
1085 p->theHelper->cmdline = p->cmdline;
1086
1087 p->theHelper->childs.updateLimits(p->children);
1088
1089 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1090
1091 p->theHelper->addr = p->local_addr;
1092
1093 helperOpenServers(p->theHelper);
1094 }
1095
1096 externalAclRegisterWithCacheManager();
1097 }
1098
1099 void
1100 externalAclShutdown(void)
1101 {
1102 external_acl *p;
1103
1104 for (p = Config.externalAclHelperList; p; p = p->next) {
1105 helperShutdown(p->theHelper);
1106 }
1107 }
1108
1109 ExternalACLLookup ExternalACLLookup::instance_;
1110 ExternalACLLookup *
1111 ExternalACLLookup::Instance()
1112 {
1113 return &instance_;
1114 }
1115
1116 void
1117 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1118 {
1119 /* TODO: optimise this - we probably have a pointer to this
1120 * around somewhere */
1121 ACL *acl = ACL::FindByName(AclMatchedName);
1122 assert(acl);
1123 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1124 assert (me);
1125 ACLExternal::ExternalAclLookup(checklist, me);
1126 }
1127
1128 /// Called when an async lookup returns
1129 void
1130 ExternalACLLookup::LookupDone(void *data, const ExternalACLEntryPointer &result)
1131 {
1132 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
1133 checklist->extacl_entry = result;
1134 checklist->resumeNonBlockingCheck(ExternalACLLookup::Instance());
1135 }
1136
1137 /* This registers "external" in the registry. To do dynamic definitions
1138 * of external ACL's, rather than a static prototype, have a Prototype instance
1139 * prototype in the class that defines each external acl 'class'.
1140 * Then, then the external acl instance is created, it self registers under
1141 * it's name.
1142 * Be sure that clone is fully functional for that acl class though!
1143 */
1144 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1145
1146 ACLExternal ACLExternal::RegistryEntry_("external");
1147
1148 ACL *
1149 ACLExternal::clone() const
1150 {
1151 return new ACLExternal(*this);
1152 }
1153
1154 ACLExternal::ACLExternal(char const *theClass) : data(NULL), class_(xstrdup(theClass))
1155 {}
1156
1157 ACLExternal::ACLExternal(ACLExternal const & old) : data(NULL), class_(old.class_ ? xstrdup(old.class_) : NULL)
1158 {
1159 /* we don't have copy constructors for the data yet */
1160 assert(!old.data);
1161 }
1162
1163 char const *
1164 ACLExternal::typeString() const
1165 {
1166 return class_;
1167 }
1168
1169 bool
1170 ACLExternal::isProxyAuth() const
1171 {
1172 #if USE_AUTH
1173 return data->def->require_auth;
1174 #else
1175 return false;
1176 #endif
1177 }
1178