5 * DEBUG: section 82 External ACL
6 * AUTHOR: Henrik Nordstrom, MARA Systems AB
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * The contents of this file is Copyright (C) 2002 by MARA Systems AB,
12 * Sweden, unless otherwise is indicated in the specific function. The
13 * author gives his full permission to include this file into the Squid
14 * software product under the terms of the GNU General Public License as
15 * published by the Free Software Foundation; either version 2 of the
16 * License, or (at your option) any later version.
18 * Squid is the result of efforts by numerous individuals from
19 * the Internet community; see the CONTRIBUTORS file for full
20 * details. Many organizations have provided support for Squid's
21 * development; see the SPONSORS file for full details. Squid is
22 * Copyrighted (C) 2001 by the Regents of the University of
23 * California; see the COPYRIGHT file for full details. Squid
24 * incorporates software developed and/or copyrighted by other
25 * sources; see the CREDITS file for full details.
27 * This program is free software; you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation; either version 2 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program; if not, write to the Free Software
39 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
45 #include "acl/FilledChecklist.h"
46 #include "client_side.h"
47 #include "comm/Connection.h"
48 #include "ExternalACLEntry.h"
49 #include "ExternalACL.h"
52 #include "HttpReply.h"
53 #include "HttpRequest.h"
56 #include "mgr/Registration.h"
59 #include "SquidTime.h"
61 #include "URLScheme.h"
64 #include "ssl/support.h"
68 #include "auth/Gadgets.h"
69 #include "auth/UserRequest.h"
72 #include "ident/AclIdent.h"
75 #ifndef DEFAULT_EXTERNAL_ACL_TTL
76 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
78 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
79 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
82 typedef struct _external_acl_format external_acl_format
;
84 static char *makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
);
85 static void external_acl_cache_delete(external_acl
* def
, external_acl_entry
* entry
);
86 static int external_acl_entry_expired(external_acl
* def
, external_acl_entry
* entry
);
87 static int external_acl_grace_expired(external_acl
* def
, external_acl_entry
* entry
);
88 static void external_acl_cache_touch(external_acl
* def
, external_acl_entry
* entry
);
89 static external_acl_entry
*external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const &data
);
91 /******************************************************************
92 * external_acl directive
101 void add(ExternalACLEntry
*);
113 external_acl_format
*format
;
117 HelperChildConfig children
;
133 * Configuration flag. May only be altered by the configuration parser.
135 * Indicates that all uses of this external_acl_type helper require authentication
136 * details to be processed. If none are available its a fail match.
142 QUOTE_METHOD_SHELL
= 1,
146 Ip::Address local_addr
;
149 struct _external_acl_format
{
173 EXT_ACL_HEADER_REQUEST
,
174 EXT_ACL_HEADER_REQUEST_MEMBER
,
175 EXT_ACL_HEADER_REQUEST_ID
,
176 EXT_ACL_HEADER_REQUEST_ID_MEMBER
,
178 EXT_ACL_HEADER_REPLY
,
179 EXT_ACL_HEADER_REPLY_MEMBER
,
180 EXT_ACL_HEADER_REPLY_ID
,
181 EXT_ACL_HEADER_REPLY_ID_MEMBER
,
186 EXT_ACL_USER_CERT_RAW
,
187 EXT_ACL_USER_CERTCHAIN_RAW
,
197 external_acl_format
*next
;
201 http_hdr_type header_id
;
204 /* FIXME: These are not really cbdata, but it is an easy way
205 * to get them pooled, refcounted, accounted and freed properly...
207 CBDATA_TYPE(external_acl
);
208 CBDATA_TYPE(external_acl_format
);
211 free_external_acl_format(void *data
)
213 external_acl_format
*p
= static_cast<external_acl_format
*>(data
);
214 safe_free(p
->header
);
218 free_external_acl(void *data
)
220 external_acl
*p
= static_cast<external_acl
*>(data
);
224 external_acl_format
*f
= p
->format
;
229 wordlistDestroy(&p
->cmdline
);
232 helperShutdown(p
->theHelper
);
237 while (p
->lru_list
.tail
)
238 external_acl_cache_delete(p
, static_cast<external_acl_entry
*>(p
->lru_list
.tail
->data
));
240 hashFreeMemory(p
->cache
);
244 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
245 * request or reply header to external helper.
247 \param header - the token being parsed (without the identifying prefix)
248 \param type - format enum identifier for this element, pulled from identifying prefix
249 \param format - structure to contain all the info about this format element.
252 parse_header_token(external_acl_format
*format
, char *header
, const _external_acl_format::format_type type
)
257 /** Cut away the closing brace */
258 end
= strchr(header
, '}');
259 if (end
&& strlen(end
) == 1)
264 member
= strchr(header
, ':');
267 /* Split in header and member */
271 if (!xisalnum(*member
)) {
272 format
->separator
= *member
;
275 format
->separator
= ',';
278 format
->member
= xstrdup(member
);
280 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
281 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
;
283 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
;
288 format
->header
= xstrdup(header
);
289 format
->header_id
= httpHeaderIdByNameDef(header
, strlen(header
));
291 if (format
->header_id
!= -1) {
293 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
294 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
;
296 format
->type
= _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
;
298 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
299 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
;
301 format
->type
= _external_acl_format::EXT_ACL_HEADER_REPLY_ID
;
307 parse_externalAclHelper(external_acl
** list
)
311 external_acl_format
**p
;
313 CBDATA_INIT_TYPE_FREECB(external_acl
, free_external_acl
);
314 CBDATA_INIT_TYPE_FREECB(external_acl_format
, free_external_acl_format
);
316 a
= cbdataAlloc(external_acl
);
319 a
->ttl
= DEFAULT_EXTERNAL_ACL_TTL
;
320 a
->negative_ttl
= -1;
321 a
->cache_size
= 256*1024;
322 a
->children
.n_max
= DEFAULT_EXTERNAL_ACL_CHILDREN
;
323 a
->children
.n_startup
= a
->children
.n_max
;
324 a
->children
.n_idle
= 1;
325 a
->local_addr
.SetLocalhost();
326 a
->quote
= external_acl::QUOTE_METHOD_URL
;
328 token
= strtok(NULL
, w_space
);
333 a
->name
= xstrdup(token
);
335 token
= strtok(NULL
, w_space
);
339 if (strncmp(token
, "ttl=", 4) == 0) {
340 a
->ttl
= atoi(token
+ 4);
341 } else if (strncmp(token
, "negative_ttl=", 13) == 0) {
342 a
->negative_ttl
= atoi(token
+ 13);
343 } else if (strncmp(token
, "children=", 9) == 0) {
344 a
->children
.n_max
= atoi(token
+ 9);
345 debugs(0, DBG_CRITICAL
, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
346 } else if (strncmp(token
, "children-max=", 13) == 0) {
347 a
->children
.n_max
= atoi(token
+ 13);
348 } else if (strncmp(token
, "children-startup=", 17) == 0) {
349 a
->children
.n_startup
= atoi(token
+ 17);
350 } else if (strncmp(token
, "children-idle=", 14) == 0) {
351 a
->children
.n_idle
= atoi(token
+ 14);
352 } else if (strncmp(token
, "concurrency=", 12) == 0) {
353 a
->children
.concurrency
= atoi(token
+ 12);
354 } else if (strncmp(token
, "cache=", 6) == 0) {
355 a
->cache_size
= atoi(token
+ 6);
356 } else if (strncmp(token
, "grace=", 6) == 0) {
357 a
->grace
= atoi(token
+ 6);
358 } else if (strcmp(token
, "protocol=2.5") == 0) {
359 a
->quote
= external_acl::QUOTE_METHOD_SHELL
;
360 } else if (strcmp(token
, "protocol=3.0") == 0) {
361 a
->quote
= external_acl::QUOTE_METHOD_URL
;
362 } else if (strcmp(token
, "quote=url") == 0) {
363 a
->quote
= external_acl::QUOTE_METHOD_URL
;
364 } else if (strcmp(token
, "quote=shell") == 0) {
365 a
->quote
= external_acl::QUOTE_METHOD_SHELL
;
367 /* INET6: allow admin to configure some helpers explicitly to
368 bind to IPv4/v6 localhost port. */
369 } else if (strcmp(token
, "ipv4") == 0) {
370 if ( !a
->local_addr
.SetIPv4() ) {
371 debugs(3, DBG_CRITICAL
, "WARNING: Error converting " << a
->local_addr
<< " to IPv4 in " << a
->name
);
373 } else if (strcmp(token
, "ipv6") == 0) {
375 debugs(3, DBG_CRITICAL
, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a
->name
);
376 // else nothing to do.
381 token
= strtok(NULL
, w_space
);
384 /* check that child startup value is sane. */
385 if (a
->children
.n_startup
> a
->children
.n_max
)
386 a
->children
.n_startup
= a
->children
.n_max
;
388 /* check that child idle value is sane. */
389 if (a
->children
.n_idle
> a
->children
.n_max
)
390 a
->children
.n_idle
= a
->children
.n_max
;
391 if (a
->children
.n_idle
< 1)
392 a
->children
.n_idle
= 1;
394 if (a
->negative_ttl
== -1)
395 a
->negative_ttl
= a
->ttl
;
401 external_acl_format
*format
;
403 /* stop on first non-format token found */
408 format
= cbdataAlloc(external_acl_format
);
410 if (strncmp(token
, "%{", 2) == 0) {
411 // deprecated. but assume the old configs all referred to request headers.
412 debugs(82, DBG_IMPORTANT
, "WARNING: external_acl_type format %{...} is being replaced by %>{...} for : " << token
);
413 parse_header_token(format
, (token
+2), _external_acl_format::EXT_ACL_HEADER_REQUEST
);
414 } else if (strncmp(token
, "%>{", 3) == 0) {
415 parse_header_token(format
, (token
+3), _external_acl_format::EXT_ACL_HEADER_REQUEST
);
416 } else if (strncmp(token
, "%<{", 3) == 0) {
417 parse_header_token(format
, (token
+3), _external_acl_format::EXT_ACL_HEADER_REPLY
);
419 } else if (strcmp(token
, "%LOGIN") == 0) {
420 format
->type
= _external_acl_format::EXT_ACL_LOGIN
;
421 a
->require_auth
= true;
426 else if (strcmp(token
, "%IDENT") == 0)
427 format
->type
= _external_acl_format::EXT_ACL_IDENT
;
431 else if (strcmp(token
, "%SRC") == 0)
432 format
->type
= _external_acl_format::EXT_ACL_SRC
;
433 else if (strcmp(token
, "%SRCPORT") == 0)
434 format
->type
= _external_acl_format::EXT_ACL_SRCPORT
;
436 else if (strcmp(token
, "%SRCEUI48") == 0)
437 format
->type
= _external_acl_format::EXT_ACL_SRCEUI48
;
438 else if (strcmp(token
, "%SRCEUI64") == 0)
439 format
->type
= _external_acl_format::EXT_ACL_SRCEUI64
;
441 else if (strcmp(token
, "%MYADDR") == 0)
442 format
->type
= _external_acl_format::EXT_ACL_MYADDR
;
443 else if (strcmp(token
, "%MYPORT") == 0)
444 format
->type
= _external_acl_format::EXT_ACL_MYPORT
;
445 else if (strcmp(token
, "%URI") == 0)
446 format
->type
= _external_acl_format::EXT_ACL_URI
;
447 else if (strcmp(token
, "%DST") == 0)
448 format
->type
= _external_acl_format::EXT_ACL_DST
;
449 else if (strcmp(token
, "%PROTO") == 0)
450 format
->type
= _external_acl_format::EXT_ACL_PROTO
;
451 else if (strcmp(token
, "%PORT") == 0)
452 format
->type
= _external_acl_format::EXT_ACL_PORT
;
453 else if (strcmp(token
, "%PATH") == 0)
454 format
->type
= _external_acl_format::EXT_ACL_PATH
;
455 else if (strcmp(token
, "%METHOD") == 0)
456 format
->type
= _external_acl_format::EXT_ACL_METHOD
;
459 else if (strcmp(token
, "%USER_CERT") == 0)
460 format
->type
= _external_acl_format::EXT_ACL_USER_CERT_RAW
;
461 else if (strcmp(token
, "%USER_CERTCHAIN") == 0)
462 format
->type
= _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW
;
463 else if (strncmp(token
, "%USER_CERT_", 11) == 0) {
464 format
->type
= _external_acl_format::EXT_ACL_USER_CERT
;
465 format
->header
= xstrdup(token
+ 11);
466 } else if (strncmp(token
, "%CA_CERT_", 11) == 0) {
467 format
->type
= _external_acl_format::EXT_ACL_USER_CERT
;
468 format
->header
= xstrdup(token
+ 11);
472 else if (strcmp(token
, "%EXT_USER") == 0)
473 format
->type
= _external_acl_format::EXT_ACL_EXT_USER
;
475 else if (strcmp(token
, "%EXT_LOG") == 0)
476 format
->type
= _external_acl_format::EXT_ACL_EXT_LOG
;
477 else if (strcmp(token
, "%TAG") == 0)
478 format
->type
= _external_acl_format::EXT_ACL_TAG
;
479 else if (strcmp(token
, "%%") == 0)
480 format
->type
= _external_acl_format::EXT_ACL_PERCENT
;
482 debugs(0, DBG_CRITICAL
, "ERROR: Unknown Format token " << token
);
488 token
= strtok(NULL
, w_space
);
491 /* There must be at least one format token */
499 wordlistAdd(&a
->cmdline
, token
);
502 parse_wordlist(&a
->cmdline
);
505 list
= &(*list
)->next
;
511 dump_externalAclHelper(StoreEntry
* sentry
, const char *name
, const external_acl
* list
)
513 const external_acl
*node
;
514 const external_acl_format
*format
;
515 const wordlist
*word
;
517 for (node
= list
; node
; node
= node
->next
) {
518 storeAppendPrintf(sentry
, "%s %s", name
, node
->name
);
520 if (!node
->local_addr
.IsIPv6())
521 storeAppendPrintf(sentry
, " ipv4");
523 storeAppendPrintf(sentry
, " ipv6");
525 if (node
->ttl
!= DEFAULT_EXTERNAL_ACL_TTL
)
526 storeAppendPrintf(sentry
, " ttl=%d", node
->ttl
);
528 if (node
->negative_ttl
!= node
->ttl
)
529 storeAppendPrintf(sentry
, " negative_ttl=%d", node
->negative_ttl
);
532 storeAppendPrintf(sentry
, " grace=%d", node
->grace
);
534 if (node
->children
.n_max
!= DEFAULT_EXTERNAL_ACL_CHILDREN
)
535 storeAppendPrintf(sentry
, " children-max=%d", node
->children
.n_max
);
537 if (node
->children
.n_startup
!= 1)
538 storeAppendPrintf(sentry
, " children-startup=%d", node
->children
.n_startup
);
540 if (node
->children
.n_idle
!= (node
->children
.n_max
+ node
->children
.n_startup
) )
541 storeAppendPrintf(sentry
, " children-idle=%d", node
->children
.n_idle
);
543 if (node
->children
.concurrency
)
544 storeAppendPrintf(sentry
, " concurrency=%d", node
->children
.concurrency
);
547 storeAppendPrintf(sentry
, " cache=%d", node
->cache_size
);
549 for (format
= node
->format
; format
; format
= format
->next
) {
550 switch (format
->type
) {
552 case _external_acl_format::EXT_ACL_HEADER_REQUEST
:
553 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
:
554 storeAppendPrintf(sentry
, " %%>{%s}", format
->header
);
557 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
:
558 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
:
559 storeAppendPrintf(sentry
, " %%>{%s:%s}", format
->header
, format
->member
);
562 case _external_acl_format::EXT_ACL_HEADER_REPLY
:
563 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID
:
564 storeAppendPrintf(sentry
, " %%<{%s}", format
->header
);
567 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER
:
568 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
:
569 storeAppendPrintf(sentry
, " %%<{%s:%s}", format
->header
, format
->member
);
571 #define DUMP_EXT_ACL_TYPE(a) \
572 case _external_acl_format::EXT_ACL_##a: \
573 storeAppendPrintf(sentry, " %%%s", #a); \
575 #define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
576 case _external_acl_format::EXT_ACL_##a: \
577 storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
580 DUMP_EXT_ACL_TYPE(LOGIN
);
584 DUMP_EXT_ACL_TYPE(IDENT
);
587 DUMP_EXT_ACL_TYPE(SRC
);
588 DUMP_EXT_ACL_TYPE(SRCPORT
);
590 DUMP_EXT_ACL_TYPE(SRCEUI48
);
591 DUMP_EXT_ACL_TYPE(SRCEUI64
);
594 DUMP_EXT_ACL_TYPE(MYADDR
);
595 DUMP_EXT_ACL_TYPE(MYPORT
);
596 DUMP_EXT_ACL_TYPE(URI
);
597 DUMP_EXT_ACL_TYPE(DST
);
598 DUMP_EXT_ACL_TYPE(PROTO
);
599 DUMP_EXT_ACL_TYPE(PORT
);
600 DUMP_EXT_ACL_TYPE(PATH
);
601 DUMP_EXT_ACL_TYPE(METHOD
);
603 DUMP_EXT_ACL_TYPE_FMT(USER_CERT_RAW
, " %%USER_CERT_RAW");
604 DUMP_EXT_ACL_TYPE_FMT(USER_CERTCHAIN_RAW
, " %%USER_CERTCHAIN_RAW");
605 DUMP_EXT_ACL_TYPE_FMT(USER_CERT
, " %%USER_CERT_%s", format
->header
);
606 DUMP_EXT_ACL_TYPE_FMT(CA_CERT
, " %%CA_CERT_%s", format
->header
);
609 DUMP_EXT_ACL_TYPE(EXT_USER
);
611 DUMP_EXT_ACL_TYPE(EXT_LOG
);
612 DUMP_EXT_ACL_TYPE(TAG
);
613 DUMP_EXT_ACL_TYPE_FMT(PERCENT
, " %%%%");
615 fatal("unknown external_acl format error");
620 for (word
= node
->cmdline
; word
; word
= word
->next
)
621 storeAppendPrintf(sentry
, " %s", word
->key
);
623 storeAppendPrintf(sentry
, "\n");
628 free_externalAclHelper(external_acl
** list
)
631 external_acl
*node
= *list
;
638 static external_acl
*
639 find_externalAclHelper(const char *name
)
643 for (node
= Config
.externalAclHelperList
; node
; node
= node
->next
) {
644 if (strcmp(node
->name
, name
) == 0)
652 external_acl::add(ExternalACLEntry
*anEntry
)
655 assert (anEntry
->def
== NULL
);
657 hash_join(cache
, anEntry
);
658 dlinkAdd(anEntry
, &anEntry
->lru
, &lru_list
);
663 external_acl::trimCache()
665 if (cache_size
&& cache_entries
>= cache_size
)
666 external_acl_cache_delete(this, static_cast<external_acl_entry
*>(lru_list
.tail
->data
));
669 /******************************************************************
673 struct _external_acl_data
{
678 CBDATA_TYPE(external_acl_data
);
680 free_external_acl_data(void *data
)
682 external_acl_data
*p
= static_cast<external_acl_data
*>(data
);
683 wordlistDestroy(&p
->arguments
);
684 cbdataReferenceDone(p
->def
);
695 CBDATA_INIT_TYPE_FREECB(external_acl_data
, free_external_acl_data
);
697 data
= cbdataAlloc(external_acl_data
);
699 token
= strtok(NULL
, w_space
);
704 data
->def
= cbdataReference(find_externalAclHelper(token
));
709 while ((token
= strtokFile())) {
710 wordlistAdd(&data
->arguments
, token
);
715 ACLExternal::valid () const
718 if (data
->def
->require_auth
) {
719 if (authenticateSchemeCount() == 0) {
720 debugs(28, DBG_CRITICAL
, "Can't use proxy auth because no authentication schemes were compiled.");
724 if (authenticateActiveSchemeCount() == 0) {
725 debugs(28, DBG_CRITICAL
, "Can't use proxy auth because no authentication schemes are fully configured.");
735 ACLExternal::empty () const
740 ACLExternal::~ACLExternal()
747 copyResultsFromEntry(HttpRequest
*req
, external_acl_entry
*entry
)
751 if (entry
->user
.size())
752 req
->extacl_user
= entry
->user
;
754 if (entry
->password
.size())
755 req
->extacl_passwd
= entry
->password
;
757 if (!req
->tag
.size())
758 req
->tag
= entry
->tag
;
760 if (entry
->log
.size())
761 req
->extacl_log
= entry
->log
;
763 if (entry
->message
.size())
764 req
->extacl_message
= entry
->message
;
769 aclMatchExternal(external_acl_data
*acl
, ACLFilledChecklist
*ch
)
771 const char *key
= "";
772 debugs(82, 9, HERE
<< "acl=\"" << acl
->def
->name
<< "\"");
773 external_acl_entry
*entry
= ch
->extacl_entry
;
776 if (cbdataReferenceValid(entry
) && entry
->def
== acl
->def
) {
777 /* Ours, use it.. if the key matches */
778 key
= makeExternalAclKey(ch
, acl
);
779 if (strcmp(key
, (char*)entry
->key
) != 0) {
780 debugs(82, 9, HERE
<< "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "' dont match. Discarded.");
781 // too bad. need a new lookup.
782 cbdataReferenceDone(ch
->extacl_entry
);
786 /* Not valid, or not ours.. get rid of it */
787 debugs(82, 9, HERE
<< "entry " << entry
<< " not valid or not ours. Discarded.");
789 debugs(82, 9, HERE
<< "entry def=" << entry
->def
<< ", our def=" << acl
->def
);
790 key
= makeExternalAclKey(ch
, acl
);
791 debugs(82, 9, HERE
<< "entry key='" << (char *)entry
->key
<< "', our key='" << key
<< "'");
793 cbdataReferenceDone(ch
->extacl_entry
);
798 external_acl_message
= "MISSING REQUIRED INFORMATION";
801 debugs(82, 9, HERE
<< "No helper entry available");
803 if (acl
->def
->require_auth
) {
804 /* Make sure the user is authenticated */
805 debugs(82, 3, HERE
<< acl
->def
->name
<< " check user authenticated.");
806 const allow_t ti
= AuthenticateAcl(ch
);
807 if (ti
!= ACCESS_ALLOWED
) {
808 debugs(82, 2, HERE
<< acl
->def
->name
<< " user not authenticated (" << ti
<< ")");
811 debugs(82, 3, HERE
<< acl
->def
->name
<< " user is authenticated.");
814 key
= makeExternalAclKey(ch
, acl
);
817 /* Not sufficient data to process */
821 entry
= static_cast<external_acl_entry
*>(hash_lookup(acl
->def
->cache
, key
));
823 external_acl_entry
*staleEntry
= entry
;
824 if (entry
&& external_acl_entry_expired(acl
->def
, entry
))
827 if (entry
&& external_acl_grace_expired(acl
->def
, entry
)) {
828 // refresh in the background
829 ExternalACLLookup::Start(ch
, acl
, true);
830 debugs(82, 4, HERE
<< "no need to wait for the refresh of '" <<
831 key
<< "' in '" << acl
->def
->name
<< "' (ch=" << ch
<< ").");
835 debugs(82, 2, HERE
<< acl
->def
->name
<< "(\"" << key
<< "\") = lookup needed");
836 debugs(82, 2, HERE
<< "\"" << key
<< "\": entry=@" <<
837 entry
<< ", age=" << (entry
? (long int) squid_curtime
- entry
->date
: 0));
839 if (acl
->def
->theHelper
->stats
.queue_size
<= (int)acl
->def
->theHelper
->childs
.n_active
) {
840 debugs(82, 2, HERE
<< "\"" << key
<< "\": queueing a call.");
841 ch
->changeState(ExternalACLLookup::Instance());
842 debugs(82, 2, HERE
<< "\"" << key
<< "\": return -1.");
843 return ACCESS_DUNNO
; // expired cached or simply absent entry
846 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
847 "' queue overload. Request rejected '" << key
<< "'.");
848 external_acl_message
= "SYSTEM TOO BUSY, TRY AGAIN LATER";
851 debugs(82, DBG_IMPORTANT
, "WARNING: external ACL '" << acl
->def
->name
<<
852 "' queue overload. Using stale result. '" << key
<< "'.");
854 /* Fall thru to processing below */
860 debugs(82, 4, HERE
<< "entry = { date=" <<
861 (long unsigned int) entry
->date
<<
862 ", result=" << entry
->result
<<
863 " tag=" << entry
->tag
<<
864 " log=" << entry
->log
<< " }");
866 debugs(82, 4, HERE
<< "entry user=" << entry
->user
);
869 external_acl_cache_touch(acl
->def
, entry
);
870 external_acl_message
= entry
->message
.termedBuf();
872 debugs(82, 2, HERE
<< acl
->def
->name
<< " = " << entry
->result
);
873 copyResultsFromEntry(ch
->request
, entry
);
874 return entry
->result
;
878 ACLExternal::match(ACLChecklist
*checklist
)
880 allow_t answer
= aclMatchExternal(data
, Filled(checklist
));
882 // convert to tri-state ACL match 1,0,-1
888 return 0; // non-match
891 case ACCESS_AUTH_REQUIRED
:
893 // If the answer is not allowed or denied (matches/not matches) and
894 // async authentication is not needed (asyncNeeded), then we are done.
895 if (!checklist
->asyncNeeded())
896 checklist
->markFinished(answer
, "aclMatchExternal exception");
902 ACLExternal::dump() const
904 external_acl_data
const *acl
= data
;
905 wordlist
*result
= NULL
;
909 mb
.Printf("%s", acl
->def
->name
);
911 for (arg
= acl
->arguments
; arg
; arg
= arg
->next
) {
912 mb
.Printf(" %s", arg
->key
);
915 wordlistAdd(&result
, mb
.buf
);
920 /******************************************************************
925 external_acl_cache_touch(external_acl
* def
, external_acl_entry
* entry
)
927 // this must not be done when nothing is being cached.
928 if (def
->cache_size
<= 0 || (def
->ttl
<= 0 && entry
->result
== 1) || (def
->negative_ttl
<= 0 && entry
->result
!= 1))
931 dlinkDelete(&entry
->lru
, &def
->lru_list
);
932 dlinkAdd(entry
, &entry
->lru
, &def
->lru_list
);
936 makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
)
942 external_acl_format
*format
;
943 HttpRequest
*request
= ch
->request
;
944 HttpReply
*reply
= ch
->reply
;
947 for (format
= acl_data
->def
->format
; format
; format
= format
->next
) {
948 const char *str
= NULL
;
951 switch (format
->type
) {
953 case _external_acl_format::EXT_ACL_LOGIN
:
954 // if this ACL line was the cause of credentials fetch
955 // they may not already be in the checklist
956 if (ch
->auth_user_request
== NULL
&& ch
->request
)
957 ch
->auth_user_request
= ch
->request
->auth_user_request
;
959 if (ch
->auth_user_request
!= NULL
)
960 str
= ch
->auth_user_request
->username();
964 case _external_acl_format::EXT_ACL_IDENT
:
968 ch
->changeState(IdentLookup::Instance());
975 case _external_acl_format::EXT_ACL_SRC
:
976 str
= ch
->src_addr
.NtoA(buf
,sizeof(buf
));
979 case _external_acl_format::EXT_ACL_SRCPORT
:
980 snprintf(buf
, sizeof(buf
), "%d", request
->client_addr
.GetPort());
985 case _external_acl_format::EXT_ACL_SRCEUI48
:
986 if (request
->clientConnectionManager
.valid() && request
->clientConnectionManager
->clientConnection
!= NULL
&&
987 request
->clientConnectionManager
->clientConnection
->remoteEui48
.encode(buf
, sizeof(buf
)))
991 case _external_acl_format::EXT_ACL_SRCEUI64
:
992 if (request
->clientConnectionManager
.valid() && request
->clientConnectionManager
->clientConnection
!= NULL
&&
993 request
->clientConnectionManager
->clientConnection
->remoteEui64
.encode(buf
, sizeof(buf
)))
998 case _external_acl_format::EXT_ACL_MYADDR
:
999 str
= request
->my_addr
.NtoA(buf
, sizeof(buf
));
1002 case _external_acl_format::EXT_ACL_MYPORT
:
1003 snprintf(buf
, sizeof(buf
), "%d", request
->my_addr
.GetPort());
1007 case _external_acl_format::EXT_ACL_URI
:
1008 str
= urlCanonical(request
);
1011 case _external_acl_format::EXT_ACL_DST
:
1012 str
= request
->GetHost();
1015 case _external_acl_format::EXT_ACL_PROTO
:
1016 str
= AnyP::ProtocolType_str
[request
->protocol
];
1019 case _external_acl_format::EXT_ACL_PORT
:
1020 snprintf(buf
, sizeof(buf
), "%d", request
->port
);
1024 case _external_acl_format::EXT_ACL_PATH
:
1025 str
= request
->urlpath
.termedBuf();
1028 case _external_acl_format::EXT_ACL_METHOD
:
1029 str
= RequestMethodStr(request
->method
);
1032 case _external_acl_format::EXT_ACL_HEADER_REQUEST
:
1033 sb
= request
->header
.getByName(format
->header
);
1034 str
= sb
.termedBuf();
1037 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
:
1038 sb
= request
->header
.getStrOrList(format
->header_id
);
1039 str
= sb
.termedBuf();
1042 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
:
1043 sb
= request
->header
.getByNameListMember(format
->header
, format
->member
, format
->separator
);
1044 str
= sb
.termedBuf();
1047 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
:
1048 sb
= request
->header
.getListMember(format
->header_id
, format
->member
, format
->separator
);
1049 str
= sb
.termedBuf();
1052 case _external_acl_format::EXT_ACL_HEADER_REPLY
:
1054 sb
= reply
->header
.getByName(format
->header
);
1055 str
= sb
.termedBuf();
1059 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID
:
1061 sb
= reply
->header
.getStrOrList(format
->header_id
);
1062 str
= sb
.termedBuf();
1066 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER
:
1068 sb
= reply
->header
.getByNameListMember(format
->header
, format
->member
, format
->separator
);
1069 str
= sb
.termedBuf();
1073 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
:
1075 sb
= reply
->header
.getListMember(format
->header_id
, format
->member
, format
->separator
);
1076 str
= sb
.termedBuf();
1081 case _external_acl_format::EXT_ACL_USER_CERT_RAW
:
1083 if (ch
->conn() != NULL
&& Comm::IsConnOpen(ch
->conn()->clientConnection
)) {
1084 SSL
*ssl
= fd_table
[ch
->conn()->clientConnection
->fd
].ssl
;
1087 str
= sslGetUserCertificatePEM(ssl
);
1092 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW
:
1094 if (ch
->conn() != NULL
&& Comm::IsConnOpen(ch
->conn()->clientConnection
)) {
1095 SSL
*ssl
= fd_table
[ch
->conn()->clientConnection
->fd
].ssl
;
1098 str
= sslGetUserCertificateChainPEM(ssl
);
1103 case _external_acl_format::EXT_ACL_USER_CERT
:
1105 if (ch
->conn() != NULL
&& Comm::IsConnOpen(ch
->conn()->clientConnection
)) {
1106 SSL
*ssl
= fd_table
[ch
->conn()->clientConnection
->fd
].ssl
;
1109 str
= sslGetUserAttribute(ssl
, format
->header
);
1114 case _external_acl_format::EXT_ACL_CA_CERT
:
1116 if (ch
->conn() != NULL
&& Comm::IsConnOpen(ch
->conn()->clientConnection
)) {
1117 SSL
*ssl
= fd_table
[ch
->conn()->clientConnection
->fd
].ssl
;
1120 str
= sslGetCAAttribute(ssl
, format
->header
);
1126 case _external_acl_format::EXT_ACL_EXT_USER
:
1127 str
= request
->extacl_user
.termedBuf();
1130 case _external_acl_format::EXT_ACL_EXT_LOG
:
1131 str
= request
->extacl_log
.termedBuf();
1133 case _external_acl_format::EXT_ACL_TAG
:
1134 str
= request
->tag
.termedBuf();
1136 case _external_acl_format::EXT_ACL_PERCENT
:
1139 case _external_acl_format::EXT_ACL_UNKNOWN
:
1141 case _external_acl_format::EXT_ACL_END
:
1142 fatal("unknown external_acl format error");
1156 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
1157 const char *quoted
= rfc1738_escape(str
);
1158 mb
.append(quoted
, strlen(quoted
));
1160 strwordquote(&mb
, str
);
1168 for (arg
= acl_data
->arguments
; arg
; arg
= arg
->next
) {
1172 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
1173 const char *quoted
= rfc1738_escape(arg
->key
);
1174 mb
.append(quoted
, strlen(quoted
));
1176 strwordquote(&mb
, arg
->key
);
1186 external_acl_entry_expired(external_acl
* def
, external_acl_entry
* entry
)
1188 if (def
->cache_size
<= 0)
1191 if (entry
->date
+ (entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
) < squid_curtime
)
1198 external_acl_grace_expired(external_acl
* def
, external_acl_entry
* entry
)
1200 if (def
->cache_size
<= 0)
1204 ttl
= entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
;
1205 ttl
= (ttl
* (100 - def
->grace
)) / 100;
1207 if (entry
->date
+ ttl
<= squid_curtime
)
1213 static external_acl_entry
*
1214 external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const & data
)
1216 ExternalACLEntry
*entry
;
1218 // do not bother caching this result if TTL is going to expire it immediately
1219 if (def
->cache_size
<= 0 || (def
->ttl
<= 0 && data
.result
== 1) || (def
->negative_ttl
<= 0 && data
.result
!= 1)) {
1221 entry
= new ExternalACLEntry
;
1222 entry
->key
= xstrdup(key
);
1223 entry
->update(data
);
1228 entry
= static_cast<ExternalACLEntry
*>(hash_lookup(def
->cache
, key
));
1229 debugs(82, 2, "external_acl_cache_add: Adding '" << key
<< "' = " << data
.result
);
1232 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1233 entry
->update(data
);
1234 external_acl_cache_touch(def
, entry
);
1239 entry
= new ExternalACLEntry
;
1240 entry
->key
= xstrdup(key
);
1241 entry
->update(data
);
1249 external_acl_cache_delete(external_acl
* def
, external_acl_entry
* entry
)
1251 assert(def
->cache_size
> 0 && entry
->def
== def
);
1252 hash_remove_link(def
->cache
, entry
);
1253 dlinkDelete(&entry
->lru
, &def
->lru_list
);
1254 def
->cache_entries
-= 1;
1258 /******************************************************************
1259 * external_acl helpers
1262 typedef struct _externalAclState externalAclState
;
1264 struct _externalAclState
{
1266 void *callback_data
;
1270 externalAclState
*queue
;
1273 CBDATA_TYPE(externalAclState
);
1275 free_externalAclState(void *data
)
1277 externalAclState
*state
= static_cast<externalAclState
*>(data
);
1278 safe_free(state
->key
);
1279 cbdataReferenceDone(state
->callback_data
);
1280 cbdataReferenceDone(state
->def
);
1284 * The helper program receives queries on stdin, one
1285 * per line, and must return the result on on stdout
1287 * General result syntax:
1289 * OK/ERR keyword=value ...
1293 * user= The users name (login)
1294 * message= Message describing the reason
1295 * tag= A string tag to be applied to the request that triggered the acl match.
1296 * applies to both OK and ERR responses.
1297 * Won't override existing request tags.
1298 * log= A string to be used in access logging
1300 * Other keywords may be added to the protocol later
1302 * value needs to be enclosed in quotes if it may contain whitespace, or
1303 * the whitespace escaped using \ (\ escaping obviously also applies to
1308 externalAclHandleReply(void *data
, char *reply
)
1310 externalAclState
*state
= static_cast<externalAclState
*>(data
);
1311 externalAclState
*next
;
1316 ExternalACLEntryData entryData
;
1317 entryData
.result
= ACCESS_DENIED
;
1318 external_acl_entry
*entry
= NULL
;
1320 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply
<< "\"");
1323 status
= strwordtok(reply
, &t
);
1325 if (status
&& strcmp(status
, "OK") == 0)
1326 entryData
.result
= ACCESS_ALLOWED
;
1328 while ((token
= strwordtok(NULL
, &t
))) {
1329 value
= strchr(token
, '=');
1332 *value
= '\0'; /* terminate the token, and move up to the value */
1335 if (state
->def
->quote
== external_acl::QUOTE_METHOD_URL
)
1336 rfc1738_unescape(value
);
1338 if (strcmp(token
, "message") == 0)
1339 entryData
.message
= value
;
1340 else if (strcmp(token
, "error") == 0)
1341 entryData
.message
= value
;
1342 else if (strcmp(token
, "tag") == 0)
1343 entryData
.tag
= value
;
1344 else if (strcmp(token
, "log") == 0)
1345 entryData
.log
= value
;
1347 else if (strcmp(token
, "user") == 0)
1348 entryData
.user
= value
;
1349 else if (strcmp(token
, "password") == 0)
1350 entryData
.password
= value
;
1351 else if (strcmp(token
, "passwd") == 0)
1352 entryData
.password
= value
;
1353 else if (strcmp(token
, "login") == 0)
1354 entryData
.user
= value
;
1360 dlinkDelete(&state
->list
, &state
->def
->queue
);
1362 if (cbdataReferenceValid(state
->def
)) {
1364 entry
= external_acl_cache_add(state
->def
, state
->key
, entryData
);
1366 external_acl_entry
*oldentry
= (external_acl_entry
*)hash_lookup(state
->def
->cache
, state
->key
);
1369 external_acl_cache_delete(state
->def
, oldentry
);
1375 cbdataReferenceDone(state
->def
);
1377 if (state
->callback
&& cbdataReferenceValidDone(state
->callback_data
, &cbdata
))
1378 state
->callback(cbdata
, entry
);
1380 next
= state
->queue
;
1389 ACLExternal::ExternalAclLookup(ACLChecklist
*checklist
, ACLExternal
* me
)
1391 ExternalACLLookup::Start(checklist
, me
->data
, false);
1395 ExternalACLLookup::Start(ACLChecklist
*checklist
, external_acl_data
*acl
, bool inBackground
)
1397 external_acl
*def
= acl
->def
;
1399 ACLFilledChecklist
*ch
= Filled(checklist
);
1400 const char *key
= makeExternalAclKey(ch
, acl
);
1403 debugs(82, 2, HERE
<< (inBackground
? "bg" : "fg") << " lookup in '" <<
1404 def
->name
<< "' for '" << key
<< "'");
1406 /* Check for a pending lookup to hook into */
1407 // only possible if we are caching results.
1408 externalAclState
*oldstate
= NULL
;
1409 if (def
->cache_size
> 0) {
1410 for (dlink_node
*node
= def
->queue
.head
; node
; node
= node
->next
) {
1411 externalAclState
*oldstatetmp
= static_cast<externalAclState
*>(node
->data
);
1413 if (strcmp(key
, oldstatetmp
->key
) == 0) {
1414 oldstate
= oldstatetmp
;
1420 // A background refresh has no need to piggiback on a pending request:
1421 // When the pending request completes, the cache will be refreshed anyway.
1422 if (oldstate
&& inBackground
) {
1423 debugs(82, 7, HERE
<< "'" << def
->name
<< "' queue is already being refreshed (ch=" << ch
<< ")");
1427 externalAclState
*state
= cbdataAlloc(externalAclState
);
1428 state
->def
= cbdataReference(def
);
1430 state
->key
= xstrdup(key
);
1432 if (!inBackground
) {
1433 state
->callback
= &ExternalACLLookup::LookupDone
;
1434 state
->callback_data
= cbdataReference(checklist
);
1438 /* Hook into pending lookup */
1439 state
->queue
= oldstate
->queue
;
1440 oldstate
->queue
= state
;
1442 /* No pending lookup found. Sumbit to helper */
1444 /* Check for queue overload */
1446 if (def
->theHelper
->stats
.queue_size
>= (int)def
->theHelper
->childs
.n_running
) {
1447 debugs(82, 7, HERE
<< "'" << def
->name
<< "' queue is too long");
1448 assert(inBackground
); // or the caller should have checked
1453 /* Send it off to the helper */
1457 buf
.Printf("%s\n", key
);
1459 debugs(82, 4, "externalAclLookup: looking up for '" << key
<< "' in '" << def
->name
<< "'.");
1461 helperSubmit(def
->theHelper
, buf
.buf
, externalAclHandleReply
, state
);
1463 dlinkAdd(state
, &state
->list
, &def
->queue
);
1468 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key
<<
1469 "' in '" << def
->name
<< "' (ch=" << ch
<< ").");
1473 externalAclStats(StoreEntry
* sentry
)
1477 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1478 storeAppendPrintf(sentry
, "External ACL Statistics: %s\n", p
->name
);
1479 storeAppendPrintf(sentry
, "Cache size: %d\n", p
->cache
->count
);
1480 helperStats(sentry
, p
->theHelper
);
1481 storeAppendPrintf(sentry
, "\n");
1486 externalAclRegisterWithCacheManager(void)
1488 Mgr::RegisterAction("external_acl",
1489 "External ACL stats",
1490 externalAclStats
, 0, 1);
1494 externalAclInit(void)
1496 static int firstTimeInit
= 1;
1499 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1501 p
->cache
= hash_create((HASHCMP
*) strcmp
, hashPrime(1024), hash4
);
1504 p
->theHelper
= new helper(p
->name
);
1506 p
->theHelper
->cmdline
= p
->cmdline
;
1508 p
->theHelper
->childs
.updateLimits(p
->children
);
1510 p
->theHelper
->ipc_type
= IPC_TCP_SOCKET
;
1512 p
->theHelper
->addr
= p
->local_addr
;
1514 helperOpenServers(p
->theHelper
);
1517 if (firstTimeInit
) {
1519 CBDATA_INIT_TYPE_FREECB(externalAclState
, free_externalAclState
);
1522 externalAclRegisterWithCacheManager();
1526 externalAclShutdown(void)
1530 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1531 helperShutdown(p
->theHelper
);
1535 ExternalACLLookup
ExternalACLLookup::instance_
;
1537 ExternalACLLookup::Instance()
1543 ExternalACLLookup::checkForAsync(ACLChecklist
*checklist
)const
1545 /* TODO: optimise this - we probably have a pointer to this
1546 * around somewhere */
1547 ACL
*acl
= ACL::FindByName(AclMatchedName
);
1549 ACLExternal
*me
= dynamic_cast<ACLExternal
*> (acl
);
1551 checklist
->asyncInProgress(true);
1552 ACLExternal::ExternalAclLookup(checklist
, me
);
1555 /// Called when an async lookup returns
1557 ExternalACLLookup::LookupDone(void *data
, void *result
)
1559 ACLFilledChecklist
*checklist
= Filled(static_cast<ACLChecklist
*>(data
));
1560 checklist
->extacl_entry
= cbdataReference((external_acl_entry
*)result
);
1561 checklist
->asyncInProgress(false);
1562 checklist
->changeState (ACLChecklist::NullState::Instance());
1563 checklist
->matchNonBlocking();
1566 /* This registers "external" in the registry. To do dynamic definitions
1567 * of external ACL's, rather than a static prototype, have a Prototype instance
1568 * prototype in the class that defines each external acl 'class'.
1569 * Then, then the external acl instance is created, it self registers under
1571 * Be sure that clone is fully functional for that acl class though!
1573 ACL::Prototype
ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_
, "external");
1575 ACLExternal
ACLExternal::RegistryEntry_("external");
1578 ACLExternal::clone() const
1580 return new ACLExternal(*this);
1583 ACLExternal::ACLExternal (char const *theClass
) : data (NULL
), class_ (xstrdup (theClass
))
1586 ACLExternal::ACLExternal (ACLExternal
const & old
) : data (NULL
), class_ (old
.class_
? xstrdup (old
.class_
) : NULL
)
1588 /* we don't have copy constructors for the data yet */
1593 ACLExternal::typeString() const
1599 ACLExternal::isProxyAuth() const
1602 return data
->def
->require_auth
;