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.
44 #include "CacheManager.h"
45 #include "ExternalACL.h"
46 #include "ExternalACLEntry.h"
47 #include "auth/UserRequest.h"
48 #include "SquidTime.h"
51 #include "acl/FilledChecklist.h"
54 #include "ident/AclIdent.h"
56 #include "client_side.h"
57 #include "HttpRequest.h"
58 #include "HttpReply.h"
60 #include "auth/Gadgets.h"
64 #include "URLScheme.h"
67 #ifndef DEFAULT_EXTERNAL_ACL_TTL
68 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
70 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
71 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
74 typedef struct _external_acl_format external_acl_format
;
76 static char *makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
);
77 static void external_acl_cache_delete(external_acl
* def
, external_acl_entry
* entry
);
78 static int external_acl_entry_expired(external_acl
* def
, external_acl_entry
* entry
);
79 static int external_acl_grace_expired(external_acl
* def
, external_acl_entry
* entry
);
80 static void external_acl_cache_touch(external_acl
* def
, external_acl_entry
* entry
);
81 static external_acl_entry
*external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const &data
);
83 /******************************************************************
84 * external_acl directive
106 external_acl_format
*format
;
110 HelperChildConfig children
;
127 QUOTE_METHOD_SHELL
= 1,
131 IpAddress local_addr
;
134 struct _external_acl_format
{
156 EXT_ACL_HEADER_REQUEST
,
157 EXT_ACL_HEADER_REQUEST_MEMBER
,
158 EXT_ACL_HEADER_REQUEST_ID
,
159 EXT_ACL_HEADER_REQUEST_ID_MEMBER
,
161 EXT_ACL_HEADER_REPLY
,
162 EXT_ACL_HEADER_REPLY_MEMBER
,
163 EXT_ACL_HEADER_REPLY_ID
,
164 EXT_ACL_HEADER_REPLY_ID_MEMBER
,
169 EXT_ACL_USER_CERT_RAW
,
170 EXT_ACL_USER_CERTCHAIN_RAW
,
175 external_acl_format
*next
;
179 http_hdr_type header_id
;
182 /* FIXME: These are not really cbdata, but it is an easy way
183 * to get them pooled, refcounted, accounted and freed properly...
185 CBDATA_TYPE(external_acl
);
186 CBDATA_TYPE(external_acl_format
);
189 free_external_acl_format(void *data
)
191 external_acl_format
*p
= static_cast<external_acl_format
*>(data
);
192 safe_free(p
->header
);
196 free_external_acl(void *data
)
198 external_acl
*p
= static_cast<external_acl
*>(data
);
202 external_acl_format
*f
= p
->format
;
207 wordlistDestroy(&p
->cmdline
);
210 helperShutdown(p
->theHelper
);
215 while (p
->lru_list
.tail
)
216 external_acl_cache_delete(p
, static_cast<external_acl_entry
*>(p
->lru_list
.tail
->data
));
218 hashFreeMemory(p
->cache
);
222 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
223 * request or reply header to external helper.
225 \param header - the token being parsed (without the identifying prefix)
226 \param type - format enum identifier for this element, pulled from identifying prefix
227 \param format - structure to contain all the info about this format element.
230 parse_header_token(external_acl_format
*format
, char *header
, const _external_acl_format::format_type type
)
235 /** Cut away the closing brace */
236 end
= strchr(header
, '}');
237 if (end
&& strlen(end
) == 1)
242 member
= strchr(header
, ':');
245 /* Split in header and member */
248 if (!xisalnum(*member
))
249 format
->separator
= *member
++;
251 format
->separator
= ',';
253 format
->member
= xstrdup(member
);
255 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
256 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
;
258 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
;
263 format
->header
= xstrdup(header
);
264 format
->header_id
= httpHeaderIdByNameDef(header
, strlen(header
));
266 if (format
->header_id
!= -1) {
268 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
269 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
;
271 format
->type
= _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
;
273 if (type
== _external_acl_format::EXT_ACL_HEADER_REQUEST
)
274 format
->type
= _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
;
276 format
->type
= _external_acl_format::EXT_ACL_HEADER_REPLY_ID
;
282 parse_externalAclHelper(external_acl
** list
)
286 external_acl_format
**p
;
288 CBDATA_INIT_TYPE_FREECB(external_acl
, free_external_acl
);
289 CBDATA_INIT_TYPE_FREECB(external_acl_format
, free_external_acl_format
);
291 a
= cbdataAlloc(external_acl
);
294 a
->ttl
= DEFAULT_EXTERNAL_ACL_TTL
;
295 a
->negative_ttl
= -1;
296 a
->children
.n_max
= DEFAULT_EXTERNAL_ACL_CHILDREN
;
297 a
->children
.n_startup
= a
->children
.n_max
;
298 a
->children
.n_idle
= 1;
299 a
->local_addr
.SetLocalhost();
300 a
->quote
= external_acl::QUOTE_METHOD_URL
;
302 token
= strtok(NULL
, w_space
);
307 a
->name
= xstrdup(token
);
309 token
= strtok(NULL
, w_space
);
313 if (strncmp(token
, "ttl=", 4) == 0) {
314 a
->ttl
= atoi(token
+ 4);
315 } else if (strncmp(token
, "negative_ttl=", 13) == 0) {
316 a
->negative_ttl
= atoi(token
+ 13);
317 } else if (strncmp(token
, "children=", 9) == 0) {
318 a
->children
.n_max
= atoi(token
+ 9);
319 debugs(0, 0, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
320 } else if (strncmp(token
, "children-max=", 13) == 0) {
321 a
->children
.n_max
= atoi(token
+ 13);
322 } else if (strncmp(token
, "children-startup=", 17) == 0) {
323 a
->children
.n_startup
= atoi(token
+ 17);
324 } else if (strncmp(token
, "children-idle=", 14) == 0) {
325 a
->children
.n_idle
= atoi(token
+ 14);
326 } else if (strncmp(token
, "concurrency=", 12) == 0) {
327 a
->children
.concurrency
= atoi(token
+ 12);
328 } else if (strncmp(token
, "cache=", 6) == 0) {
329 a
->cache_size
= atoi(token
+ 6);
330 } else if (strncmp(token
, "grace=", 6) == 0) {
331 a
->grace
= atoi(token
+ 6);
332 } else if (strcmp(token
, "protocol=2.5") == 0) {
333 a
->quote
= external_acl::QUOTE_METHOD_SHELL
;
334 } else if (strcmp(token
, "protocol=3.0") == 0) {
335 a
->quote
= external_acl::QUOTE_METHOD_URL
;
336 } else if (strcmp(token
, "quote=url") == 0) {
337 a
->quote
= external_acl::QUOTE_METHOD_URL
;
338 } else if (strcmp(token
, "quote=shell") == 0) {
339 a
->quote
= external_acl::QUOTE_METHOD_SHELL
;
341 /* INET6: allow admin to configure some helpers explicitly to
342 bind to IPv4/v6 localhost port. */
343 } else if (strcmp(token
, "ipv4") == 0) {
344 if ( !a
->local_addr
.SetIPv4() ) {
345 debugs(3, 0, "WARNING: Error converting " << a
->local_addr
<< " to IPv4 in " << a
->name
);
347 } else if (strcmp(token
, "ipv6") == 0) {
349 debugs(3, 0, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a
->name
);
357 token
= strtok(NULL
, w_space
);
360 /* check that child startup value is sane. */
361 if ((a
->children
.n_startup
> a
->children
.n_max
)
362 a
->children
.n_startup
= a
->children
.n_max
;
364 /* check that child idle value is sane. */
365 if (a
->children
.n_idle
> a
->children
.n_max
)
366 a
->children
.n_idle
= a
->children
.n_max
;
367 if (a
->children
.n_idle
< 1)
368 a
->children
.n_idle
= 1;
370 if (a
->negative_ttl
== -1)
371 a
->negative_ttl
= a
->ttl
;
377 external_acl_format
*format
;
379 /* stop on first non-format token found */
384 format
= cbdataAlloc(external_acl_format
);
386 if (strncmp(token
, "%{", 2) == 0) {
387 // deprecated. but assume the old configs all referred to request headers.
388 debugs(82, DBG_IMPORTANT
, "WARNING: external_acl_type format %{...} is being replaced by %>{...} for : " << token
);
389 parse_header_token(format
, (token
+2), _external_acl_format::EXT_ACL_HEADER_REQUEST
);
390 } else if (strncmp(token
, "%>{", 3) == 0) {
391 parse_header_token(format
, (token
+3), _external_acl_format::EXT_ACL_HEADER_REQUEST
);
392 } else if (strncmp(token
, "%<{", 3) == 0) {
393 parse_header_token(format
, (token
+3), _external_acl_format::EXT_ACL_HEADER_REPLY
);
394 } else if (strcmp(token
, "%LOGIN") == 0) {
395 format
->type
= _external_acl_format::EXT_ACL_LOGIN
;
396 a
->require_auth
= true;
400 else if (strcmp(token
, "%IDENT") == 0)
401 format
->type
= _external_acl_format::EXT_ACL_IDENT
;
405 else if (strcmp(token
, "%SRC") == 0)
406 format
->type
= _external_acl_format::EXT_ACL_SRC
;
407 else if (strcmp(token
, "%SRCPORT") == 0)
408 format
->type
= _external_acl_format::EXT_ACL_SRCPORT
;
410 else if (strcmp(token
, "%SRCEUI48") == 0)
411 format
->type
= _external_acl_format::EXT_ACL_SRCEUI48
;
412 else if (strcmp(token
, "%SRCEUI64") == 0)
413 format
->type
= _external_acl_format::EXT_ACL_SRCEUI64
;
415 else if (strcmp(token
, "%MYADDR") == 0)
416 format
->type
= _external_acl_format::EXT_ACL_MYADDR
;
417 else if (strcmp(token
, "%MYPORT") == 0)
418 format
->type
= _external_acl_format::EXT_ACL_MYPORT
;
419 else if (strcmp(token
, "%URI") == 0)
420 format
->type
= _external_acl_format::EXT_ACL_URI
;
421 else if (strcmp(token
, "%DST") == 0)
422 format
->type
= _external_acl_format::EXT_ACL_DST
;
423 else if (strcmp(token
, "%PROTO") == 0)
424 format
->type
= _external_acl_format::EXT_ACL_PROTO
;
425 else if (strcmp(token
, "%PORT") == 0)
426 format
->type
= _external_acl_format::EXT_ACL_PORT
;
427 else if (strcmp(token
, "%PATH") == 0)
428 format
->type
= _external_acl_format::EXT_ACL_PATH
;
429 else if (strcmp(token
, "%METHOD") == 0)
430 format
->type
= _external_acl_format::EXT_ACL_METHOD
;
434 else if (strcmp(token
, "%USER_CERT") == 0)
435 format
->type
= _external_acl_format::EXT_ACL_USER_CERT_RAW
;
436 else if (strcmp(token
, "%USER_CERTCHAIN") == 0)
437 format
->type
= _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW
;
438 else if (strncmp(token
, "%USER_CERT_", 11) == 0) {
439 format
->type
= _external_acl_format::EXT_ACL_USER_CERT
;
440 format
->header
= xstrdup(token
+ 11);
441 } else if (strncmp(token
, "%CA_CERT_", 11) == 0) {
442 format
->type
= _external_acl_format::EXT_ACL_USER_CERT
;
443 format
->header
= xstrdup(token
+ 11);
447 else if (strcmp(token
, "%EXT_USER") == 0)
448 format
->type
= _external_acl_format::EXT_ACL_EXT_USER
;
450 debugs(0,0, "ERROR: Unknown Format token " << token
);
456 token
= strtok(NULL
, w_space
);
459 /* There must be at least one format token */
467 wordlistAdd(&a
->cmdline
, token
);
470 parse_wordlist(&a
->cmdline
);
473 list
= &(*list
)->next
;
479 dump_externalAclHelper(StoreEntry
* sentry
, const char *name
, const external_acl
* list
)
481 const external_acl
*node
;
482 const external_acl_format
*format
;
483 const wordlist
*word
;
485 for (node
= list
; node
; node
= node
->next
) {
486 storeAppendPrintf(sentry
, "%s %s", name
, node
->name
);
488 if (!node
->local_addr
.IsIPv6())
489 storeAppendPrintf(sentry
, " ipv4");
491 storeAppendPrintf(sentry
, " ipv6");
493 if (node
->ttl
!= DEFAULT_EXTERNAL_ACL_TTL
)
494 storeAppendPrintf(sentry
, " ttl=%d", node
->ttl
);
496 if (node
->negative_ttl
!= node
->ttl
)
497 storeAppendPrintf(sentry
, " negative_ttl=%d", node
->negative_ttl
);
500 storeAppendPrintf(sentry
, " grace=%d", node
->grace
);
502 if (node
->children
.n_max
!= DEFAULT_EXTERNAL_ACL_CHILDREN
)
503 storeAppendPrintf(sentry
, " children-max=%d", node
->children
.n_max
);
505 if (node
->children
.n_startup
!= 1)
506 storeAppendPrintf(sentry
, " children-startup=%d", node
->children
.n_startup
);
508 if (node
->children
.n_idle
!= (node
->children
.n_max
+ node
->children
.n_startup
) )
509 storeAppendPrintf(sentry
, " children-idle=%d", node
->children
.n_idle
);
511 if (node
->children
.concurrency
)
512 storeAppendPrintf(sentry
, " concurrency=%d", node
->children
.concurrency
);
515 storeAppendPrintf(sentry
, " cache=%d", node
->cache_size
);
517 for (format
= node
->format
; format
; format
= format
->next
) {
518 switch (format
->type
) {
520 case _external_acl_format::EXT_ACL_HEADER_REQUEST
:
521 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
:
522 storeAppendPrintf(sentry
, " %%>{%s}", format
->header
);
525 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
:
526 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
:
527 storeAppendPrintf(sentry
, " %%>{%s:%s}", format
->header
, format
->member
);
530 case _external_acl_format::EXT_ACL_HEADER_REPLY
:
531 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID
:
532 storeAppendPrintf(sentry
, " %%<{%s}", format
->header
);
535 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER
:
536 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
:
537 storeAppendPrintf(sentry
, " %%<{%s:%s}", format
->header
, format
->member
);
539 #define DUMP_EXT_ACL_TYPE(a) \
540 case _external_acl_format::EXT_ACL_##a: \
541 storeAppendPrintf(sentry, " %%%s", #a); \
544 DUMP_EXT_ACL_TYPE(LOGIN
);
547 DUMP_EXT_ACL_TYPE(IDENT
);
550 DUMP_EXT_ACL_TYPE(SRC
);
551 DUMP_EXT_ACL_TYPE(SRCPORT
);
553 DUMP_EXT_ACL_TYPE(SRCEUI48
);
554 DUMP_EXT_ACL_TYPE(SRCEUI64
);
557 DUMP_EXT_ACL_TYPE(MYADDR
);
558 DUMP_EXT_ACL_TYPE(MYPORT
);
559 DUMP_EXT_ACL_TYPE(URI
);
560 DUMP_EXT_ACL_TYPE(DST
);
561 DUMP_EXT_ACL_TYPE(PROTO
);
562 DUMP_EXT_ACL_TYPE(PORT
);
563 DUMP_EXT_ACL_TYPE(PATH
);
564 DUMP_EXT_ACL_TYPE(METHOD
);
567 case _external_acl_format::EXT_ACL_USER_CERT_RAW
:
568 storeAppendPrintf(sentry
, " %%USER_CERT");
571 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW
:
572 storeAppendPrintf(sentry
, " %%USER_CERTCHAIN");
575 case _external_acl_format::EXT_ACL_USER_CERT
:
576 storeAppendPrintf(sentry
, " %%USER_CERT_%s", format
->header
);
579 case _external_acl_format::EXT_ACL_CA_CERT
:
580 storeAppendPrintf(sentry
, " %%USER_CERT_%s", format
->header
);
584 DUMP_EXT_ACL_TYPE(EXT_USER
);
587 fatal("unknown external_acl format error");
592 for (word
= node
->cmdline
; word
; word
= word
->next
)
593 storeAppendPrintf(sentry
, " %s", word
->key
);
595 storeAppendPrintf(sentry
, "\n");
600 free_externalAclHelper(external_acl
** list
)
603 external_acl
*node
= *list
;
610 static external_acl
*
611 find_externalAclHelper(const char *name
)
615 for (node
= Config
.externalAclHelperList
; node
; node
= node
->next
) {
616 if (strcmp(node
->name
, name
) == 0)
626 (ExternalACLEntry
*anEntry
)
629 assert (anEntry
->def
== NULL
);
631 hash_join(cache
, anEntry
);
632 dlinkAdd(anEntry
, &anEntry
->lru
, &lru_list
);
637 external_acl::trimCache()
639 if (cache_size
&& cache_entries
>= cache_size
)
640 external_acl_cache_delete(this, static_cast<external_acl_entry
*>(lru_list
.tail
->data
));
644 /******************************************************************
648 struct _external_acl_data
{
653 CBDATA_TYPE(external_acl_data
);
655 free_external_acl_data(void *data
)
657 external_acl_data
*p
= static_cast<external_acl_data
*>(data
);
658 wordlistDestroy(&p
->arguments
);
659 cbdataReferenceDone(p
->def
);
670 CBDATA_INIT_TYPE_FREECB(external_acl_data
, free_external_acl_data
);
672 data
= cbdataAlloc(external_acl_data
);
674 token
= strtok(NULL
, w_space
);
679 data
->def
= cbdataReference(find_externalAclHelper(token
));
684 while ((token
= strtokFile())) {
685 wordlistAdd(&data
->arguments
, token
);
690 ACLExternal::valid () const
692 if (data
->def
->require_auth
) {
693 if (authenticateSchemeCount() == 0) {
694 debugs(28, 0, "Can't use proxy auth because no authentication schemes were compiled.");
698 if (authenticateActiveSchemeCount() == 0) {
699 debugs(28, 0, "Can't use proxy auth because no authentication schemes are fully configured.");
708 ACLExternal::empty () const
713 ACLExternal::~ACLExternal()
720 aclMatchExternal(external_acl_data
*acl
, ACLFilledChecklist
*ch
)
723 external_acl_entry
*entry
;
724 const char *key
= "";
725 debugs(82, 9, "aclMatchExternal: acl=\"" << acl
->def
->name
<< "\"");
726 entry
= ch
->extacl_entry
;
729 if (cbdataReferenceValid(entry
) && entry
->def
== acl
->def
&&
730 strcmp((char *)entry
->key
, key
) == 0) {
733 /* Not valid, or not ours.. get rid of it */
734 cbdataReferenceDone(ch
->extacl_entry
);
739 external_acl_message
= "MISSING REQUIRED INFORMATION";
742 if (acl
->def
->require_auth
) {
744 /* Make sure the user is authenticated */
746 if ((ti
= AuthenticateAcl(ch
)) != 1) {
747 debugs(82, 2, "aclMatchExternal: " << acl
->def
->name
<< " user not authenticated (" << ti
<< ")");
752 key
= makeExternalAclKey(ch
, acl
);
754 if (acl
->def
->require_auth
)
755 AUTHUSERREQUESTUNLOCK(ch
->auth_user_request
, "ACLChecklist via aclMatchExternal");
758 /* Not sufficient data to process */
762 entry
= static_cast<external_acl_entry
*>(hash_lookup(acl
->def
->cache
, key
));
764 if (!entry
|| external_acl_grace_expired(acl
->def
, entry
)) {
765 debugs(82, 2, "aclMatchExternal: " << acl
->def
->name
<< "(\"" << key
<< "\") = lookup needed");
766 debugs(82, 2, "aclMatchExternal: \"" << key
<< "\": entry=@" <<
767 entry
<< ", age=" << (entry
? (long int) squid_curtime
- entry
->date
: 0));
769 if (acl
->def
->theHelper
->stats
.queue_size
<= (int)acl
->def
->theHelper
->childs
.n_active
) {
770 debugs(82, 2, "aclMatchExternal: \"" << key
<< "\": queueing a call.");
771 ch
->changeState (ExternalACLLookup::Instance());
774 debugs(82, 2, "aclMatchExternal: \"" << key
<< "\": return -1.");
779 debugs(82, 1, "aclMatchExternal: '" << acl
->def
->name
<<
780 "' queue overload. Request rejected '" << key
<< "'.");
781 external_acl_message
= "SYSTEM TOO BUSY, TRY AGAIN LATER";
784 debugs(82, 1, "aclMatchExternal: '" << acl
->def
->name
<<
785 "' queue overload. Using stale result. '" << key
<< "'.");
786 /* Fall thru to processing below */
792 external_acl_cache_touch(acl
->def
, entry
);
793 result
= entry
->result
;
794 external_acl_message
= entry
->message
.termedBuf();
796 debugs(82, 2, "aclMatchExternal: " << acl
->def
->name
<< " = " << result
);
799 if (entry
->user
.size())
800 ch
->request
->extacl_user
= entry
->user
;
802 if (entry
->password
.size())
803 ch
->request
->extacl_passwd
= entry
->password
;
805 if (!ch
->request
->tag
.size())
806 ch
->request
->tag
= entry
->tag
;
808 if (entry
->log
.size())
809 ch
->request
->extacl_log
= entry
->log
;
816 ACLExternal::match(ACLChecklist
*checklist
)
818 return aclMatchExternal (data
, Filled(checklist
));
822 ACLExternal::dump() const
824 external_acl_data
const *acl
= data
;
825 wordlist
*result
= NULL
;
829 mb
.Printf("%s", acl
->def
->name
);
831 for (arg
= acl
->arguments
; arg
; arg
= arg
->next
) {
832 mb
.Printf(" %s", arg
->key
);
835 wordlistAdd(&result
, mb
.buf
);
840 /******************************************************************
845 external_acl_cache_touch(external_acl
* def
, external_acl_entry
* entry
)
847 dlinkDelete(&entry
->lru
, &def
->lru_list
);
848 dlinkAdd(entry
, &entry
->lru
, &def
->lru_list
);
852 makeExternalAclKey(ACLFilledChecklist
* ch
, external_acl_data
* acl_data
)
858 external_acl_format
*format
;
859 HttpRequest
*request
= ch
->request
;
860 HttpReply
*reply
= ch
->reply
;
863 for (format
= acl_data
->def
->format
; format
; format
= format
->next
) {
864 const char *str
= NULL
;
867 switch (format
->type
) {
869 case _external_acl_format::EXT_ACL_LOGIN
:
870 assert (ch
->auth_user_request
);
871 str
= ch
->auth_user_request
->username();
875 case _external_acl_format::EXT_ACL_IDENT
:
879 ch
->changeState(IdentLookup::Instance());
886 case _external_acl_format::EXT_ACL_SRC
:
887 str
= ch
->src_addr
.NtoA(buf
,sizeof(buf
));
890 case _external_acl_format::EXT_ACL_SRCPORT
:
891 snprintf(buf
, sizeof(buf
), "%d", request
->client_addr
.GetPort());
896 case _external_acl_format::EXT_ACL_SRCEUI48
:
897 if (request
->client_eui48
.encode(buf
, sizeof(buf
)))
901 case _external_acl_format::EXT_ACL_SRCEUI64
:
902 if (request
->client_eui64
.encode(buf
, sizeof(buf
)))
907 case _external_acl_format::EXT_ACL_MYADDR
:
908 str
= request
->my_addr
.NtoA(buf
, sizeof(buf
));
911 case _external_acl_format::EXT_ACL_MYPORT
:
912 snprintf(buf
, sizeof(buf
), "%d", request
->my_addr
.GetPort());
916 case _external_acl_format::EXT_ACL_URI
:
917 str
= urlCanonical(request
);
920 case _external_acl_format::EXT_ACL_DST
:
921 str
= request
->GetHost();
924 case _external_acl_format::EXT_ACL_PROTO
:
925 str
= ProtocolStr
[request
->protocol
];
928 case _external_acl_format::EXT_ACL_PORT
:
929 snprintf(buf
, sizeof(buf
), "%d", request
->port
);
933 case _external_acl_format::EXT_ACL_PATH
:
934 str
= request
->urlpath
.termedBuf();
937 case _external_acl_format::EXT_ACL_METHOD
:
938 str
= RequestMethodStr(request
->method
);
941 case _external_acl_format::EXT_ACL_HEADER_REQUEST
:
942 sb
= request
->header
.getByName(format
->header
);
943 str
= sb
.termedBuf();
946 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID
:
947 sb
= request
->header
.getStrOrList(format
->header_id
);
948 str
= sb
.termedBuf();
951 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER
:
952 sb
= request
->header
.getByNameListMember(format
->header
, format
->member
, format
->separator
);
953 str
= sb
.termedBuf();
956 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER
:
957 sb
= request
->header
.getListMember(format
->header_id
, format
->member
, format
->separator
);
958 str
= sb
.termedBuf();
961 case _external_acl_format::EXT_ACL_HEADER_REPLY
:
963 sb
= reply
->header
.getByName(format
->header
);
964 str
= sb
.termedBuf();
968 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID
:
970 sb
= reply
->header
.getStrOrList(format
->header_id
);
971 str
= sb
.termedBuf();
975 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER
:
977 sb
= reply
->header
.getByNameListMember(format
->header
, format
->member
, format
->separator
);
978 str
= sb
.termedBuf();
982 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER
:
984 sb
= reply
->header
.getListMember(format
->header_id
, format
->member
, format
->separator
);
985 str
= sb
.termedBuf();
990 case _external_acl_format::EXT_ACL_USER_CERT_RAW
:
992 if (ch
->conn() != NULL
) {
993 SSL
*ssl
= fd_table
[ch
->conn()->fd
].ssl
;
996 str
= sslGetUserCertificatePEM(ssl
);
1001 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW
:
1003 if (ch
->conn() != NULL
) {
1004 SSL
*ssl
= fd_table
[ch
->conn()->fd
].ssl
;
1007 str
= sslGetUserCertificateChainPEM(ssl
);
1012 case _external_acl_format::EXT_ACL_USER_CERT
:
1014 if (ch
->conn() != NULL
) {
1015 SSL
*ssl
= fd_table
[ch
->conn()->fd
].ssl
;
1018 str
= sslGetUserAttribute(ssl
, format
->header
);
1023 case _external_acl_format::EXT_ACL_CA_CERT
:
1025 if (ch
->conn() != NULL
) {
1026 SSL
*ssl
= fd_table
[ch
->conn()->fd
].ssl
;
1029 str
= sslGetCAAttribute(ssl
, format
->header
);
1035 case _external_acl_format::EXT_ACL_EXT_USER
:
1036 str
= request
->extacl_user
.termedBuf();
1039 case _external_acl_format::EXT_ACL_UNKNOWN
:
1041 case _external_acl_format::EXT_ACL_END
:
1042 fatal("unknown external_acl format error");
1056 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
1057 const char *quoted
= rfc1738_escape(str
);
1058 mb
.append(quoted
, strlen(quoted
));
1060 strwordquote(&mb
, str
);
1068 for (arg
= acl_data
->arguments
; arg
; arg
= arg
->next
) {
1072 if (acl_data
->def
->quote
== external_acl::QUOTE_METHOD_URL
) {
1073 const char *quoted
= rfc1738_escape(arg
->key
);
1074 mb
.append(quoted
, strlen(quoted
));
1076 strwordquote(&mb
, arg
->key
);
1086 external_acl_entry_expired(external_acl
* def
, external_acl_entry
* entry
)
1088 if (entry
->date
+ (entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
) < squid_curtime
)
1095 external_acl_grace_expired(external_acl
* def
, external_acl_entry
* entry
)
1098 ttl
= entry
->result
== 1 ? def
->ttl
: def
->negative_ttl
;
1099 ttl
= (ttl
* (100 - def
->grace
)) / 100;
1101 if (entry
->date
+ ttl
< squid_curtime
)
1107 static external_acl_entry
*
1108 external_acl_cache_add(external_acl
* def
, const char *key
, ExternalACLEntryData
const & data
)
1110 ExternalACLEntry
*entry
= static_cast<ExternalACLEntry
*>(hash_lookup(def
->cache
, key
));
1111 debugs(82, 2, "external_acl_cache_add: Adding '" << key
<< "' = " << data
.result
);
1114 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1115 entry
->update (data
);
1116 external_acl_cache_touch(def
, entry
);
1121 entry
= new ExternalACLEntry
;
1122 entry
->key
= xstrdup(key
);
1123 entry
->update (data
);
1132 external_acl_cache_delete(external_acl
* def
, external_acl_entry
* entry
)
1134 assert (entry
->def
== def
);
1135 hash_remove_link(def
->cache
, entry
);
1136 dlinkDelete(&entry
->lru
, &def
->lru_list
);
1137 def
->cache_entries
-= 1;
1141 /******************************************************************
1142 * external_acl helpers
1145 typedef struct _externalAclState externalAclState
;
1147 struct _externalAclState
{
1149 void *callback_data
;
1153 externalAclState
*queue
;
1156 CBDATA_TYPE(externalAclState
);
1158 free_externalAclState(void *data
)
1160 externalAclState
*state
= static_cast<externalAclState
*>(data
);
1161 safe_free(state
->key
);
1162 cbdataReferenceDone(state
->callback_data
);
1163 cbdataReferenceDone(state
->def
);
1167 * The helper program receives queries on stdin, one
1168 * per line, and must return the result on on stdout
1170 * General result syntax:
1172 * OK/ERR keyword=value ...
1176 * user= The users name (login)
1177 * message= Message describing the reason
1178 * tag= A string tag to be applied to the request that triggered the acl match.
1179 * applies to both OK and ERR responses.
1180 * Won't override existing request tags.
1181 * log= A string to be used in access logging
1183 * Other keywords may be added to the protocol later
1185 * value needs to be enclosed in quotes if it may contain whitespace, or
1186 * the whitespace escaped using \ (\ escaping obviously also applies to
1191 externalAclHandleReply(void *data
, char *reply
)
1193 externalAclState
*state
= static_cast<externalAclState
*>(data
);
1194 externalAclState
*next
;
1199 ExternalACLEntryData entryData
;
1200 entryData
.result
= 0;
1201 external_acl_entry
*entry
= NULL
;
1203 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply
<< "\"");
1206 status
= strwordtok(reply
, &t
);
1208 if (status
&& strcmp(status
, "OK") == 0)
1209 entryData
.result
= 1;
1211 while ((token
= strwordtok(NULL
, &t
))) {
1212 value
= strchr(token
, '=');
1215 *value
++ = '\0'; /* terminate the token, and move up to the value */
1217 if (state
->def
->quote
== external_acl::QUOTE_METHOD_URL
)
1218 rfc1738_unescape(value
);
1220 if (strcmp(token
, "user") == 0)
1221 entryData
.user
= value
;
1222 else if (strcmp(token
, "message") == 0)
1223 entryData
.message
= value
;
1224 else if (strcmp(token
, "error") == 0)
1225 entryData
.message
= value
;
1226 else if (strcmp(token
, "tag") == 0)
1227 entryData
.tag
= value
;
1228 else if (strcmp(token
, "log") == 0)
1229 entryData
.log
= value
;
1230 else if (strcmp(token
, "password") == 0)
1231 entryData
.password
= value
;
1232 else if (strcmp(token
, "passwd") == 0)
1233 entryData
.password
= value
;
1234 else if (strcmp(token
, "login") == 0)
1235 entryData
.user
= value
;
1240 dlinkDelete(&state
->list
, &state
->def
->queue
);
1242 if (cbdataReferenceValid(state
->def
)) {
1244 entry
= external_acl_cache_add(state
->def
, state
->key
, entryData
);
1246 external_acl_entry
*oldentry
= (external_acl_entry
*)hash_lookup(state
->def
->cache
, state
->key
);
1249 external_acl_cache_delete(state
->def
, oldentry
);
1255 cbdataReferenceDone(state
->def
);
1257 if (state
->callback
&& cbdataReferenceValidDone(state
->callback_data
, &cbdata
))
1258 state
->callback(cbdata
, entry
);
1260 next
= state
->queue
;
1269 ACLExternal::ExternalAclLookup(ACLChecklist
*checklist
, ACLExternal
* me
, EAH
* callback
, void *callback_data
)
1272 external_acl_data
*acl
= me
->data
;
1273 external_acl
*def
= acl
->def
;
1274 externalAclState
*state
;
1276 externalAclState
*oldstate
= NULL
;
1279 ACLFilledChecklist
*ch
= Filled(checklist
);
1280 if (acl
->def
->require_auth
) {
1282 /* Make sure the user is authenticated */
1284 if ((ti
= AuthenticateAcl(ch
)) != 1) {
1285 debugs(82, 1, "externalAclLookup: " << acl
->def
->name
<<
1286 " user authentication failure (" << ti
<< ", ch=" << ch
<< ")");
1287 callback(callback_data
, NULL
);
1292 const char *key
= makeExternalAclKey(ch
, acl
);
1295 debugs(82, 1, "externalAclLookup: lookup in '" << def
->name
<<
1296 "', prerequisit failure (ch=" << ch
<< ")");
1297 callback(callback_data
, NULL
);
1301 debugs(82, 2, "externalAclLookup: lookup in '" << def
->name
<< "' for '" << key
<< "'");
1303 external_acl_entry
*entry
= static_cast<external_acl_entry
*>(hash_lookup(def
->cache
, key
));
1305 if (entry
&& external_acl_entry_expired(def
, entry
))
1308 /* Check for a pending lookup to hook into */
1309 for (node
= def
->queue
.head
; node
; node
= node
->next
) {
1310 externalAclState
*oldstatetmp
= static_cast<externalAclState
*>(node
->data
);
1312 if (strcmp(key
, oldstatetmp
->key
) == 0) {
1313 oldstate
= oldstatetmp
;
1318 if (entry
&& external_acl_grace_expired(def
, entry
)) {
1320 debugs(82, 4, "externalAclLookup: in grace period, but already pending lookup ('" << key
<< "', ch=" << ch
<< ")");
1321 callback(callback_data
, entry
);
1324 graceful
= 1; // grace expired, (neg)ttl did not, and we must start a new lookup.
1328 // The entry is in the cache, grace_ttl did not expired.
1329 if (!graceful
&& entry
&& !external_acl_grace_expired(def
, entry
)) {
1330 /* Should not really happen, but why not.. */
1331 callback(callback_data
, entry
);
1332 debugs(82, 4, "externalAclLookup: no lookup pending for '" << key
<< "', and grace not expired");
1333 debugs(82, 4, "externalAclLookup: (what tha' hell?)");
1337 /* No pending lookup found. Sumbit to helper */
1338 state
= cbdataAlloc(externalAclState
);
1340 state
->def
= cbdataReference(def
);
1342 state
->key
= xstrdup(key
);
1345 state
->callback
= callback
;
1346 state
->callback_data
= cbdataReference(callback_data
);
1350 /* Hook into pending lookup */
1351 state
->queue
= oldstate
->queue
;
1352 oldstate
->queue
= state
;
1354 /* Check for queue overload */
1356 if (def
->theHelper
->stats
.queue_size
>= (int)def
->theHelper
->childs
.n_running
) {
1357 debugs(82, 1, "externalAclLookup: '" << def
->name
<< "' queue overload (ch=" << ch
<< ")");
1359 callback(callback_data
, entry
);
1363 /* Send it off to the helper */
1366 buf
.Printf("%s\n", key
);
1368 debugs(82, 4, "externalAclLookup: looking up for '" << key
<< "' in '" << def
->name
<< "'.");
1370 helperSubmit(def
->theHelper
, buf
.buf
, externalAclHandleReply
, state
);
1372 dlinkAdd(state
, &state
->list
, &def
->queue
);
1378 /* No need to wait during grace period */
1379 debugs(82, 4, "externalAclLookup: no need to wait for the result of '" <<
1380 key
<< "' in '" << def
->name
<< "' (ch=" << ch
<< ").");
1381 debugs(82, 4, "externalAclLookup: using cached entry " << entry
);
1383 if (entry
!= NULL
) {
1384 debugs(82, 4, "externalAclLookup: entry = { date=" <<
1385 (long unsigned int) entry
->date
<< ", result=" <<
1386 entry
->result
<< ", user=" << entry
->user
<< " tag=" <<
1387 entry
->tag
<< " log=" << entry
->log
<< " }");
1391 callback(callback_data
, entry
);
1395 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key
<<
1396 "' in '" << def
->name
<< "' (ch=" << ch
<< ").");
1400 externalAclStats(StoreEntry
* sentry
)
1404 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1405 storeAppendPrintf(sentry
, "External ACL Statistics: %s\n", p
->name
);
1406 storeAppendPrintf(sentry
, "Cache size: %d\n", p
->cache
->count
);
1407 helperStats(sentry
, p
->theHelper
);
1408 storeAppendPrintf(sentry
, "\n");
1413 externalAclRegisterWithCacheManager(void)
1415 CacheManager::GetInstance()->
1416 registerAction("external_acl",
1417 "External ACL stats",
1418 externalAclStats
, 0, 1);
1422 externalAclInit(void)
1424 static int firstTimeInit
= 1;
1427 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1429 p
->cache
= hash_create((HASHCMP
*) strcmp
, hashPrime(1024), hash4
);
1432 p
->theHelper
= new helper(p
->name
);
1434 p
->theHelper
->cmdline
= p
->cmdline
;
1436 p
->theHelper
->childs
= p
->children
;
1438 p
->theHelper
->ipc_type
= IPC_TCP_SOCKET
;
1440 p
->theHelper
->addr
= p
->local_addr
;
1442 helperOpenServers(p
->theHelper
);
1445 if (firstTimeInit
) {
1447 CBDATA_INIT_TYPE_FREECB(externalAclState
, free_externalAclState
);
1450 externalAclRegisterWithCacheManager();
1454 externalAclShutdown(void)
1458 for (p
= Config
.externalAclHelperList
; p
; p
= p
->next
) {
1459 helperShutdown(p
->theHelper
);
1463 ExternalACLLookup
ExternalACLLookup::instance_
;
1465 ExternalACLLookup::Instance()
1471 ExternalACLLookup::checkForAsync(ACLChecklist
*checklist
)const
1473 /* TODO: optimise this - we probably have a pointer to this
1474 * around somewhere */
1475 ACL
*acl
= ACL::FindByName(AclMatchedName
);
1477 ACLExternal
*me
= dynamic_cast<ACLExternal
*> (acl
);
1479 checklist
->asyncInProgress(true);
1480 ACLExternal::ExternalAclLookup(checklist
, me
, LookupDone
, checklist
);
1484 ExternalACLLookup::LookupDone(void *data
, void *result
)
1486 ACLFilledChecklist
*checklist
= Filled(static_cast<ACLChecklist
*>(data
));
1487 checklist
->extacl_entry
= cbdataReference((external_acl_entry
*)result
);
1488 checklist
->asyncInProgress(false);
1489 checklist
->changeState (ACLChecklist::NullState::Instance());
1493 /* This registers "external" in the registry. To do dynamic definitions
1494 * of external ACL's, rather than a static prototype, have a Prototype instance
1495 * prototype in the class that defines each external acl 'class'.
1496 * Then, then the external acl instance is created, it self registers under
1498 * Be sure that clone is fully functional for that acl class though!
1500 ACL::Prototype
ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_
, "external");
1502 ACLExternal
ACLExternal::RegistryEntry_("external");
1505 ACLExternal::clone() const
1507 return new ACLExternal(*this);
1510 ACLExternal::ACLExternal (char const *theClass
) : data (NULL
), class_ (xstrdup (theClass
))
1513 ACLExternal::ACLExternal (ACLExternal
const & old
) : data (NULL
), class_ (old
.class_
? xstrdup (old
.class_
) : NULL
)
1515 /* we don't have copy constructors for the data yet */
1520 ACLExternal::typeString() const
1526 ACLExternal::isProxyAuth() const
1528 return data
->def
->require_auth
;