]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / external_acl.cc
1
2 /*
3 * $Id$
4 *
5 * DEBUG: section 82 External ACL
6 * AUTHOR: Henrik Nordstrom, MARA Systems AB
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
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.
17 *
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.
26 *
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.
31 *
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.
36 *
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.
40 *
41 */
42
43 #include "squid.h"
44 #include "acl/Acl.h"
45 #include "acl/FilledChecklist.h"
46 #include "client_side.h"
47 #include "comm/Connection.h"
48 #include "ExternalACLEntry.h"
49 #include "ExternalACL.h"
50 #include "fde.h"
51 #include "helper.h"
52 #include "HttpReply.h"
53 #include "HttpRequest.h"
54 #include "ip/tools.h"
55 #include "MemBuf.h"
56 #include "mgr/Registration.h"
57 #include "protos.h"
58 #include "rfc1738.h"
59 #include "SquidTime.h"
60 #include "Store.h"
61 #include "URLScheme.h"
62 #include "wordlist.h"
63 #if USE_SSL
64 #include "ssl/support.h"
65 #endif
66 #if USE_AUTH
67 #include "auth/Acl.h"
68 #include "auth/Gadgets.h"
69 #include "auth/UserRequest.h"
70 #endif
71 #if USE_IDENT
72 #include "ident/AclIdent.h"
73 #endif
74
75 #ifndef DEFAULT_EXTERNAL_ACL_TTL
76 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
77 #endif
78 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
79 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
80 #endif
81
82 typedef struct _external_acl_format external_acl_format;
83
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);
90
91 /******************************************************************
92 * external_acl directive
93 */
94
95 class external_acl
96 {
97
98 public:
99 external_acl *next;
100
101 void add(ExternalACLEntry *);
102
103 void trimCache();
104
105 int ttl;
106
107 int negative_ttl;
108
109 int grace;
110
111 char *name;
112
113 external_acl_format *format;
114
115 wordlist *cmdline;
116
117 HelperChildConfig children;
118
119 helper *theHelper;
120
121 hash_table *cache;
122
123 dlink_list lru_list;
124
125 int cache_size;
126
127 int cache_entries;
128
129 dlink_list queue;
130
131 #if USE_AUTH
132 /**
133 * Configuration flag. May only be altered by the configuration parser.
134 *
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.
137 */
138 bool require_auth;
139 #endif
140
141 enum {
142 QUOTE_METHOD_SHELL = 1,
143 QUOTE_METHOD_URL
144 } quote;
145
146 Ip::Address local_addr;
147 };
148
149 struct _external_acl_format {
150 enum format_type {
151 EXT_ACL_UNKNOWN,
152 #if USE_AUTH
153 EXT_ACL_LOGIN,
154 #endif
155 #if USE_IDENT
156 EXT_ACL_IDENT,
157 #endif
158 EXT_ACL_SRC,
159 EXT_ACL_SRCPORT,
160 #if USE_SQUID_EUI
161 EXT_ACL_SRCEUI48,
162 EXT_ACL_SRCEUI64,
163 #endif
164 EXT_ACL_MYADDR,
165 EXT_ACL_MYPORT,
166 EXT_ACL_URI,
167 EXT_ACL_DST,
168 EXT_ACL_PROTO,
169 EXT_ACL_PORT,
170 EXT_ACL_PATH,
171 EXT_ACL_METHOD,
172
173 EXT_ACL_HEADER_REQUEST,
174 EXT_ACL_HEADER_REQUEST_MEMBER,
175 EXT_ACL_HEADER_REQUEST_ID,
176 EXT_ACL_HEADER_REQUEST_ID_MEMBER,
177
178 EXT_ACL_HEADER_REPLY,
179 EXT_ACL_HEADER_REPLY_MEMBER,
180 EXT_ACL_HEADER_REPLY_ID,
181 EXT_ACL_HEADER_REPLY_ID_MEMBER,
182
183 #if USE_SSL
184 EXT_ACL_USER_CERT,
185 EXT_ACL_CA_CERT,
186 EXT_ACL_USER_CERT_RAW,
187 EXT_ACL_USER_CERTCHAIN_RAW,
188 #endif
189 #if USE_AUTH
190 EXT_ACL_EXT_USER,
191 #endif
192 EXT_ACL_EXT_LOG,
193 EXT_ACL_TAG,
194 EXT_ACL_PERCENT,
195 EXT_ACL_END
196 } type;
197 external_acl_format *next;
198 char *header;
199 char *member;
200 char separator;
201 http_hdr_type header_id;
202 };
203
204 /* FIXME: These are not really cbdata, but it is an easy way
205 * to get them pooled, refcounted, accounted and freed properly...
206 */
207 CBDATA_TYPE(external_acl);
208 CBDATA_TYPE(external_acl_format);
209
210 static void
211 free_external_acl_format(void *data)
212 {
213 external_acl_format *p = static_cast<external_acl_format *>(data);
214 safe_free(p->header);
215 }
216
217 static void
218 free_external_acl(void *data)
219 {
220 external_acl *p = static_cast<external_acl *>(data);
221 safe_free(p->name);
222
223 while (p->format) {
224 external_acl_format *f = p->format;
225 p->format = f->next;
226 cbdataFree(f);
227 }
228
229 wordlistDestroy(&p->cmdline);
230
231 if (p->theHelper) {
232 helperShutdown(p->theHelper);
233 delete p->theHelper;
234 p->theHelper = NULL;
235 }
236
237 while (p->lru_list.tail)
238 external_acl_cache_delete(p, static_cast<external_acl_entry *>(p->lru_list.tail->data));
239 if (p->cache)
240 hashFreeMemory(p->cache);
241 }
242
243 /**
244 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
245 * request or reply header to external helper.
246 *
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.
250 */
251 void
252 parse_header_token(external_acl_format *format, char *header, const _external_acl_format::format_type type)
253 {
254 /* header format */
255 char *member, *end;
256
257 /** Cut away the closing brace */
258 end = strchr(header, '}');
259 if (end && strlen(end) == 1)
260 *end = '\0';
261 else
262 self_destruct();
263
264 member = strchr(header, ':');
265
266 if (member) {
267 /* Split in header and member */
268 *member = '\0';
269 ++member;
270
271 if (!xisalnum(*member)) {
272 format->separator = *member;
273 ++member;
274 } else {
275 format->separator = ',';
276 }
277
278 format->member = xstrdup(member);
279
280 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
281 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
282 else
283 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
284 } else {
285 format->type = type;
286 }
287
288 format->header = xstrdup(header);
289 format->header_id = httpHeaderIdByNameDef(header, strlen(header));
290
291 if (format->header_id != -1) {
292 if (member) {
293 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
294 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER;
295 else
296 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER;
297 } else {
298 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
299 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID;
300 else
301 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID;
302 }
303 }
304 }
305
306 void
307 parse_externalAclHelper(external_acl ** list)
308 {
309 external_acl *a;
310 char *token;
311 external_acl_format **p;
312
313 CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
314 CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
315
316 a = cbdataAlloc(external_acl);
317
318 /* set defaults */
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;
327
328 token = strtok(NULL, w_space);
329
330 if (!token)
331 self_destruct();
332
333 a->name = xstrdup(token);
334
335 token = strtok(NULL, w_space);
336
337 /* Parse options */
338 while (token) {
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;
366
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 );
372 }
373 } else if (strcmp(token, "ipv6") == 0) {
374 if (!Ip::EnableIpv6)
375 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
376 // else nothing to do.
377 } else {
378 break;
379 }
380
381 token = strtok(NULL, w_space);
382 }
383
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;
387
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;
393
394 if (a->negative_ttl == -1)
395 a->negative_ttl = a->ttl;
396
397 /* Parse format */
398 p = &a->format;
399
400 while (token) {
401 external_acl_format *format;
402
403 /* stop on first non-format token found */
404
405 if (*token != '%')
406 break;
407
408 format = cbdataAlloc(external_acl_format);
409
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);
418 #if USE_AUTH
419 } else if (strcmp(token, "%LOGIN") == 0) {
420 format->type = _external_acl_format::EXT_ACL_LOGIN;
421 a->require_auth = true;
422 #endif
423 }
424
425 #if USE_IDENT
426 else if (strcmp(token, "%IDENT") == 0)
427 format->type = _external_acl_format::EXT_ACL_IDENT;
428
429 #endif
430
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;
435 #if USE_SQUID_EUI
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;
440 #endif
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;
457
458 #if USE_SSL
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);
469 }
470 #endif
471 #if USE_AUTH
472 else if (strcmp(token, "%EXT_USER") == 0)
473 format->type = _external_acl_format::EXT_ACL_EXT_USER;
474 #endif
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;
481 else {
482 debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
483 self_destruct();
484 }
485
486 *p = format;
487 p = &format->next;
488 token = strtok(NULL, w_space);
489 }
490
491 /* There must be at least one format token */
492 if (!a->format)
493 self_destruct();
494
495 /* helper */
496 if (!token)
497 self_destruct();
498
499 wordlistAdd(&a->cmdline, token);
500
501 /* arguments */
502 parse_wordlist(&a->cmdline);
503
504 while (*list)
505 list = &(*list)->next;
506
507 *list = a;
508 }
509
510 void
511 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
512 {
513 const external_acl *node;
514 const external_acl_format *format;
515 const wordlist *word;
516
517 for (node = list; node; node = node->next) {
518 storeAppendPrintf(sentry, "%s %s", name, node->name);
519
520 if (!node->local_addr.IsIPv6())
521 storeAppendPrintf(sentry, " ipv4");
522 else
523 storeAppendPrintf(sentry, " ipv6");
524
525 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
526 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
527
528 if (node->negative_ttl != node->ttl)
529 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
530
531 if (node->grace)
532 storeAppendPrintf(sentry, " grace=%d", node->grace);
533
534 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
535 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
536
537 if (node->children.n_startup != 1)
538 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
539
540 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
541 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
542
543 if (node->children.concurrency)
544 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
545
546 if (node->cache)
547 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
548
549 for (format = node->format; format; format = format->next) {
550 switch (format->type) {
551
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);
555 break;
556
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);
560 break;
561
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);
565 break;
566
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);
570 break;
571 #define DUMP_EXT_ACL_TYPE(a) \
572 case _external_acl_format::EXT_ACL_##a: \
573 storeAppendPrintf(sentry, " %%%s", #a); \
574 break
575 #define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
576 case _external_acl_format::EXT_ACL_##a: \
577 storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
578 break
579 #if USE_AUTH
580 DUMP_EXT_ACL_TYPE(LOGIN);
581 #endif
582 #if USE_IDENT
583
584 DUMP_EXT_ACL_TYPE(IDENT);
585 #endif
586
587 DUMP_EXT_ACL_TYPE(SRC);
588 DUMP_EXT_ACL_TYPE(SRCPORT);
589 #if USE_SQUID_EUI
590 DUMP_EXT_ACL_TYPE(SRCEUI48);
591 DUMP_EXT_ACL_TYPE(SRCEUI64);
592 #endif
593
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);
602 #if USE_SSL
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);
607 #endif
608 #if USE_AUTH
609 DUMP_EXT_ACL_TYPE(EXT_USER);
610 #endif
611 DUMP_EXT_ACL_TYPE(EXT_LOG);
612 DUMP_EXT_ACL_TYPE(TAG);
613 DUMP_EXT_ACL_TYPE_FMT(PERCENT, " %%%%");
614 default:
615 fatal("unknown external_acl format error");
616 break;
617 }
618 }
619
620 for (word = node->cmdline; word; word = word->next)
621 storeAppendPrintf(sentry, " %s", word->key);
622
623 storeAppendPrintf(sentry, "\n");
624 }
625 }
626
627 void
628 free_externalAclHelper(external_acl ** list)
629 {
630 while (*list) {
631 external_acl *node = *list;
632 *list = node->next;
633 node->next = NULL;
634 cbdataFree(node);
635 }
636 }
637
638 static external_acl *
639 find_externalAclHelper(const char *name)
640 {
641 external_acl *node;
642
643 for (node = Config.externalAclHelperList; node; node = node->next) {
644 if (strcmp(node->name, name) == 0)
645 return node;
646 }
647
648 return NULL;
649 }
650
651 void
652 external_acl::add(ExternalACLEntry *anEntry)
653 {
654 trimCache();
655 assert (anEntry->def == NULL);
656 anEntry->def = this;
657 hash_join(cache, anEntry);
658 dlinkAdd(anEntry, &anEntry->lru, &lru_list);
659 ++cache_entries;
660 }
661
662 void
663 external_acl::trimCache()
664 {
665 if (cache_size && cache_entries >= cache_size)
666 external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
667 }
668
669 /******************************************************************
670 * external acl type
671 */
672
673 struct _external_acl_data {
674 external_acl *def;
675 wordlist *arguments;
676 };
677
678 CBDATA_TYPE(external_acl_data);
679 static void
680 free_external_acl_data(void *data)
681 {
682 external_acl_data *p = static_cast<external_acl_data *>(data);
683 wordlistDestroy(&p->arguments);
684 cbdataReferenceDone(p->def);
685 }
686
687 void
688 ACLExternal::parse()
689 {
690 char *token;
691
692 if (data)
693 self_destruct();
694
695 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
696
697 data = cbdataAlloc(external_acl_data);
698
699 token = strtok(NULL, w_space);
700
701 if (!token)
702 self_destruct();
703
704 data->def = cbdataReference(find_externalAclHelper(token));
705
706 if (!data->def)
707 self_destruct();
708
709 while ((token = strtokFile())) {
710 wordlistAdd(&data->arguments, token);
711 }
712 }
713
714 bool
715 ACLExternal::valid () const
716 {
717 #if USE_AUTH
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.");
721 return false;
722 }
723
724 if (authenticateActiveSchemeCount() == 0) {
725 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
726 return false;
727 }
728 }
729 #endif
730
731 return true;
732 }
733
734 bool
735 ACLExternal::empty () const
736 {
737 return false;
738 }
739
740 ACLExternal::~ACLExternal()
741 {
742 cbdataFree(data);
743 safe_free (class_);
744 }
745
746 static void
747 copyResultsFromEntry(HttpRequest *req, external_acl_entry *entry)
748 {
749 if (req) {
750 #if USE_AUTH
751 if (entry->user.size())
752 req->extacl_user = entry->user;
753
754 if (entry->password.size())
755 req->extacl_passwd = entry->password;
756 #endif
757 if (!req->tag.size())
758 req->tag = entry->tag;
759
760 if (entry->log.size())
761 req->extacl_log = entry->log;
762
763 if (entry->message.size())
764 req->extacl_message = entry->message;
765 }
766 }
767
768 static allow_t
769 aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
770 {
771 const char *key = "";
772 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
773 external_acl_entry *entry = ch->extacl_entry;
774
775 if (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);
783 entry = NULL;
784 }
785 } else {
786 /* Not valid, or not ours.. get rid of it */
787 debugs(82, 9, HERE << "entry " << entry << " not valid or not ours. Discarded.");
788 if (entry) {
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 << "'");
792 }
793 cbdataReferenceDone(ch->extacl_entry);
794 entry = NULL;
795 }
796 }
797
798 external_acl_message = "MISSING REQUIRED INFORMATION";
799
800 if (!entry) {
801 debugs(82, 9, HERE << "No helper entry available");
802 #if USE_AUTH
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 << ")");
809 return ti;
810 }
811 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
812 }
813 #endif
814 key = makeExternalAclKey(ch, acl);
815
816 if (!key) {
817 /* Not sufficient data to process */
818 return ACCESS_DUNNO;
819 }
820
821 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
822
823 external_acl_entry *staleEntry = entry;
824 if (entry && external_acl_entry_expired(acl->def, entry))
825 entry = NULL;
826
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 << ").");
832 }
833
834 if (!entry) {
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));
838
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
844 } else {
845 if (!staleEntry) {
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";
849 return ACCESS_DUNNO;
850 } else {
851 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
852 "' queue overload. Using stale result. '" << key << "'.");
853 entry = staleEntry;
854 /* Fall thru to processing below */
855 }
856 }
857 }
858 }
859
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 << " }");
865 #if USE_AUTH
866 debugs(82, 4, HERE << "entry user=" << entry->user);
867 #endif
868
869 external_acl_cache_touch(acl->def, entry);
870 external_acl_message = entry->message.termedBuf();
871
872 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
873 copyResultsFromEntry(ch->request, entry);
874 return entry->result;
875 }
876
877 int
878 ACLExternal::match(ACLChecklist *checklist)
879 {
880 allow_t answer = aclMatchExternal(data, Filled(checklist));
881
882 // convert to tri-state ACL match 1,0,-1
883 switch (answer) {
884 case ACCESS_ALLOWED:
885 return 1; // match
886
887 case ACCESS_DENIED:
888 return 0; // non-match
889
890 case ACCESS_DUNNO:
891 case ACCESS_AUTH_REQUIRED:
892 default:
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");
897 return -1; // other
898 }
899 }
900
901 wordlist *
902 ACLExternal::dump() const
903 {
904 external_acl_data const *acl = data;
905 wordlist *result = NULL;
906 wordlist *arg;
907 MemBuf mb;
908 mb.init();
909 mb.Printf("%s", acl->def->name);
910
911 for (arg = acl->arguments; arg; arg = arg->next) {
912 mb.Printf(" %s", arg->key);
913 }
914
915 wordlistAdd(&result, mb.buf);
916 mb.clean();
917 return result;
918 }
919
920 /******************************************************************
921 * external_acl cache
922 */
923
924 static void
925 external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
926 {
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))
929 return;
930
931 dlinkDelete(&entry->lru, &def->lru_list);
932 dlinkAdd(entry, &entry->lru, &def->lru_list);
933 }
934
935 static char *
936 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
937 {
938 static MemBuf mb;
939 char buf[256];
940 int first = 1;
941 wordlist *arg;
942 external_acl_format *format;
943 HttpRequest *request = ch->request;
944 HttpReply *reply = ch->reply;
945 mb.reset();
946
947 for (format = acl_data->def->format; format; format = format->next) {
948 const char *str = NULL;
949 String sb;
950
951 switch (format->type) {
952 #if USE_AUTH
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;
958
959 if (ch->auth_user_request != NULL)
960 str = ch->auth_user_request->username();
961 break;
962 #endif
963 #if USE_IDENT
964 case _external_acl_format::EXT_ACL_IDENT:
965 str = ch->rfc931;
966
967 if (!str || !*str) {
968 ch->changeState(IdentLookup::Instance());
969 return NULL;
970 }
971
972 break;
973 #endif
974
975 case _external_acl_format::EXT_ACL_SRC:
976 str = ch->src_addr.NtoA(buf,sizeof(buf));
977 break;
978
979 case _external_acl_format::EXT_ACL_SRCPORT:
980 snprintf(buf, sizeof(buf), "%d", request->client_addr.GetPort());
981 str = buf;
982 break;
983
984 #if USE_SQUID_EUI
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)))
988 str = buf;
989 break;
990
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)))
994 str = buf;
995 break;
996 #endif
997
998 case _external_acl_format::EXT_ACL_MYADDR:
999 str = request->my_addr.NtoA(buf, sizeof(buf));
1000 break;
1001
1002 case _external_acl_format::EXT_ACL_MYPORT:
1003 snprintf(buf, sizeof(buf), "%d", request->my_addr.GetPort());
1004 str = buf;
1005 break;
1006
1007 case _external_acl_format::EXT_ACL_URI:
1008 str = urlCanonical(request);
1009 break;
1010
1011 case _external_acl_format::EXT_ACL_DST:
1012 str = request->GetHost();
1013 break;
1014
1015 case _external_acl_format::EXT_ACL_PROTO:
1016 str = AnyP::ProtocolType_str[request->protocol];
1017 break;
1018
1019 case _external_acl_format::EXT_ACL_PORT:
1020 snprintf(buf, sizeof(buf), "%d", request->port);
1021 str = buf;
1022 break;
1023
1024 case _external_acl_format::EXT_ACL_PATH:
1025 str = request->urlpath.termedBuf();
1026 break;
1027
1028 case _external_acl_format::EXT_ACL_METHOD:
1029 str = RequestMethodStr(request->method);
1030 break;
1031
1032 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
1033 sb = request->header.getByName(format->header);
1034 str = sb.termedBuf();
1035 break;
1036
1037 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
1038 sb = request->header.getStrOrList(format->header_id);
1039 str = sb.termedBuf();
1040 break;
1041
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();
1045 break;
1046
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();
1050 break;
1051
1052 case _external_acl_format::EXT_ACL_HEADER_REPLY:
1053 if (reply) {
1054 sb = reply->header.getByName(format->header);
1055 str = sb.termedBuf();
1056 }
1057 break;
1058
1059 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
1060 if (reply) {
1061 sb = reply->header.getStrOrList(format->header_id);
1062 str = sb.termedBuf();
1063 }
1064 break;
1065
1066 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
1067 if (reply) {
1068 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
1069 str = sb.termedBuf();
1070 }
1071 break;
1072
1073 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
1074 if (reply) {
1075 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
1076 str = sb.termedBuf();
1077 }
1078 break;
1079 #if USE_SSL
1080
1081 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
1082
1083 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1084 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1085
1086 if (ssl)
1087 str = sslGetUserCertificatePEM(ssl);
1088 }
1089
1090 break;
1091
1092 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
1093
1094 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1095 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1096
1097 if (ssl)
1098 str = sslGetUserCertificateChainPEM(ssl);
1099 }
1100
1101 break;
1102
1103 case _external_acl_format::EXT_ACL_USER_CERT:
1104
1105 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1106 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1107
1108 if (ssl)
1109 str = sslGetUserAttribute(ssl, format->header);
1110 }
1111
1112 break;
1113
1114 case _external_acl_format::EXT_ACL_CA_CERT:
1115
1116 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1117 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1118
1119 if (ssl)
1120 str = sslGetCAAttribute(ssl, format->header);
1121 }
1122
1123 break;
1124 #endif
1125 #if USE_AUTH
1126 case _external_acl_format::EXT_ACL_EXT_USER:
1127 str = request->extacl_user.termedBuf();
1128 break;
1129 #endif
1130 case _external_acl_format::EXT_ACL_EXT_LOG:
1131 str = request->extacl_log.termedBuf();
1132 break;
1133 case _external_acl_format::EXT_ACL_TAG:
1134 str = request->tag.termedBuf();
1135 break;
1136 case _external_acl_format::EXT_ACL_PERCENT:
1137 str = "%";
1138 break;
1139 case _external_acl_format::EXT_ACL_UNKNOWN:
1140
1141 case _external_acl_format::EXT_ACL_END:
1142 fatal("unknown external_acl format error");
1143 break;
1144 }
1145
1146 if (str)
1147 if (!*str)
1148 str = NULL;
1149
1150 if (!str)
1151 str = "-";
1152
1153 if (!first)
1154 mb.append(" ", 1);
1155
1156 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1157 const char *quoted = rfc1738_escape(str);
1158 mb.append(quoted, strlen(quoted));
1159 } else {
1160 strwordquote(&mb, str);
1161 }
1162
1163 sb.clean();
1164
1165 first = 0;
1166 }
1167
1168 for (arg = acl_data->arguments; arg; arg = arg->next) {
1169 if (!first)
1170 mb.append(" ", 1);
1171
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));
1175 } else {
1176 strwordquote(&mb, arg->key);
1177 }
1178
1179 first = 0;
1180 }
1181
1182 return mb.buf;
1183 }
1184
1185 static int
1186 external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1187 {
1188 if (def->cache_size <= 0)
1189 return 1;
1190
1191 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
1192 return 1;
1193 else
1194 return 0;
1195 }
1196
1197 static int
1198 external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1199 {
1200 if (def->cache_size <= 0)
1201 return 1;
1202
1203 int ttl;
1204 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1205 ttl = (ttl * (100 - def->grace)) / 100;
1206
1207 if (entry->date + ttl <= squid_curtime)
1208 return 1;
1209 else
1210 return 0;
1211 }
1212
1213 static external_acl_entry *
1214 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
1215 {
1216 ExternalACLEntry *entry;
1217
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)) {
1220 debugs(82,6, HERE);
1221 entry = new ExternalACLEntry;
1222 entry->key = xstrdup(key);
1223 entry->update(data);
1224 entry->def = def;
1225 return entry;
1226 }
1227
1228 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
1229 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
1230
1231 if (entry) {
1232 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1233 entry->update(data);
1234 external_acl_cache_touch(def, entry);
1235
1236 return entry;
1237 }
1238
1239 entry = new ExternalACLEntry;
1240 entry->key = xstrdup(key);
1241 entry->update(data);
1242
1243 def->add(entry);
1244
1245 return entry;
1246 }
1247
1248 static void
1249 external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1250 {
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;
1255 delete entry;
1256 }
1257
1258 /******************************************************************
1259 * external_acl helpers
1260 */
1261
1262 typedef struct _externalAclState externalAclState;
1263
1264 struct _externalAclState {
1265 EAH *callback;
1266 void *callback_data;
1267 char *key;
1268 external_acl *def;
1269 dlink_node list;
1270 externalAclState *queue;
1271 };
1272
1273 CBDATA_TYPE(externalAclState);
1274 static void
1275 free_externalAclState(void *data)
1276 {
1277 externalAclState *state = static_cast<externalAclState *>(data);
1278 safe_free(state->key);
1279 cbdataReferenceDone(state->callback_data);
1280 cbdataReferenceDone(state->def);
1281 }
1282
1283 /*
1284 * The helper program receives queries on stdin, one
1285 * per line, and must return the result on on stdout
1286 *
1287 * General result syntax:
1288 *
1289 * OK/ERR keyword=value ...
1290 *
1291 * Keywords:
1292 *
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
1299 *
1300 * Other keywords may be added to the protocol later
1301 *
1302 * value needs to be enclosed in quotes if it may contain whitespace, or
1303 * the whitespace escaped using \ (\ escaping obviously also applies to
1304 * any " characters)
1305 */
1306
1307 static void
1308 externalAclHandleReply(void *data, char *reply)
1309 {
1310 externalAclState *state = static_cast<externalAclState *>(data);
1311 externalAclState *next;
1312 char *status;
1313 char *token;
1314 char *value;
1315 char *t = NULL;
1316 ExternalACLEntryData entryData;
1317 entryData.result = ACCESS_DENIED;
1318 external_acl_entry *entry = NULL;
1319
1320 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
1321
1322 if (reply) {
1323 status = strwordtok(reply, &t);
1324
1325 if (status && strcmp(status, "OK") == 0)
1326 entryData.result = ACCESS_ALLOWED;
1327
1328 while ((token = strwordtok(NULL, &t))) {
1329 value = strchr(token, '=');
1330
1331 if (value) {
1332 *value = '\0'; /* terminate the token, and move up to the value */
1333 ++value;
1334
1335 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
1336 rfc1738_unescape(value);
1337
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;
1346 #if USE_AUTH
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;
1355 #endif
1356 }
1357 }
1358 }
1359
1360 dlinkDelete(&state->list, &state->def->queue);
1361
1362 if (cbdataReferenceValid(state->def)) {
1363 if (reply)
1364 entry = external_acl_cache_add(state->def, state->key, entryData);
1365 else {
1366 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
1367
1368 if (oldentry)
1369 external_acl_cache_delete(state->def, oldentry);
1370 }
1371 }
1372
1373 do {
1374 void *cbdata;
1375 cbdataReferenceDone(state->def);
1376
1377 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
1378 state->callback(cbdata, entry);
1379
1380 next = state->queue;
1381
1382 cbdataFree(state);
1383
1384 state = next;
1385 } while (state);
1386 }
1387
1388 void
1389 ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
1390 {
1391 ExternalACLLookup::Start(checklist, me->data, false);
1392 }
1393
1394 void
1395 ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
1396 {
1397 external_acl *def = acl->def;
1398
1399 ACLFilledChecklist *ch = Filled(checklist);
1400 const char *key = makeExternalAclKey(ch, acl);
1401 assert(key);
1402
1403 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
1404 def->name << "' for '" << key << "'");
1405
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);
1412
1413 if (strcmp(key, oldstatetmp->key) == 0) {
1414 oldstate = oldstatetmp;
1415 break;
1416 }
1417 }
1418 }
1419
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 << ")");
1424 return;
1425 }
1426
1427 externalAclState *state = cbdataAlloc(externalAclState);
1428 state->def = cbdataReference(def);
1429
1430 state->key = xstrdup(key);
1431
1432 if (!inBackground) {
1433 state->callback = &ExternalACLLookup::LookupDone;
1434 state->callback_data = cbdataReference(checklist);
1435 }
1436
1437 if (oldstate) {
1438 /* Hook into pending lookup */
1439 state->queue = oldstate->queue;
1440 oldstate->queue = state;
1441 } else {
1442 /* No pending lookup found. Sumbit to helper */
1443
1444 /* Check for queue overload */
1445
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
1449 cbdataFree(state);
1450 return;
1451 }
1452
1453 /* Send it off to the helper */
1454 MemBuf buf;
1455 buf.init();
1456
1457 buf.Printf("%s\n", key);
1458
1459 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1460
1461 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
1462
1463 dlinkAdd(state, &state->list, &def->queue);
1464
1465 buf.clean();
1466 }
1467
1468 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1469 "' in '" << def->name << "' (ch=" << ch << ").");
1470 }
1471
1472 static void
1473 externalAclStats(StoreEntry * sentry)
1474 {
1475 external_acl *p;
1476
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");
1482 }
1483 }
1484
1485 static void
1486 externalAclRegisterWithCacheManager(void)
1487 {
1488 Mgr::RegisterAction("external_acl",
1489 "External ACL stats",
1490 externalAclStats, 0, 1);
1491 }
1492
1493 void
1494 externalAclInit(void)
1495 {
1496 static int firstTimeInit = 1;
1497 external_acl *p;
1498
1499 for (p = Config.externalAclHelperList; p; p = p->next) {
1500 if (!p->cache)
1501 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1502
1503 if (!p->theHelper)
1504 p->theHelper = new helper(p->name);
1505
1506 p->theHelper->cmdline = p->cmdline;
1507
1508 p->theHelper->childs.updateLimits(p->children);
1509
1510 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1511
1512 p->theHelper->addr = p->local_addr;
1513
1514 helperOpenServers(p->theHelper);
1515 }
1516
1517 if (firstTimeInit) {
1518 firstTimeInit = 0;
1519 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
1520 }
1521
1522 externalAclRegisterWithCacheManager();
1523 }
1524
1525 void
1526 externalAclShutdown(void)
1527 {
1528 external_acl *p;
1529
1530 for (p = Config.externalAclHelperList; p; p = p->next) {
1531 helperShutdown(p->theHelper);
1532 }
1533 }
1534
1535 ExternalACLLookup ExternalACLLookup::instance_;
1536 ExternalACLLookup *
1537 ExternalACLLookup::Instance()
1538 {
1539 return &instance_;
1540 }
1541
1542 void
1543 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1544 {
1545 /* TODO: optimise this - we probably have a pointer to this
1546 * around somewhere */
1547 ACL *acl = ACL::FindByName(AclMatchedName);
1548 assert(acl);
1549 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1550 assert (me);
1551 checklist->asyncInProgress(true);
1552 ACLExternal::ExternalAclLookup(checklist, me);
1553 }
1554
1555 /// Called when an async lookup returns
1556 void
1557 ExternalACLLookup::LookupDone(void *data, void *result)
1558 {
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();
1564 }
1565
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
1570 * it's name.
1571 * Be sure that clone is fully functional for that acl class though!
1572 */
1573 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1574
1575 ACLExternal ACLExternal::RegistryEntry_("external");
1576
1577 ACL *
1578 ACLExternal::clone() const
1579 {
1580 return new ACLExternal(*this);
1581 }
1582
1583 ACLExternal::ACLExternal (char const *theClass) : data (NULL), class_ (xstrdup (theClass))
1584 {}
1585
1586 ACLExternal::ACLExternal (ACLExternal const & old) : data (NULL), class_ (old.class_ ? xstrdup (old.class_) : NULL)
1587 {
1588 /* we don't have copy constructors for the data yet */
1589 assert (!old.data);
1590 }
1591
1592 char const *
1593 ACLExternal::typeString() const
1594 {
1595 return class_;
1596 }
1597
1598 bool
1599 ACLExternal::isProxyAuth() const
1600 {
1601 #if USE_AUTH
1602 return data->def->require_auth;
1603 #else
1604 return false;
1605 #endif
1606 }