]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / external_acl.cc
1 /*
2 * Copyright (C) 1996-2017 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 != 0) // sync with helper/ChildConfig.cc default
382 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
383
384 if (node->children.n_idle != 1) // sync with helper/ChildConfig.cc default
385 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
386
387 if (node->children.concurrency != 0)
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 // TODO: All other helpers allow temporary overload. Should not we?
632 if (!acl->def->theHelper->willOverload()) {
633 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
634 if (!ch->goAsync(ExternalACLLookup::Instance()))
635 debugs(82, 2, "\"" << key << "\": no async support!");
636 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
637 return ACCESS_DUNNO; // expired cached or simply absent entry
638 } else {
639 if (!staleEntry) {
640 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
641 "' queue full. Request rejected '" << key << "'.");
642 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
643 return ACCESS_DUNNO;
644 } else {
645 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
646 "' queue full. Using stale result. '" << key << "'.");
647 entry = staleEntry;
648 /* Fall thru to processing below */
649 }
650 }
651 }
652 }
653
654 debugs(82, 4, HERE << "entry = { date=" <<
655 (long unsigned int) entry->date <<
656 ", result=" << entry->result <<
657 " tag=" << entry->tag <<
658 " log=" << entry->log << " }");
659 #if USE_AUTH
660 debugs(82, 4, HERE << "entry user=" << entry->user);
661 #endif
662
663 external_acl_cache_touch(acl->def, entry);
664 external_acl_message = entry->message.termedBuf();
665
666 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
667 copyResultsFromEntry(ch->request, entry);
668 return entry->result;
669 }
670
671 int
672 ACLExternal::match(ACLChecklist *checklist)
673 {
674 allow_t answer = aclMatchExternal(data, Filled(checklist));
675
676 // convert to tri-state ACL match 1,0,-1
677 switch (answer) {
678 case ACCESS_ALLOWED:
679 return 1; // match
680
681 case ACCESS_DENIED:
682 return 0; // non-match
683
684 case ACCESS_DUNNO:
685 case ACCESS_AUTH_REQUIRED:
686 default:
687 // If the answer is not allowed or denied (matches/not matches) and
688 // async authentication is not in progress, then we are done.
689 if (checklist->keepMatching())
690 checklist->markFinished(answer, "aclMatchExternal exception");
691 return -1; // other
692 }
693 }
694
695 SBufList
696 ACLExternal::dump() const
697 {
698 external_acl_data const *acl = data;
699 SBufList rv;
700 rv.push_back(SBuf(acl->def->name));
701
702 for (wordlist *arg = acl->arguments; arg; arg = arg->next) {
703 SBuf s;
704 s.Printf(" %s", arg->key);
705 rv.push_back(s);
706 }
707
708 return rv;
709 }
710
711 /******************************************************************
712 * external_acl cache
713 */
714
715 static void
716 external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry)
717 {
718 // this must not be done when nothing is being cached.
719 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
720 return;
721
722 dlinkDelete(&entry->lru, &def->lru_list);
723 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
724 dlinkAdd(e, &entry->lru, &def->lru_list);
725 }
726
727 static char *
728 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
729 {
730 static MemBuf mb;
731 mb.reset();
732
733 // check for special case tokens in the format
734 for (Format::Token *t = acl_data->def->format.format; t ; t = t->next) {
735
736 if (t->type == Format::LFT_EXT_ACL_NAME) {
737 // setup for %ACL
738 safe_free(ch->al->lastAclName);
739 ch->al->lastAclName = xstrdup(acl_data->name);
740 }
741
742 if (t->type == Format::LFT_EXT_ACL_DATA) {
743 // setup string for %DATA
744 SBuf sb;
745 for (auto arg = acl_data->arguments; arg; arg = arg->next) {
746 if (sb.length())
747 sb.append(" ", 1);
748
749 if (acl_data->def->quote == Format::LOG_QUOTE_URL) {
750 const char *quoted = rfc1738_escape(arg->key);
751 sb.append(quoted, strlen(quoted));
752 } else {
753 static MemBuf mb2;
754 mb2.init();
755 strwordquote(&mb2, arg->key);
756 sb.append(mb2.buf, mb2.size);
757 mb2.clean();
758 }
759 }
760
761 ch->al->lastAclData = sb;
762 }
763
764 #if USE_IDENT
765 if (t->type == Format::LFT_USER_IDENT) {
766 if (!*ch->rfc931) {
767 // if we fail to go async, we still return NULL and the caller
768 // will detect the failure in ACLExternal::match().
769 (void)ch->goAsync(IdentLookup::Instance());
770 return NULL;
771 }
772 }
773 #endif
774 }
775
776 // assemble the full helper lookup string
777 acl_data->def->format.assemble(mb, ch->al, 0);
778
779 return mb.buf;
780 }
781
782 static int
783 external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry)
784 {
785 if (def->cache_size <= 0)
786 return 1;
787
788 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
789 return 1;
790 else
791 return 0;
792 }
793
794 static int
795 external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry)
796 {
797 if (def->cache_size <= 0)
798 return 1;
799
800 int ttl;
801 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
802 ttl = (ttl * (100 - def->grace)) / 100;
803
804 if (entry->date + ttl <= squid_curtime)
805 return 1;
806 else
807 return 0;
808 }
809
810 static ExternalACLEntryPointer
811 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
812 {
813 ExternalACLEntryPointer entry;
814
815 // do not bother caching this result if TTL is going to expire it immediately
816 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
817 debugs(82,6, HERE);
818 entry = new ExternalACLEntry;
819 entry->key = xstrdup(key);
820 entry->update(data);
821 entry->def = def;
822 return entry;
823 }
824
825 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
826 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
827
828 if (entry != NULL) {
829 debugs(82, 3, "updating existing entry");
830 entry->update(data);
831 external_acl_cache_touch(def, entry);
832 return entry;
833 }
834
835 entry = new ExternalACLEntry;
836 entry->key = xstrdup(key);
837 entry->update(data);
838
839 def->add(entry);
840
841 return entry;
842 }
843
844 static void
845 external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry)
846 {
847 assert(entry != NULL);
848 assert(def->cache_size > 0 && entry->def == def);
849 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
850 hash_remove_link(def->cache, e);
851 dlinkDelete(&e->lru, &def->lru_list);
852 e->unlock(); // unlock on behalf of the hash
853 def->cache_entries -= 1;
854 }
855
856 /******************************************************************
857 * external_acl helpers
858 */
859
860 class externalAclState
861 {
862 CBDATA_CLASS(externalAclState);
863
864 public:
865 externalAclState(external_acl* aDef, const char *aKey) :
866 callback(NULL),
867 callback_data(NULL),
868 key(xstrdup(aKey)),
869 def(cbdataReference(aDef)),
870 queue(NULL)
871 {}
872 ~externalAclState();
873
874 EAH *callback;
875 void *callback_data;
876 char *key;
877 external_acl *def;
878 dlink_node list;
879 externalAclState *queue;
880 };
881
882 CBDATA_CLASS_INIT(externalAclState);
883
884 externalAclState::~externalAclState()
885 {
886 xfree(key);
887 cbdataReferenceDone(callback_data);
888 cbdataReferenceDone(def);
889 }
890
891 /*
892 * The helper program receives queries on stdin, one
893 * per line, and must return the result on on stdout
894 *
895 * General result syntax:
896 *
897 * OK/ERR keyword=value ...
898 *
899 * Keywords:
900 *
901 * user= The users name (login)
902 * message= Message describing the reason
903 * tag= A string tag to be applied to the request that triggered the acl match.
904 * applies to both OK and ERR responses.
905 * Won't override existing request tags.
906 * log= A string to be used in access logging
907 *
908 * Other keywords may be added to the protocol later
909 *
910 * value needs to be URL-encoded or enclosed in double quotes (")
911 * with \-escaping on any whitespace, quotes, or slashes (\).
912 */
913 static void
914 externalAclHandleReply(void *data, const Helper::Reply &reply)
915 {
916 externalAclState *state = static_cast<externalAclState *>(data);
917 externalAclState *next;
918 ExternalACLEntryData entryData;
919 entryData.result = ACCESS_DENIED;
920
921 debugs(82, 2, HERE << "reply=" << reply);
922
923 if (reply.result == Helper::Okay)
924 entryData.result = ACCESS_ALLOWED;
925 // XXX: handle other non-DENIED results better
926
927 // XXX: make entryData store a proper Helper::Reply object instead of copying.
928
929 entryData.notes.append(&reply.notes);
930
931 const char *label = reply.notes.findFirst("tag");
932 if (label != NULL && *label != '\0')
933 entryData.tag = label;
934
935 label = reply.notes.findFirst("message");
936 if (label != NULL && *label != '\0')
937 entryData.message = label;
938
939 label = reply.notes.findFirst("log");
940 if (label != NULL && *label != '\0')
941 entryData.log = label;
942
943 #if USE_AUTH
944 label = reply.notes.findFirst("user");
945 if (label != NULL && *label != '\0')
946 entryData.user = label;
947
948 label = reply.notes.findFirst("password");
949 if (label != NULL && *label != '\0')
950 entryData.password = label;
951 #endif
952
953 dlinkDelete(&state->list, &state->def->queue);
954
955 ExternalACLEntryPointer entry;
956 if (cbdataReferenceValid(state->def)) {
957 // only cache OK and ERR results.
958 if (reply.result == Helper::Okay || reply.result == Helper::Error)
959 entry = external_acl_cache_add(state->def, state->key, entryData);
960 else {
961 const ExternalACLEntryPointer oldentry = static_cast<ExternalACLEntry *>(hash_lookup(state->def->cache, state->key));
962
963 if (oldentry != NULL)
964 external_acl_cache_delete(state->def, oldentry);
965 }
966 }
967
968 do {
969 void *cbdata;
970 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
971 state->callback(cbdata, entry);
972
973 next = state->queue;
974 state->queue = NULL;
975
976 delete state;
977
978 state = next;
979 } while (state);
980 }
981
982 void
983 ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
984 {
985 ExternalACLLookup::Start(checklist, me->data, false);
986 }
987
988 void
989 ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
990 {
991 external_acl *def = acl->def;
992
993 ACLFilledChecklist *ch = Filled(checklist);
994 const char *key = makeExternalAclKey(ch, acl);
995 assert(key); // XXX: will fail if EXT_ACL_IDENT case needs an async lookup
996
997 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
998 def->name << "' for '" << key << "'");
999
1000 /* Check for a pending lookup to hook into */
1001 // only possible if we are caching results.
1002 externalAclState *oldstate = NULL;
1003 if (def->cache_size > 0) {
1004 for (dlink_node *node = def->queue.head; node; node = node->next) {
1005 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1006
1007 if (strcmp(key, oldstatetmp->key) == 0) {
1008 oldstate = oldstatetmp;
1009 break;
1010 }
1011 }
1012 }
1013
1014 // A background refresh has no need to piggiback on a pending request:
1015 // When the pending request completes, the cache will be refreshed anyway.
1016 if (oldstate && inBackground) {
1017 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
1018 return;
1019 }
1020
1021 externalAclState *state = new externalAclState(def, key);
1022
1023 if (!inBackground) {
1024 state->callback = &ExternalACLLookup::LookupDone;
1025 state->callback_data = cbdataReference(checklist);
1026 }
1027
1028 if (oldstate) {
1029 /* Hook into pending lookup */
1030 state->queue = oldstate->queue;
1031 oldstate->queue = state;
1032 } else {
1033 /* No pending lookup found. Sumbit to helper */
1034
1035 MemBuf buf;
1036 buf.init();
1037 buf.appendf("%s\n", key);
1038 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1039
1040 if (!def->theHelper->trySubmit(buf.buf, externalAclHandleReply, state)) {
1041 debugs(82, 7, HERE << "'" << def->name << "' submit to helper failed");
1042 assert(inBackground); // or the caller should have checked
1043 delete state;
1044 return;
1045 }
1046
1047 dlinkAdd(state, &state->list, &def->queue);
1048
1049 buf.clean();
1050 }
1051
1052 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1053 "' in '" << def->name << "' (ch=" << ch << ").");
1054 }
1055
1056 static void
1057 externalAclStats(StoreEntry * sentry)
1058 {
1059 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1060 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1061 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1062 assert(p->theHelper);
1063 p->theHelper->packStatsInto(sentry);
1064 storeAppendPrintf(sentry, "\n");
1065 }
1066 }
1067
1068 static void
1069 externalAclRegisterWithCacheManager(void)
1070 {
1071 Mgr::RegisterAction("external_acl",
1072 "External ACL stats",
1073 externalAclStats, 0, 1);
1074 }
1075
1076 void
1077 externalAclInit(void)
1078 {
1079 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1080 if (!p->cache)
1081 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1082
1083 if (!p->theHelper)
1084 p->theHelper = new helper(p->name);
1085
1086 p->theHelper->cmdline = p->cmdline;
1087
1088 p->theHelper->childs.updateLimits(p->children);
1089
1090 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1091
1092 p->theHelper->addr = p->local_addr;
1093
1094 helperOpenServers(p->theHelper);
1095 }
1096
1097 externalAclRegisterWithCacheManager();
1098 }
1099
1100 void
1101 externalAclShutdown(void)
1102 {
1103 external_acl *p;
1104
1105 for (p = Config.externalAclHelperList; p; p = p->next) {
1106 helperShutdown(p->theHelper);
1107 }
1108 }
1109
1110 ExternalACLLookup ExternalACLLookup::instance_;
1111 ExternalACLLookup *
1112 ExternalACLLookup::Instance()
1113 {
1114 return &instance_;
1115 }
1116
1117 void
1118 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1119 {
1120 /* TODO: optimise this - we probably have a pointer to this
1121 * around somewhere */
1122 ACL *acl = ACL::FindByName(AclMatchedName);
1123 assert(acl);
1124 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1125 assert (me);
1126 ACLExternal::ExternalAclLookup(checklist, me);
1127 }
1128
1129 /// Called when an async lookup returns
1130 void
1131 ExternalACLLookup::LookupDone(void *data, const ExternalACLEntryPointer &result)
1132 {
1133 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
1134 checklist->extacl_entry = result;
1135 checklist->resumeNonBlockingCheck(ExternalACLLookup::Instance());
1136 }
1137
1138 /* This registers "external" in the registry. To do dynamic definitions
1139 * of external ACL's, rather than a static prototype, have a Prototype instance
1140 * prototype in the class that defines each external acl 'class'.
1141 * Then, then the external acl instance is created, it self registers under
1142 * it's name.
1143 * Be sure that clone is fully functional for that acl class though!
1144 */
1145 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1146
1147 ACLExternal ACLExternal::RegistryEntry_("external");
1148
1149 ACL *
1150 ACLExternal::clone() const
1151 {
1152 return new ACLExternal(*this);
1153 }
1154
1155 ACLExternal::ACLExternal(char const *theClass) : data(NULL), class_(xstrdup(theClass))
1156 {}
1157
1158 ACLExternal::ACLExternal(ACLExternal const & old) : data(NULL), class_(old.class_ ? xstrdup(old.class_) : NULL)
1159 {
1160 /* we don't have copy constructors for the data yet */
1161 assert(!old.data);
1162 }
1163
1164 char const *
1165 ACLExternal::typeString() const
1166 {
1167 return class_;
1168 }
1169
1170 bool
1171 ACLExternal::isProxyAuth() const
1172 {
1173 #if USE_AUTH
1174 return data->def->require_auth;
1175 #else
1176 return false;
1177 #endif
1178 }
1179