2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 82 External ACL */
13 #include "acl/FilledChecklist.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"
22 #include "format/Token.h"
24 #include "helper/Reply.h"
25 #include "http/Stream.h"
26 #include "HttpHeaderTools.h"
27 #include "HttpReply.h"
28 #include "HttpRequest.h"
31 #include "mgr/Registration.h"
33 #include "SquidConfig.h"
34 #include "SquidString.h"
39 #include "ssl/ServerBump.h"
40 #include "ssl/support.h"
44 #include "auth/Gadgets.h"
45 #include "auth/UserRequest.h"
48 #ifndef DEFAULT_EXTERNAL_ACL_TTL
49 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
51 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
52 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
55 static void external_acl_cache_delete(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
56 static int external_acl_entry_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
57 static int external_acl_grace_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
58 static void external_acl_cache_touch(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
59 static ExternalACLEntryPointer
external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const &data
);
61 /******************************************************************
62 * external_acl directive
67 /* XXX: These are not really cbdata, but it is an easy way
68 * to get them pooled, refcounted, accounted and freed properly...
69 * Use RefCountable MEMPROXY_CLASS instead
71 CBDATA_CLASS(external_acl
);
79 void add(const ExternalACLEntryPointer
&);
83 bool maybeCacheable(const Acl::Answer
&) const;
93 Format::Format format
;
97 Helper::ChildConfig children
;
99 Helper::Client::Pointer theHelper
;
113 * Configuration flag. May only be altered by the configuration parser.
115 * Indicates that all uses of this external_acl_type helper require authentication
116 * details to be processed. If none are available its a fail match.
121 Format::Quoting quote
; // default quoting to use, set by protocol= parameter
123 Ip::Address local_addr
;
126 CBDATA_CLASS_INIT(external_acl
);
128 external_acl::external_acl() :
130 ttl(DEFAULT_EXTERNAL_ACL_TTL
),
134 format("external_acl_type"),
136 children(DEFAULT_EXTERNAL_ACL_CHILDREN
),
139 cache_size(256*1024),
144 quote(Format::LOG_QUOTE_URL
)
146 local_addr
.setLocalhost();
149 external_acl::~external_acl()
152 wordlistDestroy(&cmdline
);
155 helperShutdown(theHelper
);
159 while (lru_list
.tail
) {
160 ExternalACLEntryPointer
e(static_cast<ExternalACLEntry
*>(lru_list
.tail
->data
));
161 external_acl_cache_delete(this, e
);
164 hashFreeMemory(cache
);
167 external_acl
*node
= next
;
169 node
->next
= nullptr; // prevent recursion
175 parse_externalAclHelper(external_acl
** list
)
177 char *token
= ConfigParser::NextToken();
184 external_acl
*a
= new external_acl
;
185 a
->name
= xstrdup(token
);
187 // Allow supported %macros inside quoted tokens
188 ConfigParser::EnableMacros();
189 token
= ConfigParser::NextToken();
193 if (strncmp(token
, "ttl=", 4) == 0) {
194 a
->ttl
= atoi(token
+ 4);
195 } else if (strncmp(token
, "negative_ttl=", 13) == 0) {
196 a
->negative_ttl
= atoi(token
+ 13);
197 } else if (strncmp(token
, "children=", 9) == 0) {
198 a
->children
.n_max
= atoi(token
+ 9);
199 debugs(0, DBG_CRITICAL
, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
200 } else if (strncmp(token
, "children-max=", 13) == 0) {
201 a
->children
.n_max
= atoi(token
+ 13);
202 } else if (strncmp(token
, "children-startup=", 17) == 0) {
203 a
->children
.n_startup
= atoi(token
+ 17);
204 } else if (strncmp(token
, "children-idle=", 14) == 0) {
205 a
->children
.n_idle
= atoi(token
+ 14);
206 } else if (strncmp(token
, "concurrency=", 12) == 0) {
207 a
->children
.concurrency
= atoi(token
+ 12);
208 } else if (strncmp(token
, "queue-size=", 11) == 0) {
209 a
->children
.queue_size
= atoi(token
+ 11);
210 a
->children
.defaultQueueSize
= false;
211 } else if (strncmp(token
, "cache=", 6) == 0) {
212 a
->cache_size
= atoi(token
+ 6);
213 } else if (strncmp(token
, "grace=", 6) == 0) {
214 a
->grace
= atoi(token
+ 6);
215 } else if (strcmp(token
, "protocol=2.5") == 0) {
216 a
->quote
= Format::LOG_QUOTE_SHELL
;
217 } else if (strcmp(token
, "protocol=3.0") == 0) {
218 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option protocol=3.0 is deprecated. Remove this from your config.");
219 a
->quote
= Format::LOG_QUOTE_URL
;
220 } else if (strcmp(token
, "quote=url") == 0) {
221 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
222 a
->quote
= Format::LOG_QUOTE_URL
;
223 } else if (strcmp(token
, "quote=shell") == 0) {
224 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
225 a
->quote
= Format::LOG_QUOTE_SHELL
;
227 /* INET6: allow admin to configure some helpers explicitly to
228 bind to IPv4/v6 localhost port. */
229 } else if (strcmp(token
, "ipv4") == 0) {
230 if ( !a
->local_addr
.setIPv4() ) {
231 debugs(3, DBG_CRITICAL
, "WARNING: Error converting " << a
->local_addr
<< " to IPv4 in " << a
->name
);
233 } else if (strcmp(token
, "ipv6") == 0) {
235 debugs(3, DBG_CRITICAL
, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a
->name
);
236 // else nothing to do.
241 token
= ConfigParser::NextToken();
243 ConfigParser::DisableMacros();
245 /* check that child startup value is sane. */
246 if (a
->children
.n_startup
> a
->children
.n_max
)
247 a
->children
.n_startup
= a
->children
.n_max
;
249 /* check that child idle value is sane. */
250 if (a
->children
.n_idle
> a
->children
.n_max
)
251 a
->children
.n_idle
= a
->children
.n_max
;
252 if (a
->children
.n_idle
< 1)
253 a
->children
.n_idle
= 1;
255 if (a
->negative_ttl
== -1)
256 a
->negative_ttl
= a
->ttl
;
258 if (a
->children
.defaultQueueSize
)
259 a
->children
.queue_size
= 2 * a
->children
.n_max
;
261 /* Legacy external_acl_type format parser.
262 * Handles a series of %... tokens where any non-% means
263 * the start of another parameter field (ie the path to binary).
265 enum Format::Quoting quote
= Format::LOG_QUOTE_NONE
;
266 Format::Token
**fmt
= &a
->format
.format
;
267 bool data_used
= false;
269 /* stop on first non-% token found */
273 *fmt
= new Format::Token
;
274 // these tokens are whitespace delimited
275 (*fmt
)->space
= true;
277 // set the default encoding to match the protocol= config
278 // this will be overridden by explicit %macro attributes
279 (*fmt
)->quote
= a
->quote
;
281 // compatibility for old tokens incompatible with Format::Token syntax
282 #if USE_OPENSSL // do not bother unless we have to.
283 if (strncmp(token
, "%USER_CERT_", 11) == 0) {
284 (*fmt
)->type
= Format::LFT_EXT_ACL_USER_CERT
;
285 (*fmt
)->data
.string
= xstrdup(token
+ 11);
286 (*fmt
)->data
.header
.header
= (*fmt
)->data
.string
;
287 } else if (strncmp(token
, "%USER_CA_CERT_", 14) == 0) {
288 (*fmt
)->type
= Format::LFT_EXT_ACL_USER_CA_CERT
;
289 (*fmt
)->data
.string
= xstrdup(token
+ 14);
290 (*fmt
)->data
.header
.header
= (*fmt
)->data
.string
;
291 } else if (strncmp(token
, "%CA_CERT_", 9) == 0) {
292 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead");
293 (*fmt
)->type
= Format::LFT_EXT_ACL_USER_CA_CERT
;
294 (*fmt
)->data
.string
= xstrdup(token
+ 9);
295 (*fmt
)->data
.header
.header
= (*fmt
)->data
.string
;
298 if (strncmp(token
,"%<{", 3) == 0) {
301 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: external_acl_type format %<{...} is deprecated. Use " << tmp
);
302 const size_t parsedLen
= (*fmt
)->parse(tmp
.c_str(), "e
);
303 assert(parsedLen
== tmp
.length());
304 assert((*fmt
)->type
== Format::LFT_REPLY_HEADER
||
305 (*fmt
)->type
== Format::LFT_REPLY_HEADER_ELEM
);
307 } else if (strncmp(token
,"%>{", 3) == 0) {
310 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: external_acl_type format %>{...} is deprecated. Use " << tmp
);
311 const size_t parsedLen
= (*fmt
)->parse(tmp
.c_str(), "e
);
312 assert(parsedLen
== tmp
.length());
313 assert((*fmt
)->type
== Format::LFT_ADAPTED_REQUEST_HEADER
||
314 (*fmt
)->type
== Format::LFT_ADAPTED_REQUEST_HEADER_ELEM
);
317 // we can use the Format::Token::parse() method since it
318 // only pulls off one token. Since we already checked
319 // for '%' prefix above this is guaranteed to be a token.
320 const size_t len
= (*fmt
)->parse(token
, "e
);
321 assert(len
== strlen(token
));
324 // process special token-specific actions (only if necessary)
326 if ((*fmt
)->type
== Format::LFT_USER_LOGIN
)
327 a
->require_auth
= true;
330 if ((*fmt
)->type
== Format::LFT_EXT_ACL_DATA
)
333 fmt
= &((*fmt
)->next
);
334 token
= ConfigParser::NextToken();
337 /* There must be at least one format token */
338 if (!a
->format
.format
) {
344 // format has implicit %DATA on the end if not used explicitly
346 *fmt
= new Format::Token
;
347 (*fmt
)->type
= Format::LFT_EXT_ACL_DATA
;
348 (*fmt
)->quote
= Format::LOG_QUOTE_NONE
;
358 wordlistAdd(&a
->cmdline
, token
);
361 parse_wordlist(&a
->cmdline
);
364 list
= &(*list
)->next
;
370 dump_externalAclHelper(StoreEntry
* sentry
, const char *name
, const external_acl
* list
)
372 const external_acl
*node
;
373 const wordlist
*word
;
375 for (node
= list
; node
; node
= node
->next
) {
376 storeAppendPrintf(sentry
, "%s %s", name
, node
->name
);
378 if (!node
->local_addr
.isIPv6())
379 storeAppendPrintf(sentry
, " ipv4");
381 storeAppendPrintf(sentry
, " ipv6");
383 if (node
->ttl
!= DEFAULT_EXTERNAL_ACL_TTL
)
384 storeAppendPrintf(sentry
, " ttl=%d", node
->ttl
);
386 if (node
->negative_ttl
!= node
->ttl
)
387 storeAppendPrintf(sentry
, " negative_ttl=%d", node
->negative_ttl
);
390 storeAppendPrintf(sentry
, " grace=%d", node
->grace
);
392 if (node
->children
.n_max
!= DEFAULT_EXTERNAL_ACL_CHILDREN
)
393 storeAppendPrintf(sentry
, " children-max=%d", node
->children
.n_max
);
395 if (node
->children
.n_startup
!= 0) // sync with helper/ChildConfig.cc default
396 storeAppendPrintf(sentry
, " children-startup=%d", node
->children
.n_startup
);
398 if (node
->children
.n_idle
!= 1) // sync with helper/ChildConfig.cc default
399 storeAppendPrintf(sentry
, " children-idle=%d", node
->children
.n_idle
);
401 if (node
->children
.concurrency
!= 0)
402 storeAppendPrintf(sentry
, " concurrency=%d", node
->children
.concurrency
);
405 storeAppendPrintf(sentry
, " cache=%d", node
->cache_size
);
407 if (node
->quote
== Format::LOG_QUOTE_SHELL
)
408 storeAppendPrintf(sentry
, " protocol=2.5");
410 node
->format
.dump(sentry
, nullptr, false);
412 for (word
= node
->cmdline
; word
; word
= word
->next
)
413 storeAppendPrintf(sentry
, " %s", word
->key
);
415 storeAppendPrintf(sentry
, "\n");
420 free_externalAclHelper(external_acl
** list
)
426 static external_acl
*
427 find_externalAclHelper(const char *name
)
431 for (node
= Config
.externalAclHelperList
; node
; node
= node
->next
) {
432 if (strcmp(node
->name
, name
) == 0)
440 external_acl::add(const ExternalACLEntryPointer
&anEntry
)
443 assert(anEntry
!= nullptr);
444 assert (anEntry
->def
== nullptr);
446 ExternalACLEntry
*e
= const_cast<ExternalACLEntry
*>(anEntry
.getRaw()); // XXX: make hash a std::map of Pointer.
448 dlinkAdd(e
, &e
->lru
, &lru_list
);
449 e
->lock(); //cbdataReference(e); // lock it on behalf of the hash
454 external_acl::trimCache()
456 if (cache_size
&& cache_entries
>= cache_size
) {
457 ExternalACLEntryPointer
e(static_cast<ExternalACLEntry
*>(lru_list
.tail
->data
));
458 external_acl_cache_delete(this, e
);
463 external_acl::maybeCacheable(const Acl::Answer
&result
) const
466 return false; // cache is disabled
468 if (result
== ACCESS_DUNNO
)
469 return false; // non-cacheable response
471 if ((result
.allowed() ? ttl
: negative_ttl
) <= 0)
472 return false; // not caching this type of response
477 /******************************************************************
481 class external_acl_data
483 CBDATA_CLASS(external_acl_data
);
486 explicit external_acl_data(external_acl
* const aDef
): def(cbdataReference(aDef
)), arguments(nullptr) {}
487 ~external_acl_data();
494 CBDATA_CLASS_INIT(external_acl_data
);
496 external_acl_data::~external_acl_data()
498 wordlistDestroy(&arguments
);
499 cbdataReferenceDone(def
);
510 char *token
= ConfigParser::strtokFile();
517 data
= new external_acl_data(find_externalAclHelper(token
));
525 // def->name is the name of the external_acl_type.
526 // this is the name of the 'acl' directive being tested
529 while ((token
= ConfigParser::strtokFile())) {
530 wordlistAdd(&data
->arguments
, token
);
535 ACLExternal::valid () const
538 if (data
->def
->require_auth
) {
539 if (authenticateSchemeCount() == 0) {
540 debugs(28, DBG_CRITICAL
, "ERROR: Cannot use proxy auth because no authentication schemes were compiled.");
544 if (authenticateActiveSchemeCount() == 0) {
545 debugs(28, DBG_CRITICAL
, "ERROR: Cannot use proxy auth because no authentication schemes are fully configured.");
555 ACLExternal::empty () const
560 ACLExternal::~ACLExternal()
567 copyResultsFromEntry(const HttpRequest::Pointer
&req
, const ExternalACLEntryPointer
&entry
)
571 if (entry
->user
.size())
572 req
->extacl_user
= entry
->user
;
574 if (entry
->password
.size())
575 req
->extacl_passwd
= entry
->password
;
577 if (!req
->tag
.size())
578 req
->tag
= entry
->tag
;
580 if (entry
->log
.size())
581 req
->extacl_log
= entry
->log
;
583 if (entry
->message
.size())
584 req
->extacl_message
= entry
->message
;
586 // attach the helper kv-pair to the transaction
587 UpdateRequestNotes(req
->clientConnectionManager
.get(), *req
, entry
->notes
);
591 // TODO: Diff reduction. Rename this helper method to match_() or similar.
593 ACLExternal::aclMatchExternal(external_acl_data
*acl
, ACLFilledChecklist
*ch
) const
595 debugs(82, 9, "acl=\"" << acl
->def
->name
<< "\"");
596 ExternalACLEntryPointer entry
= ch
->extacl_entry
;
598 external_acl_message
= "MISSING REQUIRED INFORMATION";
600 if (entry
!= nullptr) {
601 if (entry
->def
== acl
->def
) {
602 /* Ours, use it.. if the key matches */
603 const char *key
= makeExternalAclKey(ch
, acl
);
605 return ACCESS_DUNNO
; // insufficient data to continue
606 if (strcmp(key
, (char*)entry
->key
) != 0) {
607 debugs(82, 9, "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "' do not match. Discarded.");
608 // too bad. need a new lookup.
609 entry
= ch
->extacl_entry
= nullptr;
612 /* Not ours.. get rid of it */
613 debugs(82, 9, "entry " << entry
<< " not valid or not ours. Discarded.");
614 if (entry
!= nullptr) {
615 debugs(82, 9, "entry def=" << entry
->def
<< ", our def=" << acl
->def
);
616 const char *key
= makeExternalAclKey(ch
, acl
); // may be nil
617 debugs(82, 9, "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "'");
619 entry
= ch
->extacl_entry
= nullptr;
624 debugs(82, 9, "No helper entry available");
626 if (acl
->def
->require_auth
) {
627 /* Make sure the user is authenticated */
628 debugs(82, 3, acl
->def
->name
<< " check user authenticated.");
629 const auto ti
= AuthenticateAcl(ch
, *this);
631 debugs(82, 2, acl
->def
->name
<< " user not authenticated (" << ti
<< ")");
634 debugs(82, 3, acl
->def
->name
<< " user is authenticated.");
637 const char *key
= makeExternalAclKey(ch
, acl
);
640 /* Not sufficient data to process */
644 entry
= static_cast<ExternalACLEntry
*>(hash_lookup(acl
->def
->cache
, key
));
646 const ExternalACLEntryPointer staleEntry
= entry
;
647 if (entry
!= nullptr && external_acl_entry_expired(acl
->def
, entry
))
650 if (entry
!= nullptr && external_acl_grace_expired(acl
->def
, entry
)) {
651 // refresh in the background
652 startLookup(ch
, acl
, true);
653 debugs(82, 4, "no need to wait for the refresh of '" <<
654 key
<< "' in '" << acl
->def
->name
<< "' (ch=" << ch
<< ").");
658 debugs(82, 2, acl
->def
->name
<< "(\"" << key
<< "\") = lookup needed");
660 // TODO: All other helpers allow temporary overload. Should not we?
661 if (!acl
->def
->theHelper
->willOverload()) {
662 debugs(82, 2, "\"" << key
<< "\": queueing a call.");
663 if (!ch
->goAsync(StartLookup
, *this))
664 debugs(82, 2, "\"" << key
<< "\": no async support!");
665 debugs(82, 2, "\"" << key
<< "\": return -1.");
666 return ACCESS_DUNNO
; // expired cached or simply absent entry
669 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
670 "' queue full. Request rejected '" << key
<< "'.");
671 external_acl_message
= "SYSTEM TOO BUSY, TRY AGAIN LATER";
674 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
675 "' queue full. Using stale result. '" << key
<< "'.");
677 /* Fall thru to processing below */
683 debugs(82, 4, "entry = { date=" <<
684 (long unsigned int) entry
->date
<<
685 ", result=" << entry
->result
<<
686 " tag=" << entry
->tag
<<
687 " log=" << entry
->log
<< " }");
689 debugs(82, 4, "entry user=" << entry
->user
);
692 external_acl_cache_touch(acl
->def
, entry
);
693 external_acl_message
= entry
->message
.termedBuf();
695 debugs(82, 2, acl
->def
->name
<< " = " << entry
->result
);
696 copyResultsFromEntry(ch
->request
, entry
);
697 return entry
->result
;
701 ACLExternal::match(ACLChecklist
*checklist
)
703 auto answer
= aclMatchExternal(data
, Filled(checklist
));
705 // convert to tri-state ACL match 1,0,-1
711 return 0; // non-match
714 case ACCESS_AUTH_REQUIRED
:
716 // If the answer is not allowed or denied (matches/not matches) and
717 // async authentication is not in progress, then we are done.
718 if (checklist
->keepMatching())
719 checklist
->markFinished(answer
, "aclMatchExternal exception");
725 ACLExternal::dump() const
727 external_acl_data
const *acl
= data
;
729 rv
.push_back(SBuf(acl
->def
->name
));
731 for (wordlist
*arg
= acl
->arguments
; arg
; arg
= arg
->next
) {
733 s
.Printf(" %s", arg
->key
);
740 /******************************************************************
745 external_acl_cache_touch(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
747 // this must not be done when nothing is being cached.
748 if (!def
->maybeCacheable(entry
->result
))
751 dlinkDelete(&entry
->lru
, &def
->lru_list
);
752 ExternalACLEntry
*e
= const_cast<ExternalACLEntry
*>(entry
.getRaw()); // XXX: make hash a std::map of Pointer.
753 dlinkAdd(e
, &entry
->lru
, &def
->lru_list
);
757 ACLExternal::makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
) const
762 // check for special case tokens in the format
763 for (Format::Token
*t
= acl_data
->def
->format
.format
; t
; t
= t
->next
) {
765 if (t
->type
== Format::LFT_EXT_ACL_NAME
) {
767 ch
->al
->lastAclName
= acl_data
->name
;
770 if (t
->type
== Format::LFT_EXT_ACL_DATA
) {
771 // setup string for %DATA
773 for (auto arg
= acl_data
->arguments
; arg
; arg
= arg
->next
) {
777 if (acl_data
->def
->quote
== Format::LOG_QUOTE_URL
) {
778 const char *quoted
= rfc1738_escape(arg
->key
);
779 sb
.append(quoted
, strlen(quoted
));
783 strwordquote(&mb2
, arg
->key
);
784 sb
.append(mb2
.buf
, mb2
.size
);
789 ch
->al
->lastAclData
= sb
;
793 // assemble the full helper lookup string
794 acl_data
->def
->format
.assemble(mb
, ch
->al
, 0);
800 external_acl_entry_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
802 if (def
->cache_size
<= 0 || entry
->result
== ACCESS_DUNNO
)
805 if (entry
->date
+ (entry
->result
.allowed() ? def
->ttl
: def
->negative_ttl
) < squid_curtime
)
812 external_acl_grace_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
814 if (def
->cache_size
<= 0 || entry
->result
== ACCESS_DUNNO
)
818 ttl
= entry
->result
.allowed() ? def
->ttl
: def
->negative_ttl
;
819 ttl
= (ttl
* (100 - def
->grace
)) / 100;
821 if (entry
->date
+ ttl
<= squid_curtime
)
827 static ExternalACLEntryPointer
828 external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const & data
)
830 ExternalACLEntryPointer entry
;
832 if (!def
->maybeCacheable(data
.result
)) {
833 debugs(82,6, MYNAME
);
835 if (data
.result
== ACCESS_DUNNO
) {
836 if (const ExternalACLEntryPointer oldentry
= static_cast<ExternalACLEntry
*>(hash_lookup(def
->cache
, key
)))
837 external_acl_cache_delete(def
, oldentry
);
839 entry
= new ExternalACLEntry
;
840 entry
->key
= xstrdup(key
);
846 entry
= static_cast<ExternalACLEntry
*>(hash_lookup(def
->cache
, key
));
847 debugs(82, 2, "external_acl_cache_add: Adding '" << key
<< "' = " << data
.result
);
849 if (entry
!= nullptr) {
850 debugs(82, 3, "updating existing entry");
852 external_acl_cache_touch(def
, entry
);
856 entry
= new ExternalACLEntry
;
857 entry
->key
= xstrdup(key
);
866 external_acl_cache_delete(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
868 assert(entry
!= nullptr);
869 assert(def
->cache_size
> 0 && entry
->def
== def
);
870 ExternalACLEntry
*e
= const_cast<ExternalACLEntry
*>(entry
.getRaw()); // XXX: make hash a std::map of Pointer.
871 hash_remove_link(def
->cache
, e
);
872 dlinkDelete(&e
->lru
, &def
->lru_list
);
873 e
->unlock(); // unlock on behalf of the hash
874 def
->cache_entries
-= 1;
877 /******************************************************************
878 * external_acl helpers
881 class externalAclState
883 CBDATA_CLASS(externalAclState
);
886 externalAclState(external_acl
* aDef
, const char *aKey
) :
888 callback_data(nullptr),
890 def(cbdataReference(aDef
)),
900 externalAclState
*queue
;
903 CBDATA_CLASS_INIT(externalAclState
);
905 externalAclState::~externalAclState()
908 cbdataReferenceDone(callback_data
);
909 cbdataReferenceDone(def
);
913 * The helper program receives queries on stdin, one
914 * per line, and must return the result on on stdout
916 * General result syntax:
918 * OK/ERR keyword=value ...
922 * user= The users name (login)
923 * message= Message describing the reason
924 * tag= A string tag to be applied to the request that triggered the acl match.
925 * applies to both OK and ERR responses.
926 * Won't override existing request tags.
927 * log= A string to be used in access logging
929 * Other keywords may be added to the protocol later
931 * value needs to be URL-encoded or enclosed in double quotes (")
932 * with \-escaping on any whitespace, quotes, or slashes (\).
935 externalAclHandleReply(void *data
, const Helper::Reply
&reply
)
937 externalAclState
*state
= static_cast<externalAclState
*>(data
);
938 externalAclState
*next
;
939 ExternalACLEntryData entryData
;
941 debugs(82, 2, "reply=" << reply
);
943 if (reply
.result
== Helper::Okay
)
944 entryData
.result
= ACCESS_ALLOWED
;
945 else if (reply
.result
== Helper::Error
)
946 entryData
.result
= ACCESS_DENIED
;
947 else //BrokenHelper,TimedOut or Unknown. Should not cached.
948 entryData
.result
= ACCESS_DUNNO
;
950 // XXX: make entryData store a proper Helper::Reply object instead of copying.
952 entryData
.notes
.append(&reply
.notes
);
954 const char *label
= reply
.notes
.findFirst("tag");
955 if (label
!= nullptr && *label
!= '\0')
956 entryData
.tag
= label
;
958 label
= reply
.notes
.findFirst("message");
959 if (label
!= nullptr && *label
!= '\0')
960 entryData
.message
= label
;
962 label
= reply
.notes
.findFirst("log");
963 if (label
!= nullptr && *label
!= '\0')
964 entryData
.log
= label
;
967 label
= reply
.notes
.findFirst("user");
968 if (label
!= nullptr && *label
!= '\0')
969 entryData
.user
= label
;
971 label
= reply
.notes
.findFirst("password");
972 if (label
!= nullptr && *label
!= '\0')
973 entryData
.password
= label
;
976 // XXX: This state->def access conflicts with the cbdata validity check
978 dlinkDelete(&state
->list
, &state
->def
->queue
);
980 ExternalACLEntryPointer entry
;
981 if (cbdataReferenceValid(state
->def
))
982 entry
= external_acl_cache_add(state
->def
, state
->key
, entryData
);
986 if (state
->callback
&& cbdataReferenceValidDone(state
->callback_data
, &cbdata
))
987 state
->callback(cbdata
, entry
);
990 state
->queue
= nullptr;
998 /// Asks the helper (if needed) or returns the [cached] result (otherwise).
999 /// Does not support "background" lookups. See also: ACLExternal::Start().
1001 ACLExternal::StartLookup(ACLFilledChecklist
&checklist
, const Acl::Node
&acl
)
1003 const auto &me
= dynamic_cast<const ACLExternal
&>(acl
);
1004 me
.startLookup(&checklist
, me
.data
, false);
1007 // If possible, starts an asynchronous lookup of an external ACL.
1008 // Otherwise, asserts (or bails if background refresh is requested).
1010 ACLExternal::startLookup(ACLFilledChecklist
*ch
, external_acl_data
*acl
, bool inBackground
) const
1012 external_acl
*def
= acl
->def
;
1014 const char *key
= makeExternalAclKey(ch
, acl
);
1017 debugs(82, 2, (inBackground
? "bg" : "fg") << " lookup in '" <<
1018 def
->name
<< "' for '" << key
<< "'");
1020 /* Check for a pending lookup to hook into */
1021 // only possible if we are caching results.
1022 externalAclState
*oldstate
= nullptr;
1023 if (def
->cache_size
> 0) {
1024 for (dlink_node
*node
= def
->queue
.head
; node
; node
= node
->next
) {
1025 externalAclState
*oldstatetmp
= static_cast<externalAclState
*>(node
->data
);
1027 if (strcmp(key
, oldstatetmp
->key
) == 0) {
1028 oldstate
= oldstatetmp
;
1034 // A background refresh has no need to piggiback on a pending request:
1035 // When the pending request completes, the cache will be refreshed anyway.
1036 if (oldstate
&& inBackground
) {
1037 debugs(82, 7, "'" << def
->name
<< "' queue is already being refreshed (ch=" << ch
<< ")");
1041 externalAclState
*state
= new externalAclState(def
, key
);
1043 if (!inBackground
) {
1044 state
->callback
= &LookupDone
;
1045 state
->callback_data
= cbdataReference(ch
);
1049 /* Hook into pending lookup */
1050 state
->queue
= oldstate
->queue
;
1051 oldstate
->queue
= state
;
1053 /* No pending lookup found. Sumbit to helper */
1057 buf
.appendf("%s\n", key
);
1058 debugs(82, 4, "externalAclLookup: looking up for '" << key
<< "' in '" << def
->name
<< "'.");
1060 if (!def
->theHelper
->trySubmit(buf
.buf
, externalAclHandleReply
, state
)) {
1061 debugs(82, 7, "'" << def
->name
<< "' submit to helper failed");
1062 assert(inBackground
); // or the caller should have checked
1067 dlinkAdd(state
, &state
->list
, &def
->queue
);
1072 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key
<<
1073 "' in '" << def
->name
<< "' (ch=" << ch
<< ").");
1077 externalAclStats(StoreEntry
* sentry
)
1079 for (external_acl
*p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1080 storeAppendPrintf(sentry
, "External ACL Statistics: %s\n", p
->name
);
1081 storeAppendPrintf(sentry
, "Cache size: %d\n", p
->cache
->count
);
1082 assert(p
->theHelper
);
1083 p
->theHelper
->packStatsInto(sentry
);
1084 storeAppendPrintf(sentry
, "\n");
1089 externalAclRegisterWithCacheManager(void)
1091 Mgr::RegisterAction("external_acl",
1092 "External ACL stats",
1093 externalAclStats
, 0, 1);
1097 externalAclInit(void)
1099 for (external_acl
*p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1101 p
->cache
= hash_create((HASHCMP
*) strcmp
, hashPrime(1024), hash4
);
1104 p
->theHelper
= Helper::Client::Make("external_acl_type");
1106 p
->theHelper
->cmdline
= p
->cmdline
;
1108 p
->theHelper
->childs
.updateLimits(p
->children
);
1110 p
->theHelper
->ipc_type
= IPC_TCP_SOCKET
;
1112 p
->theHelper
->addr
= p
->local_addr
;
1114 p
->theHelper
->openSessions();
1117 externalAclRegisterWithCacheManager();
1121 externalAclShutdown(void)
1125 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1126 helperShutdown(p
->theHelper
);
1130 /// Called when an async lookup returns
1132 ACLExternal::LookupDone(void *data
, const ExternalACLEntryPointer
&result
)
1134 ACLFilledChecklist
*checklist
= Filled(static_cast<ACLChecklist
*>(data
));
1135 checklist
->extacl_entry
= result
;
1136 checklist
->resumeNonBlockingCheck();
1139 ACLExternal::ACLExternal(char const *theClass
) : data(nullptr), class_(xstrdup(theClass
))
1143 ACLExternal::typeString() const
1149 ACLExternal::isProxyAuth() const
1152 return data
->def
->require_auth
;