2 * Copyright (C) 1996-2015 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 "HttpHeaderTools.h"
26 #include "HttpReply.h"
27 #include "HttpRequest.h"
30 #include "mgr/Registration.h"
32 #include "SquidConfig.h"
33 #include "SquidString.h"
34 #include "SquidTime.h"
40 #include "ssl/ServerBump.h"
41 #include "ssl/support.h"
45 #include "auth/Gadgets.h"
46 #include "auth/UserRequest.h"
49 #include "ident/AclIdent.h"
52 #ifndef DEFAULT_EXTERNAL_ACL_TTL
53 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
55 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
56 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
59 static char *makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
);
60 static void external_acl_cache_delete(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
61 static int external_acl_entry_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
62 static int external_acl_grace_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
63 static void external_acl_cache_touch(external_acl
* def
, const ExternalACLEntryPointer
&entry
);
64 static ExternalACLEntryPointer
external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const &data
);
66 /******************************************************************
67 * external_acl directive
72 /* FIXME: These are not really cbdata, but it is an easy way
73 * to get them pooled, refcounted, accounted and freed properly...
75 CBDATA_CLASS(external_acl
);
83 void add(const ExternalACLEntryPointer
&);
95 Format::Format format
;
99 Helper::ChildConfig children
;
115 * Configuration flag. May only be altered by the configuration parser.
117 * Indicates that all uses of this external_acl_type helper require authentication
118 * details to be processed. If none are available its a fail match.
124 QUOTE_METHOD_SHELL
= 1,
128 Ip::Address local_addr
;
131 CBDATA_CLASS_INIT(external_acl
);
133 external_acl::external_acl() :
135 ttl(DEFAULT_EXTERNAL_ACL_TTL
),
139 format("external_acl_type"),
141 children(DEFAULT_EXTERNAL_ACL_CHILDREN
),
144 cache_size(256*1024),
149 quote(external_acl::QUOTE_METHOD_URL
)
151 local_addr
.setLocalhost();
154 external_acl::~external_acl()
157 wordlistDestroy(&cmdline
);
160 helperShutdown(theHelper
);
165 while (lru_list
.tail
) {
166 ExternalACLEntryPointer
e(static_cast<ExternalACLEntry
*>(lru_list
.tail
->data
));
167 external_acl_cache_delete(this, e
);
170 hashFreeMemory(cache
);
173 external_acl
*node
= next
;
175 node
->next
= NULL
; // prevent recursion
181 parse_externalAclHelper(external_acl
** list
)
183 external_acl
*a
= new external_acl
;
184 char *token
= ConfigParser::NextToken();
189 a
->name
= xstrdup(token
);
191 // Allow supported %macros inside quoted tokens
192 ConfigParser::EnableMacros();
193 token
= ConfigParser::NextToken();
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
= external_acl::QUOTE_METHOD_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
= external_acl::QUOTE_METHOD_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
= external_acl::QUOTE_METHOD_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
= external_acl::QUOTE_METHOD_SHELL
;
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
);
237 } else if (strcmp(token
, "ipv6") == 0) {
239 debugs(3, DBG_CRITICAL
, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a
->name
);
240 // else nothing to do.
245 token
= ConfigParser::NextToken();
247 ConfigParser::DisableMacros();
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
;
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;
259 if (a
->negative_ttl
== -1)
260 a
->negative_ttl
= a
->ttl
;
262 if (a
->children
.defaultQueueSize
)
263 a
->children
.queue_size
= 2 * a
->children
.n_max
;
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).
269 enum Format::Quoting quote
= Format::LOG_QUOTE_NONE
;
270 Format::Token
**fmt
= &a
->format
.format
;
271 bool data_used
= false;
273 /* stop on first non-% token found */
277 *fmt
= new Format::Token
;
278 // these tokens are whitespace delimited
279 (*fmt
)->space
= true;
281 // compatibility for old tokens incompatible with Format::Token syntax
282 #if USE_OPENSSL // dont bother if we dont 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
;
299 // we can use the Format::Token::parse() method since it
300 // only pulls off one token. Since we already checked
301 // for '%' prefix above this is guaranteed to be a token.
302 const size_t len
= (*fmt
)->parse(token
, "e
);
303 assert(len
== strlen(token
));
306 // process special token-specific actions (only if necessary)
308 if ((*fmt
)->type
== Format::LFT_USER_LOGIN
)
309 a
->require_auth
= true;
312 if ((*fmt
)->type
== Format::LFT_EXT_ACL_DATA
)
315 fmt
= &((*fmt
)->next
);
316 token
= ConfigParser::NextToken();
319 /* There must be at least one format token */
320 if (!a
->format
.format
)
323 // format has implicit %DATA on the end if not used explicitly
325 *fmt
= new Format::Token
;
326 (*fmt
)->type
= Format::LFT_EXT_ACL_DATA
;
333 wordlistAdd(&a
->cmdline
, token
);
336 parse_wordlist(&a
->cmdline
);
339 list
= &(*list
)->next
;
345 dump_externalAclHelper(StoreEntry
* sentry
, const char *name
, const external_acl
* list
)
347 const external_acl
*node
;
348 const wordlist
*word
;
350 for (node
= list
; node
; node
= node
->next
) {
351 storeAppendPrintf(sentry
, "%s %s", name
, node
->name
);
353 if (!node
->local_addr
.isIPv6())
354 storeAppendPrintf(sentry
, " ipv4");
356 storeAppendPrintf(sentry
, " ipv6");
358 if (node
->ttl
!= DEFAULT_EXTERNAL_ACL_TTL
)
359 storeAppendPrintf(sentry
, " ttl=%d", node
->ttl
);
361 if (node
->negative_ttl
!= node
->ttl
)
362 storeAppendPrintf(sentry
, " negative_ttl=%d", node
->negative_ttl
);
365 storeAppendPrintf(sentry
, " grace=%d", node
->grace
);
367 if (node
->children
.n_max
!= DEFAULT_EXTERNAL_ACL_CHILDREN
)
368 storeAppendPrintf(sentry
, " children-max=%d", node
->children
.n_max
);
370 if (node
->children
.n_startup
!= 1)
371 storeAppendPrintf(sentry
, " children-startup=%d", node
->children
.n_startup
);
373 if (node
->children
.n_idle
!= (node
->children
.n_max
+ node
->children
.n_startup
) )
374 storeAppendPrintf(sentry
, " children-idle=%d", node
->children
.n_idle
);
376 if (node
->children
.concurrency
)
377 storeAppendPrintf(sentry
, " concurrency=%d", node
->children
.concurrency
);
380 storeAppendPrintf(sentry
, " cache=%d", node
->cache_size
);
382 if (node
->quote
== external_acl::QUOTE_METHOD_SHELL
)
383 storeAppendPrintf(sentry
, " protocol=2.5");
385 node
->format
.dump(sentry
, NULL
, false);
387 for (word
= node
->cmdline
; word
; word
= word
->next
)
388 storeAppendPrintf(sentry
, " %s", word
->key
);
390 storeAppendPrintf(sentry
, "\n");
395 free_externalAclHelper(external_acl
** list
)
401 static external_acl
*
402 find_externalAclHelper(const char *name
)
406 for (node
= Config
.externalAclHelperList
; node
; node
= node
->next
) {
407 if (strcmp(node
->name
, name
) == 0)
415 external_acl::add(const ExternalACLEntryPointer
&anEntry
)
418 assert(anEntry
!= NULL
);
419 assert (anEntry
->def
== NULL
);
421 ExternalACLEntry
*e
= const_cast<ExternalACLEntry
*>(anEntry
.getRaw()); // XXX: make hash a std::map of Pointer.
423 dlinkAdd(e
, &e
->lru
, &lru_list
);
424 e
->lock(); //cbdataReference(e); // lock it on behalf of the hash
429 external_acl::trimCache()
431 if (cache_size
&& cache_entries
>= cache_size
) {
432 ExternalACLEntryPointer
e(static_cast<ExternalACLEntry
*>(lru_list
.tail
->data
));
433 external_acl_cache_delete(this, e
);
437 /******************************************************************
441 class external_acl_data
443 CBDATA_CLASS(external_acl_data
);
446 explicit external_acl_data(external_acl
*aDef
) : def(cbdataReference(aDef
)), name(NULL
), arguments(NULL
) {}
447 ~external_acl_data();
454 CBDATA_CLASS_INIT(external_acl_data
);
456 external_acl_data::~external_acl_data()
459 wordlistDestroy(&arguments
);
460 cbdataReferenceDone(def
);
469 char *token
= ConfigParser::strtokFile();
474 data
= new external_acl_data(find_externalAclHelper(token
));
479 // def->name is the name of the external_acl_type.
480 // this is the name of the 'acl' directive being tested
481 data
->name
= xstrdup(AclMatchedName
);
483 while ((token
= ConfigParser::strtokFile())) {
484 wordlistAdd(&data
->arguments
, token
);
489 ACLExternal::valid () const
492 if (data
->def
->require_auth
) {
493 if (authenticateSchemeCount() == 0) {
494 debugs(28, DBG_CRITICAL
, "Can't use proxy auth because no authentication schemes were compiled.");
498 if (authenticateActiveSchemeCount() == 0) {
499 debugs(28, DBG_CRITICAL
, "Can't use proxy auth because no authentication schemes are fully configured.");
509 ACLExternal::empty () const
514 ACLExternal::~ACLExternal()
521 copyResultsFromEntry(HttpRequest
*req
, const ExternalACLEntryPointer
&entry
)
525 if (entry
->user
.size())
526 req
->extacl_user
= entry
->user
;
528 if (entry
->password
.size())
529 req
->extacl_passwd
= entry
->password
;
531 if (!req
->tag
.size())
532 req
->tag
= entry
->tag
;
534 if (entry
->log
.size())
535 req
->extacl_log
= entry
->log
;
537 if (entry
->message
.size())
538 req
->extacl_message
= entry
->message
;
540 // attach the helper kv-pair to the transaction
541 UpdateRequestNotes(req
->clientConnectionManager
.get(), *req
, entry
->notes
);
546 aclMatchExternal(external_acl_data
*acl
, ACLFilledChecklist
*ch
)
548 debugs(82, 9, HERE
<< "acl=\"" << acl
->def
->name
<< "\"");
549 ExternalACLEntryPointer entry
= ch
->extacl_entry
;
551 external_acl_message
= "MISSING REQUIRED INFORMATION";
554 if (entry
->def
== acl
->def
) {
555 /* Ours, use it.. if the key matches */
556 const char *key
= makeExternalAclKey(ch
, acl
);
558 return ACCESS_DUNNO
; // insufficent data to continue
559 if (strcmp(key
, (char*)entry
->key
) != 0) {
560 debugs(82, 9, "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "' dont match. Discarded.");
561 // too bad. need a new lookup.
562 entry
= ch
->extacl_entry
= NULL
;
565 /* Not ours.. get rid of it */
566 debugs(82, 9, "entry " << entry
<< " not valid or not ours. Discarded.");
568 debugs(82, 9, "entry def=" << entry
->def
<< ", our def=" << acl
->def
);
569 const char *key
= makeExternalAclKey(ch
, acl
); // may be nil
570 debugs(82, 9, "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "'");
572 entry
= ch
->extacl_entry
= NULL
;
577 debugs(82, 9, HERE
<< "No helper entry available");
579 if (acl
->def
->require_auth
) {
580 /* Make sure the user is authenticated */
581 debugs(82, 3, HERE
<< acl
->def
->name
<< " check user authenticated.");
582 const allow_t ti
= AuthenticateAcl(ch
);
583 if (ti
!= ACCESS_ALLOWED
) {
584 debugs(82, 2, HERE
<< acl
->def
->name
<< " user not authenticated (" << ti
<< ")");
587 debugs(82, 3, HERE
<< acl
->def
->name
<< " user is authenticated.");
590 const char *key
= makeExternalAclKey(ch
, acl
);
593 /* Not sufficient data to process */
597 entry
= static_cast<ExternalACLEntry
*>(hash_lookup(acl
->def
->cache
, key
));
599 const ExternalACLEntryPointer staleEntry
= entry
;
600 if (entry
!= NULL
&& external_acl_entry_expired(acl
->def
, entry
))
603 if (entry
!= NULL
&& external_acl_grace_expired(acl
->def
, entry
)) {
604 // refresh in the background
605 ExternalACLLookup::Start(ch
, acl
, true);
606 debugs(82, 4, HERE
<< "no need to wait for the refresh of '" <<
607 key
<< "' in '" << acl
->def
->name
<< "' (ch=" << ch
<< ").");
611 debugs(82, 2, HERE
<< acl
->def
->name
<< "(\"" << key
<< "\") = lookup needed");
613 if (!acl
->def
->theHelper
->queueFull()) {
614 debugs(82, 2, HERE
<< "\"" << key
<< "\": queueing a call.");
615 if (!ch
->goAsync(ExternalACLLookup::Instance()))
616 debugs(82, 2, "\"" << key
<< "\": no async support!");
617 debugs(82, 2, HERE
<< "\"" << key
<< "\": return -1.");
618 return ACCESS_DUNNO
; // expired cached or simply absent entry
621 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
622 "' queue overload. Request rejected '" << key
<< "'.");
623 external_acl_message
= "SYSTEM TOO BUSY, TRY AGAIN LATER";
626 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
627 "' queue overload. Using stale result. '" << key
<< "'.");
629 /* Fall thru to processing below */
635 debugs(82, 4, HERE
<< "entry = { date=" <<
636 (long unsigned int) entry
->date
<<
637 ", result=" << entry
->result
<<
638 " tag=" << entry
->tag
<<
639 " log=" << entry
->log
<< " }");
641 debugs(82, 4, HERE
<< "entry user=" << entry
->user
);
644 external_acl_cache_touch(acl
->def
, entry
);
645 external_acl_message
= entry
->message
.termedBuf();
647 debugs(82, 2, HERE
<< acl
->def
->name
<< " = " << entry
->result
);
648 copyResultsFromEntry(ch
->request
, entry
);
649 return entry
->result
;
653 ACLExternal::match(ACLChecklist
*checklist
)
655 allow_t answer
= aclMatchExternal(data
, Filled(checklist
));
657 // convert to tri-state ACL match 1,0,-1
663 return 0; // non-match
666 case ACCESS_AUTH_REQUIRED
:
668 // If the answer is not allowed or denied (matches/not matches) and
669 // async authentication is not in progress, then we are done.
670 if (checklist
->keepMatching())
671 checklist
->markFinished(answer
, "aclMatchExternal exception");
677 ACLExternal::dump() const
679 external_acl_data
const *acl
= data
;
681 rv
.push_back(SBuf(acl
->def
->name
));
683 for (wordlist
*arg
= acl
->arguments
; arg
; arg
= arg
->next
) {
685 s
.Printf(" %s", arg
->key
);
692 /******************************************************************
697 external_acl_cache_touch(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
699 // this must not be done when nothing is being cached.
700 if (def
->cache_size
<= 0 || (def
->ttl
<= 0 && entry
->result
== 1) || (def
->negative_ttl
<= 0 && entry
->result
!= 1))
703 dlinkDelete(&entry
->lru
, &def
->lru_list
);
704 ExternalACLEntry
*e
= const_cast<ExternalACLEntry
*>(entry
.getRaw()); // XXX: make hash a std::map of Pointer.
705 dlinkAdd(e
, &entry
->lru
, &def
->lru_list
);
709 makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
)
714 // check for special case tokens in the format
715 for (Format::Token
*t
= acl_data
->def
->format
.format
; t
; t
= t
->next
) {
717 if (t
->type
== Format::LFT_EXT_ACL_NAME
) {
719 safe_free(ch
->al
->_private
.lastAclName
);
720 ch
->al
->_private
.lastAclName
= xstrdup(acl_data
->name
);
723 if (t
->type
== Format::LFT_EXT_ACL_NAME
) {
724 // setup string for %DATA
727 for (auto arg
= acl_data
->arguments
; arg
; arg
= arg
->next
) {
731 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
732 const char *quoted
= rfc1738_escape(arg
->key
);
733 sb
.append(quoted
, strlen(quoted
));
737 strwordquote(&mb2
, arg
->key
);
738 sb
.append(mb2
.buf
, mb2
.size
);
746 if (t
->type
== Format::LFT_USER_IDENT
) {
748 // if we fail to go async, we still return NULL and the caller
749 // will detect the failure in ACLExternal::match().
750 (void)ch
->goAsync(IdentLookup::Instance());
756 // generate %DATA token value of this acl lookup
758 for (auto arg
= acl_data
->arguments
; arg
; arg
= arg
->next
) {
762 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
763 const char *quoted
= rfc1738_escape(arg
->key
);
764 sb
.append(quoted
, strlen(quoted
));
768 strwordquote(&mb2
, arg
->key
);
769 sb
.append(mb2
.buf
, mb2
.size
);
773 ch
->al
->_private
.lastAclData
= sb
.c_str();
775 // assemble the full helper lookup string
776 acl_data
->def
->format
.assemble(mb
, ch
->al
, 0);
782 external_acl_entry_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
784 if (def
->cache_size
<= 0)
787 if (entry
->date
+ (entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
) < squid_curtime
)
794 external_acl_grace_expired(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
796 if (def
->cache_size
<= 0)
800 ttl
= entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
;
801 ttl
= (ttl
* (100 - def
->grace
)) / 100;
803 if (entry
->date
+ ttl
<= squid_curtime
)
809 static ExternalACLEntryPointer
810 external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const & data
)
812 ExternalACLEntryPointer entry
;
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)) {
817 entry
= new ExternalACLEntry
;
818 entry
->key
= xstrdup(key
);
824 entry
= static_cast<ExternalACLEntry
*>(hash_lookup(def
->cache
, key
));
825 debugs(82, 2, "external_acl_cache_add: Adding '" << key
<< "' = " << data
.result
);
828 debugs(82, 3, "updating existing entry");
830 external_acl_cache_touch(def
, entry
);
834 entry
= new ExternalACLEntry
;
835 entry
->key
= xstrdup(key
);
844 external_acl_cache_delete(external_acl
* def
, const ExternalACLEntryPointer
&entry
)
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;
855 /******************************************************************
856 * external_acl helpers
859 class externalAclState
861 CBDATA_CLASS(externalAclState
);
864 externalAclState(external_acl
* aDef
, const char *aKey
) :
868 def(cbdataReference(aDef
)),
878 externalAclState
*queue
;
881 CBDATA_CLASS_INIT(externalAclState
);
883 externalAclState::~externalAclState()
886 cbdataReferenceDone(callback_data
);
887 cbdataReferenceDone(def
);
891 * The helper program receives queries on stdin, one
892 * per line, and must return the result on on stdout
894 * General result syntax:
896 * OK/ERR keyword=value ...
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
907 * Other keywords may be added to the protocol later
909 * value needs to be URL-encoded or enclosed in double quotes (")
910 * with \-escaping on any whitespace, quotes, or slashes (\).
913 externalAclHandleReply(void *data
, const Helper::Reply
&reply
)
915 externalAclState
*state
= static_cast<externalAclState
*>(data
);
916 externalAclState
*next
;
917 ExternalACLEntryData entryData
;
918 entryData
.result
= ACCESS_DENIED
;
920 debugs(82, 2, HERE
<< "reply=" << reply
);
922 if (reply
.result
== Helper::Okay
)
923 entryData
.result
= ACCESS_ALLOWED
;
924 // XXX: handle other non-DENIED results better
926 // XXX: make entryData store a proper Helper::Reply object instead of copying.
928 entryData
.notes
.append(&reply
.notes
);
930 const char *label
= reply
.notes
.findFirst("tag");
931 if (label
!= NULL
&& *label
!= '\0')
932 entryData
.tag
= label
;
934 label
= reply
.notes
.findFirst("message");
935 if (label
!= NULL
&& *label
!= '\0')
936 entryData
.message
= label
;
938 label
= reply
.notes
.findFirst("log");
939 if (label
!= NULL
&& *label
!= '\0')
940 entryData
.log
= label
;
943 label
= reply
.notes
.findFirst("user");
944 if (label
!= NULL
&& *label
!= '\0')
945 entryData
.user
= label
;
947 label
= reply
.notes
.findFirst("password");
948 if (label
!= NULL
&& *label
!= '\0')
949 entryData
.password
= label
;
952 dlinkDelete(&state
->list
, &state
->def
->queue
);
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
);
960 const ExternalACLEntryPointer oldentry
= static_cast<ExternalACLEntry
*>(hash_lookup(state
->def
->cache
, state
->key
));
962 if (oldentry
!= NULL
)
963 external_acl_cache_delete(state
->def
, oldentry
);
969 if (state
->callback
&& cbdataReferenceValidDone(state
->callback_data
, &cbdata
))
970 state
->callback(cbdata
, entry
);
982 ACLExternal::ExternalAclLookup(ACLChecklist
*checklist
, ACLExternal
* me
)
984 ExternalACLLookup::Start(checklist
, me
->data
, false);
988 ExternalACLLookup::Start(ACLChecklist
*checklist
, external_acl_data
*acl
, bool inBackground
)
990 external_acl
*def
= acl
->def
;
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
996 debugs(82, 2, HERE
<< (inBackground
? "bg" : "fg") << " lookup in '" <<
997 def
->name
<< "' for '" << key
<< "'");
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
);
1006 if (strcmp(key
, oldstatetmp
->key
) == 0) {
1007 oldstate
= oldstatetmp
;
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
<< ")");
1020 externalAclState
*state
= new externalAclState(def
, key
);
1022 if (!inBackground
) {
1023 state
->callback
= &ExternalACLLookup::LookupDone
;
1024 state
->callback_data
= cbdataReference(checklist
);
1028 /* Hook into pending lookup */
1029 state
->queue
= oldstate
->queue
;
1030 oldstate
->queue
= state
;
1032 /* No pending lookup found. Sumbit to helper */
1036 buf
.appendf("%s\n", key
);
1037 debugs(82, 4, "externalAclLookup: looking up for '" << key
<< "' in '" << def
->name
<< "'.");
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
1046 dlinkAdd(state
, &state
->list
, &def
->queue
);
1051 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key
<<
1052 "' in '" << def
->name
<< "' (ch=" << ch
<< ").");
1056 externalAclStats(StoreEntry
* sentry
)
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");
1068 externalAclRegisterWithCacheManager(void)
1070 Mgr::RegisterAction("external_acl",
1071 "External ACL stats",
1072 externalAclStats
, 0, 1);
1076 externalAclInit(void)
1078 for (external_acl
*p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1080 p
->cache
= hash_create((HASHCMP
*) strcmp
, hashPrime(1024), hash4
);
1083 p
->theHelper
= new helper(p
->name
);
1085 p
->theHelper
->cmdline
= p
->cmdline
;
1087 p
->theHelper
->childs
.updateLimits(p
->children
);
1089 p
->theHelper
->ipc_type
= IPC_TCP_SOCKET
;
1091 p
->theHelper
->addr
= p
->local_addr
;
1093 helperOpenServers(p
->theHelper
);
1096 externalAclRegisterWithCacheManager();
1100 externalAclShutdown(void)
1104 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1105 helperShutdown(p
->theHelper
);
1109 ExternalACLLookup
ExternalACLLookup::instance_
;
1111 ExternalACLLookup::Instance()
1117 ExternalACLLookup::checkForAsync(ACLChecklist
*checklist
)const
1119 /* TODO: optimise this - we probably have a pointer to this
1120 * around somewhere */
1121 ACL
*acl
= ACL::FindByName(AclMatchedName
);
1123 ACLExternal
*me
= dynamic_cast<ACLExternal
*> (acl
);
1125 ACLExternal::ExternalAclLookup(checklist
, me
);
1128 /// Called when an async lookup returns
1130 ExternalACLLookup::LookupDone(void *data
, const ExternalACLEntryPointer
&result
)
1132 ACLFilledChecklist
*checklist
= Filled(static_cast<ACLChecklist
*>(data
));
1133 checklist
->extacl_entry
= result
;
1134 checklist
->resumeNonBlockingCheck(ExternalACLLookup::Instance());
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
1142 * Be sure that clone is fully functional for that acl class though!
1144 ACL::Prototype
ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_
, "external");
1146 ACLExternal
ACLExternal::RegistryEntry_("external");
1149 ACLExternal::clone() const
1151 return new ACLExternal(*this);
1154 ACLExternal::ACLExternal(char const *theClass
) : data(NULL
), class_(xstrdup(theClass
))
1157 ACLExternal::ACLExternal(ACLExternal
const & old
) : data(NULL
), class_(old
.class_
? xstrdup(old
.class_
) : NULL
)
1159 /* we don't have copy constructors for the data yet */
1164 ACLExternal::typeString() const
1170 ACLExternal::isProxyAuth() const
1173 return data
->def
->require_auth
;