]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
Merged from trunk
[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 "CacheManager.h"
45 #include "ExternalACL.h"
46 #include "ExternalACLEntry.h"
47 #include "AuthUserRequest.h"
48 #include "SquidTime.h"
49 #include "Store.h"
50 #include "fde.h"
51 #include "ACLChecklist.h"
52 #include "ACL.h"
53 #if USE_IDENT
54 #include "ACLIdent.h"
55 #endif
56 #include "client_side.h"
57 #include "HttpRequest.h"
58 #include "HttpReply.h"
59 #include "authenticate.h"
60 #include "helper.h"
61 #include "MemBuf.h"
62 #include "URLScheme.h"
63 #include "wordlist.h"
64
65 #ifndef DEFAULT_EXTERNAL_ACL_TTL
66 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
67 #endif
68 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
69 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
70 #endif
71
72 typedef struct _external_acl_format external_acl_format;
73
74 static char *makeExternalAclKey(ACLChecklist * ch, external_acl_data * acl_data);
75 static void external_acl_cache_delete(external_acl * def, external_acl_entry * entry);
76 static int external_acl_entry_expired(external_acl * def, external_acl_entry * entry);
77 static int external_acl_grace_expired(external_acl * def, external_acl_entry * entry);
78 static void external_acl_cache_touch(external_acl * def, external_acl_entry * entry);
79 static external_acl_entry *external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
80
81 /******************************************************************
82 * external_acl directive
83 */
84
85 class external_acl
86 {
87
88 public:
89 external_acl *next;
90
91 void add
92 (ExternalACLEntry *);
93
94 void trimCache();
95
96 int ttl;
97
98 int negative_ttl;
99
100 int grace;
101
102 char *name;
103
104 external_acl_format *format;
105
106 wordlist *cmdline;
107
108 int children;
109
110 int concurrency;
111
112 helper *theHelper;
113
114 hash_table *cache;
115
116 dlink_list lru_list;
117
118 int cache_size;
119
120 int cache_entries;
121
122 dlink_list queue;
123
124 bool require_auth;
125
126 enum {
127 QUOTE_METHOD_SHELL = 1,
128 QUOTE_METHOD_URL
129 } quote;
130
131 IpAddress local_addr;
132 };
133
134 struct _external_acl_format {
135 enum format_type {
136 EXT_ACL_UNKNOWN,
137 EXT_ACL_LOGIN,
138 #if USE_IDENT
139 EXT_ACL_IDENT,
140 #endif
141 EXT_ACL_SRC,
142 EXT_ACL_SRCPORT,
143 EXT_ACL_MYADDR,
144 EXT_ACL_MYPORT,
145 EXT_ACL_URI,
146 EXT_ACL_DST,
147 EXT_ACL_PROTO,
148 EXT_ACL_PORT,
149 EXT_ACL_PATH,
150 EXT_ACL_METHOD,
151
152 EXT_ACL_HEADER_REQUEST,
153 EXT_ACL_HEADER_REQUEST_MEMBER,
154 EXT_ACL_HEADER_REQUEST_ID,
155 EXT_ACL_HEADER_REQUEST_ID_MEMBER,
156
157 EXT_ACL_HEADER_REPLY,
158 EXT_ACL_HEADER_REPLY_MEMBER,
159 EXT_ACL_HEADER_REPLY_ID,
160 EXT_ACL_HEADER_REPLY_ID_MEMBER,
161
162 #if USE_SSL
163 EXT_ACL_USER_CERT,
164 EXT_ACL_CA_CERT,
165 EXT_ACL_USER_CERT_RAW,
166 EXT_ACL_USER_CERTCHAIN_RAW,
167 #endif
168 EXT_ACL_EXT_USER,
169 EXT_ACL_END
170 } type;
171 external_acl_format *next;
172 char *header;
173 char *member;
174 char separator;
175 http_hdr_type header_id;
176 };
177
178 /* FIXME: These are not really cbdata, but it is an easy way
179 * to get them pooled, refcounted, accounted and freed properly...
180 */
181 CBDATA_TYPE(external_acl);
182 CBDATA_TYPE(external_acl_format);
183
184 static void
185 free_external_acl_format(void *data)
186 {
187 external_acl_format *p = static_cast<external_acl_format *>(data);
188 safe_free(p->header);
189 }
190
191 static void
192 free_external_acl(void *data)
193 {
194 external_acl *p = static_cast<external_acl *>(data);
195 safe_free(p->name);
196
197 while (p->format) {
198 external_acl_format *f = p->format;
199 p->format = f->next;
200 cbdataFree(f);
201 }
202
203 wordlistDestroy(&p->cmdline);
204
205 if (p->theHelper) {
206 helperShutdown(p->theHelper);
207 helperFree(p->theHelper);
208 p->theHelper = NULL;
209 }
210
211 while (p->lru_list.tail)
212 external_acl_cache_delete(p, static_cast<external_acl_entry *>(p->lru_list.tail->data));
213 if (p->cache)
214 hashFreeMemory(p->cache);
215 }
216
217 /**
218 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
219 * request or reply header to external helper.
220 *
221 \param header - the token being parsed (without the identifying prefix)
222 \param type - format enum identifier for this element, pulled from identifying prefix
223 \param format - structure to contain all the info about this format element.
224 */
225 void
226 parse_header_token(external_acl_format *format, char *header, const _external_acl_format::format_type type)
227 {
228 /* header format */
229 char *member, *end;
230
231 /** Cut away the closing brace */
232 end = strchr(header, '}');
233 if (end && strlen(end) == 1)
234 *end = '\0';
235 else
236 self_destruct();
237
238 member = strchr(header, ':');
239
240 if (member) {
241 /* Split in header and member */
242 *member++ = '\0';
243
244 if (!xisalnum(*member))
245 format->separator = *member++;
246 else
247 format->separator = ',';
248
249 format->member = xstrdup(member);
250
251 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
252 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
253 else
254 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER;
255 } else {
256 format->type = type;
257 }
258
259 format->header = xstrdup(header);
260 format->header_id = httpHeaderIdByNameDef(header, strlen(header));
261
262 if (format->header_id != -1) {
263 if (member) {
264 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
265 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER;
266 else
267 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER;
268 } else {
269 if (type == _external_acl_format::EXT_ACL_HEADER_REQUEST)
270 format->type = _external_acl_format::EXT_ACL_HEADER_REQUEST_ID;
271 else
272 format->type = _external_acl_format::EXT_ACL_HEADER_REPLY_ID;
273 }
274 }
275 }
276
277 void
278 parse_externalAclHelper(external_acl ** list)
279 {
280 external_acl *a;
281 char *token;
282 external_acl_format **p;
283
284 CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
285 CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
286
287 a = cbdataAlloc(external_acl);
288
289 /* set defaults */
290 a->ttl = DEFAULT_EXTERNAL_ACL_TTL;
291 a->negative_ttl = -1;
292 a->children = DEFAULT_EXTERNAL_ACL_CHILDREN;
293 a->local_addr.SetLocalhost();
294 a->quote = external_acl::QUOTE_METHOD_URL;
295
296
297 token = strtok(NULL, w_space);
298
299 if (!token)
300 self_destruct();
301
302 a->name = xstrdup(token);
303
304 token = strtok(NULL, w_space);
305
306 /* Parse options */
307 while (token) {
308 if (strncmp(token, "ttl=", 4) == 0) {
309 a->ttl = atoi(token + 4);
310 } else if (strncmp(token, "negative_ttl=", 13) == 0) {
311 a->negative_ttl = atoi(token + 13);
312 } else if (strncmp(token, "children=", 9) == 0) {
313 a->children = atoi(token + 9);
314 } else if (strncmp(token, "concurrency=", 12) == 0) {
315 a->concurrency = atoi(token + 12);
316 } else if (strncmp(token, "cache=", 6) == 0) {
317 a->cache_size = atoi(token + 6);
318 } else if (strncmp(token, "grace=", 6) == 0) {
319 a->grace = atoi(token + 6);
320 } else if (strcmp(token, "protocol=2.5") == 0) {
321 a->quote = external_acl::QUOTE_METHOD_SHELL;
322 } else if (strcmp(token, "protocol=3.0") == 0) {
323 a->quote = external_acl::QUOTE_METHOD_URL;
324 } else if (strcmp(token, "quote=url") == 0) {
325 a->quote = external_acl::QUOTE_METHOD_URL;
326 } else if (strcmp(token, "quote=shell") == 0) {
327 a->quote = external_acl::QUOTE_METHOD_SHELL;
328
329 /* INET6: allow admin to configure some helpers explicitly to
330 bind to IPv4/v6 localhost port. */
331 } else if (strcmp(token, "ipv4") == 0) {
332 if ( !a->local_addr.SetIPv4() ) {
333 debugs(3, 0, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
334 }
335 } else if (strcmp(token, "ipv6") == 0) {
336 #if !USE_IPV6
337 debugs(3, 0, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
338 #else
339 (void)0;
340 #endif
341 } else {
342 break;
343 }
344
345 token = strtok(NULL, w_space);
346 }
347
348 if (a->negative_ttl == -1)
349 a->negative_ttl = a->ttl;
350
351 /* Parse format */
352 p = &a->format;
353
354 while (token) {
355 external_acl_format *format;
356
357 /* stop on first non-format token found */
358
359 if (*token != '%')
360 break;
361
362 format = cbdataAlloc(external_acl_format);
363
364 if (strncmp(token, "%{", 2) == 0) {
365 // deprecated. but assume the old configs all referred to request headers.
366 debugs(82, DBG_IMPORTANT, "WARNING: external_acl_type format %{...} is being replaced by %>{...} for : " << token);
367 parse_header_token(format, (token+2), _external_acl_format::EXT_ACL_HEADER_REQUEST);
368 }
369
370 if (strncmp(token, "%>{", 3) == 0) {
371 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REQUEST);
372 }
373 if (strncmp(token, "%<{", 3) == 0) {
374 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REPLY);
375
376 } else if (strcmp(token, "%LOGIN") == 0) {
377 format->type = _external_acl_format::EXT_ACL_LOGIN;
378 a->require_auth = true;
379 }
380
381 #if USE_IDENT
382 else if (strcmp(token, "%IDENT") == 0)
383 format->type = _external_acl_format::EXT_ACL_IDENT;
384
385 #endif
386
387 else if (strcmp(token, "%SRC") == 0)
388 format->type = _external_acl_format::EXT_ACL_SRC;
389 else if (strcmp(token, "%SRCPORT") == 0)
390 format->type = _external_acl_format::EXT_ACL_SRCPORT;
391 else if (strcmp(token, "%MYADDR") == 0)
392 format->type = _external_acl_format::EXT_ACL_MYADDR;
393 else if (strcmp(token, "%MYPORT") == 0)
394 format->type = _external_acl_format::EXT_ACL_MYPORT;
395 else if (strcmp(token, "%URI") == 0)
396 format->type = _external_acl_format::EXT_ACL_URI;
397 else if (strcmp(token, "%DST") == 0)
398 format->type = _external_acl_format::EXT_ACL_DST;
399 else if (strcmp(token, "%PROTO") == 0)
400 format->type = _external_acl_format::EXT_ACL_PROTO;
401 else if (strcmp(token, "%PORT") == 0)
402 format->type = _external_acl_format::EXT_ACL_PORT;
403 else if (strcmp(token, "%PATH") == 0)
404 format->type = _external_acl_format::EXT_ACL_PATH;
405 else if (strcmp(token, "%METHOD") == 0)
406 format->type = _external_acl_format::EXT_ACL_METHOD;
407
408 #if USE_SSL
409
410 else if (strcmp(token, "%USER_CERT") == 0)
411 format->type = _external_acl_format::EXT_ACL_USER_CERT_RAW;
412 else if (strcmp(token, "%USER_CERTCHAIN") == 0)
413 format->type = _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW;
414 else if (strncmp(token, "%USER_CERT_", 11)) {
415 format->type = _external_acl_format::EXT_ACL_USER_CERT;
416 format->header = xstrdup(token + 11);
417 } else if (strncmp(token, "%CA_CERT_", 11)) {
418 format->type = _external_acl_format::EXT_ACL_USER_CERT;
419 format->header = xstrdup(token + 11);
420 }
421
422 #endif
423 else if (strcmp(token, "%EXT_USER") == 0)
424 format->type = _external_acl_format::EXT_ACL_EXT_USER;
425 else {
426 self_destruct();
427 }
428
429 *p = format;
430 p = &format->next;
431 token = strtok(NULL, w_space);
432 }
433
434 /* There must be at least one format token */
435 if (!a->format)
436 self_destruct();
437
438 /* helper */
439 if (!token)
440 self_destruct();
441
442 wordlistAdd(&a->cmdline, token);
443
444 /* arguments */
445 parse_wordlist(&a->cmdline);
446
447 while (*list)
448 list = &(*list)->next;
449
450 *list = a;
451 }
452
453 void
454 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
455 {
456 const external_acl *node;
457 const external_acl_format *format;
458 const wordlist *word;
459
460 for (node = list; node; node = node->next) {
461 storeAppendPrintf(sentry, "%s %s", name, node->name);
462
463 if (!node->local_addr.IsIPv6())
464 storeAppendPrintf(sentry, " ipv4");
465 else
466 storeAppendPrintf(sentry, " ipv6");
467
468 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
469 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
470
471 if (node->negative_ttl != node->ttl)
472 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
473
474 if (node->grace)
475 storeAppendPrintf(sentry, " grace=%d", node->grace);
476
477 if (node->children != DEFAULT_EXTERNAL_ACL_CHILDREN)
478 storeAppendPrintf(sentry, " children=%d", node->children);
479
480 if (node->concurrency)
481 storeAppendPrintf(sentry, " concurrency=%d", node->concurrency);
482
483 if (node->cache)
484 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
485
486 for (format = node->format; format; format = format->next) {
487 switch (format->type) {
488
489 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
490 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
491 storeAppendPrintf(sentry, " %%>{%s}", format->header);
492 break;
493
494 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
495 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
496 storeAppendPrintf(sentry, " %%>{%s:%s}", format->header, format->member);
497 break;
498
499 case _external_acl_format::EXT_ACL_HEADER_REPLY:
500 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
501 storeAppendPrintf(sentry, " %%<{%s}", format->header);
502 break;
503
504 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
505 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
506 storeAppendPrintf(sentry, " %%<{%s:%s}", format->header, format->member);
507 break;
508 #define DUMP_EXT_ACL_TYPE(a) \
509 case _external_acl_format::EXT_ACL_##a: \
510 storeAppendPrintf(sentry, " %%%s", #a); \
511 break
512
513 DUMP_EXT_ACL_TYPE(LOGIN);
514 #if USE_IDENT
515
516 DUMP_EXT_ACL_TYPE(IDENT);
517 #endif
518
519 DUMP_EXT_ACL_TYPE(SRC);
520 DUMP_EXT_ACL_TYPE(SRCPORT);
521 DUMP_EXT_ACL_TYPE(MYADDR);
522 DUMP_EXT_ACL_TYPE(MYPORT);
523 DUMP_EXT_ACL_TYPE(URI);
524 DUMP_EXT_ACL_TYPE(DST);
525 DUMP_EXT_ACL_TYPE(PROTO);
526 DUMP_EXT_ACL_TYPE(PORT);
527 DUMP_EXT_ACL_TYPE(PATH);
528 DUMP_EXT_ACL_TYPE(METHOD);
529 #if USE_SSL
530
531 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
532 storeAppendPrintf(sentry, " %%USER_CERT");
533 break;
534
535 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
536 storeAppendPrintf(sentry, " %%USER_CERTCHAIN");
537 break;
538
539 case _external_acl_format::EXT_ACL_USER_CERT:
540 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
541 break;
542
543 case _external_acl_format::EXT_ACL_CA_CERT:
544 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
545 break;
546 #endif
547
548 DUMP_EXT_ACL_TYPE(EXT_USER);
549
550 default:
551 fatal("unknown external_acl format error");
552 break;
553 }
554 }
555
556 for (word = node->cmdline; word; word = word->next)
557 storeAppendPrintf(sentry, " %s", word->key);
558
559 storeAppendPrintf(sentry, "\n");
560 }
561 }
562
563 void
564 free_externalAclHelper(external_acl ** list)
565 {
566 while (*list) {
567 external_acl *node = *list;
568 *list = node->next;
569 node->next = NULL;
570 cbdataFree(node);
571 }
572 }
573
574 static external_acl *
575 find_externalAclHelper(const char *name)
576 {
577 external_acl *node;
578
579 for (node = Config.externalAclHelperList; node; node = node->next) {
580 if (strcmp(node->name, name) == 0)
581 return node;
582 }
583
584 return NULL;
585 }
586
587 void
588
589 external_acl::add
590 (ExternalACLEntry *anEntry)
591 {
592 trimCache();
593 assert (anEntry->def == NULL);
594 anEntry->def = this;
595 hash_join(cache, anEntry);
596 dlinkAdd(anEntry, &anEntry->lru, &lru_list);
597 cache_entries++;
598 }
599
600 void
601 external_acl::trimCache()
602 {
603 if (cache_size && cache_entries >= cache_size)
604 external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
605 }
606
607
608 /******************************************************************
609 * external acl type
610 */
611
612 struct _external_acl_data {
613 external_acl *def;
614 wordlist *arguments;
615 };
616
617 CBDATA_TYPE(external_acl_data);
618 static void
619 free_external_acl_data(void *data)
620 {
621 external_acl_data *p = static_cast<external_acl_data *>(data);
622 wordlistDestroy(&p->arguments);
623 cbdataReferenceDone(p->def);
624 }
625
626 void
627 ACLExternal::parse()
628 {
629 char *token;
630
631 if (data)
632 self_destruct();
633
634 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
635
636 data = cbdataAlloc(external_acl_data);
637
638 token = strtok(NULL, w_space);
639
640 if (!token)
641 self_destruct();
642
643 data->def = cbdataReference(find_externalAclHelper(token));
644
645 if (!data->def)
646 self_destruct();
647
648 while ((token = strtokFile())) {
649 wordlistAdd(&data->arguments, token);
650 }
651 }
652
653 bool
654 ACLExternal::valid () const
655 {
656 if (data->def->require_auth) {
657 if (authenticateSchemeCount() == 0) {
658 debugs(28, 0, "Can't use proxy auth because no authentication schemes were compiled.");
659 return false;
660 }
661
662 if (authenticateActiveSchemeCount() == 0) {
663 debugs(28, 0, "Can't use proxy auth because no authentication schemes are fully configured.");
664 return false;
665 }
666 }
667
668 return true;
669 }
670
671 bool
672 ACLExternal::empty () const
673 {
674 return false;
675 }
676
677 ACLExternal::~ACLExternal()
678 {
679 cbdataFree(data);
680 safe_free (class_);
681 }
682
683 static int
684 aclMatchExternal(external_acl_data *acl, ACLChecklist * ch);
685 static int
686 aclMatchExternal(external_acl_data *acl, ACLChecklist * ch)
687 {
688 int result;
689 external_acl_entry *entry;
690 const char *key = "";
691 debugs(82, 9, "aclMatchExternal: acl=\"" << acl->def->name << "\"");
692 entry = ch->extacl_entry;
693
694 if (entry) {
695 if (cbdataReferenceValid(entry) && entry->def == acl->def &&
696 strcmp((char *)entry->key, key) == 0) {
697 /* Ours, use it.. */
698 } else {
699 /* Not valid, or not ours.. get rid of it */
700 cbdataReferenceDone(ch->extacl_entry);
701 entry = NULL;
702 }
703 }
704
705 external_acl_message = "MISSING REQUIRED INFORMATION";
706
707 if (!entry) {
708 if (acl->def->require_auth) {
709 int ti;
710 /* Make sure the user is authenticated */
711
712 if ((ti = ch->authenticated()) != 1) {
713 debugs(82, 2, "aclMatchExternal: " << acl->def->name << " user not authenticated (" << ti << ")");
714 return ti;
715 }
716 }
717
718 key = makeExternalAclKey(ch, acl);
719
720 if (acl->def->require_auth)
721 AUTHUSERREQUESTUNLOCK(ch->auth_user_request, "ACLChecklist via aclMatchExternal");
722
723 if (!key) {
724 /* Not sufficient data to process */
725 return -1;
726 }
727
728 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
729
730 if (!entry || external_acl_grace_expired(acl->def, entry)) {
731 debugs(82, 2, "aclMatchExternal: " << acl->def->name << "(\"" << key << "\") = lookup needed");
732 debugs(82, 2, "aclMatchExternal: \"" << key << "\": entry=@" <<
733 entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
734
735 if (acl->def->theHelper->stats.queue_size <= acl->def->theHelper->n_running) {
736 debugs(82, 2, "aclMatchExternal: \"" << key << "\": queueing a call.");
737 ch->changeState (ExternalACLLookup::Instance());
738
739 if (entry == NULL) {
740 debugs(82, 2, "aclMatchExternal: \"" << key << "\": return -1.");
741 return -1;
742 }
743 } else {
744 if (!entry) {
745 debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
746 "' queue overload. Request rejected '" << key << "'.");
747 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
748 return -1;
749 } else {
750 debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
751 "' queue overload. Using stale result. '" << key << "'.");
752 /* Fall thru to processing below */
753 }
754 }
755 }
756 }
757
758 external_acl_cache_touch(acl->def, entry);
759 result = entry->result;
760 external_acl_message = entry->message.unsafeBuf();
761
762 debugs(82, 2, "aclMatchExternal: " << acl->def->name << " = " << result);
763
764 if (ch->request) {
765 if (entry->user.size())
766 ch->request->extacl_user = entry->user;
767
768 if (entry->password.size())
769 ch->request->extacl_passwd = entry->password;
770
771 if (!ch->request->tag.size())
772 ch->request->tag = entry->tag;
773
774 if (entry->log.size())
775 ch->request->extacl_log = entry->log;
776 }
777
778 return result;
779 }
780
781 int
782 ACLExternal::match(ACLChecklist *checklist)
783 {
784 return aclMatchExternal (data, checklist);
785 }
786
787 wordlist *
788 ACLExternal::dump() const
789 {
790 external_acl_data const *acl = data;
791 wordlist *result = NULL;
792 wordlist *arg;
793 MemBuf mb;
794 mb.init();
795 mb.Printf("%s", acl->def->name);
796
797 for (arg = acl->arguments; arg; arg = arg->next) {
798 mb.Printf(" %s", arg->key);
799 }
800
801 wordlistAdd(&result, mb.buf);
802 mb.clean();
803 return result;
804 }
805
806 /******************************************************************
807 * external_acl cache
808 */
809
810 static void
811 external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
812 {
813 dlinkDelete(&entry->lru, &def->lru_list);
814 dlinkAdd(entry, &entry->lru, &def->lru_list);
815 }
816
817 static char *
818 makeExternalAclKey(ACLChecklist * ch, external_acl_data * acl_data)
819 {
820 static MemBuf mb;
821 char buf[256];
822 int first = 1;
823 wordlist *arg;
824 external_acl_format *format;
825 HttpRequest *request = ch->request;
826 HttpReply *reply = ch->reply;
827 mb.reset();
828
829 for (format = acl_data->def->format; format; format = format->next) {
830 const char *str = NULL;
831 String sb;
832
833 switch (format->type) {
834
835 case _external_acl_format::EXT_ACL_LOGIN:
836 assert (ch->auth_user_request);
837 str = ch->auth_user_request->username();
838 break;
839 #if USE_IDENT
840
841 case _external_acl_format::EXT_ACL_IDENT:
842 str = ch->rfc931;
843
844 if (!str || !*str) {
845 ch->changeState(IdentLookup::Instance());
846 return NULL;
847 }
848
849 break;
850 #endif
851
852 case _external_acl_format::EXT_ACL_SRC:
853 str = ch->src_addr.NtoA(buf,sizeof(buf));
854 break;
855
856 case _external_acl_format::EXT_ACL_SRCPORT:
857 snprintf(buf, sizeof(buf), "%d", request->client_addr.GetPort());
858 str = buf;
859 break;
860
861 case _external_acl_format::EXT_ACL_MYADDR:
862 str = request->my_addr.NtoA(buf, sizeof(buf));
863 break;
864
865 case _external_acl_format::EXT_ACL_MYPORT:
866 snprintf(buf, sizeof(buf), "%d", request->my_addr.GetPort());
867 str = buf;
868 break;
869
870 case _external_acl_format::EXT_ACL_URI:
871 str = urlCanonical(request);
872 break;
873
874 case _external_acl_format::EXT_ACL_DST:
875 str = request->GetHost();
876 break;
877
878 case _external_acl_format::EXT_ACL_PROTO:
879 str = ProtocolStr[request->protocol];
880 break;
881
882 case _external_acl_format::EXT_ACL_PORT:
883 snprintf(buf, sizeof(buf), "%d", request->port);
884 str = buf;
885 break;
886
887 case _external_acl_format::EXT_ACL_PATH:
888 str = request->urlpath.unsafeBuf();
889 break;
890
891 case _external_acl_format::EXT_ACL_METHOD:
892 str = RequestMethodStr(request->method);
893 break;
894
895 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
896 sb = request->header.getByName(format->header);
897 str = sb.unsafeBuf();
898 break;
899
900 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
901 sb = request->header.getStrOrList(format->header_id);
902 str = sb.unsafeBuf();
903 break;
904
905 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
906 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
907 str = sb.unsafeBuf();
908 break;
909
910 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
911 sb = request->header.getListMember(format->header_id, format->member, format->separator);
912 str = sb.unsafeBuf();
913 break;
914
915 case _external_acl_format::EXT_ACL_HEADER_REPLY:
916 if (reply) {
917 sb = reply->header.getByName(format->header);
918 str = sb.unsafeBuf();
919 }
920 break;
921
922 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
923 if (reply) {
924 sb = reply->header.getStrOrList(format->header_id);
925 str = sb.unsafeBuf();
926 }
927 break;
928
929 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
930 if (reply) {
931 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
932 str = sb.unsafeBuf();
933 }
934 break;
935
936 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
937 if (reply) {
938 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
939 str = sb.unsafeBuf();
940 }
941 break;
942 #if USE_SSL
943
944 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
945
946 if (ch->conn() != NULL) {
947 SSL *ssl = fd_table[ch->conn()->fd].ssl;
948
949 if (ssl)
950 str = sslGetUserCertificatePEM(ssl);
951 }
952
953 break;
954
955 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
956
957 if (ch->conn() != NULL) {
958 SSL *ssl = fd_table[ch->conn()->fd].ssl;
959
960 if (ssl)
961 str = sslGetUserCertificateChainPEM(ssl);
962 }
963
964 break;
965
966 case _external_acl_format::EXT_ACL_USER_CERT:
967
968 if (ch->conn() != NULL) {
969 SSL *ssl = fd_table[ch->conn()->fd].ssl;
970
971 if (ssl)
972 str = sslGetUserAttribute(ssl, format->header);
973 }
974
975 break;
976
977 case _external_acl_format::EXT_ACL_CA_CERT:
978
979 if (ch->conn() != NULL) {
980 SSL *ssl = fd_table[ch->conn()->fd].ssl;
981
982 if (ssl)
983 str = sslGetCAAttribute(ssl, format->header);
984 }
985
986 break;
987 #endif
988
989 case _external_acl_format::EXT_ACL_EXT_USER:
990 str = request->extacl_user.unsafeBuf();
991 break;
992
993 case _external_acl_format::EXT_ACL_UNKNOWN:
994
995 case _external_acl_format::EXT_ACL_END:
996 fatal("unknown external_acl format error");
997 break;
998 }
999
1000 if (str)
1001 if (!*str)
1002 str = NULL;
1003
1004 if (!str)
1005 str = "-";
1006
1007 if (!first)
1008 mb.append(" ", 1);
1009
1010 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1011 const char *quoted = rfc1738_escape(str);
1012 mb.append(quoted, strlen(quoted));
1013 } else {
1014 strwordquote(&mb, str);
1015 }
1016
1017 sb.clean();
1018
1019 first = 0;
1020 }
1021
1022 for (arg = acl_data->arguments; arg; arg = arg->next) {
1023 if (!first)
1024 mb.append(" ", 1);
1025
1026 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1027 const char *quoted = rfc1738_escape(arg->key);
1028 mb.append(quoted, strlen(quoted));
1029 } else {
1030 strwordquote(&mb, arg->key);
1031 }
1032
1033 first = 0;
1034 }
1035
1036 return mb.buf;
1037 }
1038
1039 static int
1040 external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1041 {
1042 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
1043 return 1;
1044 else
1045 return 0;
1046 }
1047
1048 static int
1049 external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1050 {
1051 int ttl;
1052 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1053 ttl = (ttl * (100 - def->grace)) / 100;
1054
1055 if (entry->date + ttl < squid_curtime)
1056 return 1;
1057 else
1058 return 0;
1059 }
1060
1061 static external_acl_entry *
1062 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
1063 {
1064 ExternalACLEntry *entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
1065 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
1066
1067 if (entry) {
1068 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1069 entry->update (data);
1070 external_acl_cache_touch(def, entry);
1071
1072 return entry;
1073 }
1074
1075 entry = new ExternalACLEntry;
1076 entry->key = xstrdup(key);
1077 entry->update (data);
1078
1079 def->add
1080 (entry);
1081
1082 return entry;
1083 }
1084
1085 static void
1086 external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1087 {
1088 assert (entry->def == def);
1089 hash_remove_link(def->cache, entry);
1090 dlinkDelete(&entry->lru, &def->lru_list);
1091 def->cache_entries -= 1;
1092 delete entry;
1093 }
1094
1095 /******************************************************************
1096 * external_acl helpers
1097 */
1098
1099 typedef struct _externalAclState externalAclState;
1100
1101 struct _externalAclState {
1102 EAH *callback;
1103 void *callback_data;
1104 char *key;
1105 external_acl *def;
1106 dlink_node list;
1107 externalAclState *queue;
1108 };
1109
1110 CBDATA_TYPE(externalAclState);
1111 static void
1112 free_externalAclState(void *data)
1113 {
1114 externalAclState *state = static_cast<externalAclState *>(data);
1115 safe_free(state->key);
1116 cbdataReferenceDone(state->callback_data);
1117 cbdataReferenceDone(state->def);
1118 }
1119
1120 /*
1121 * The helper program receives queries on stdin, one
1122 * per line, and must return the result on on stdout
1123 *
1124 * General result syntax:
1125 *
1126 * OK/ERR keyword=value ...
1127 *
1128 * Keywords:
1129 *
1130 * user= The users name (login)
1131 * message= Message describing the reason
1132 * tag= A string tag to be applied to the request that triggered the acl match.
1133 * applies to both OK and ERR responses.
1134 * Won't override existing request tags.
1135 * log= A string to be used in access logging
1136 *
1137 * Other keywords may be added to the protocol later
1138 *
1139 * value needs to be enclosed in quotes if it may contain whitespace, or
1140 * the whitespace escaped using \ (\ escaping obviously also applies to
1141 * any " characters)
1142 */
1143
1144 static void
1145 externalAclHandleReply(void *data, char *reply)
1146 {
1147 externalAclState *state = static_cast<externalAclState *>(data);
1148 externalAclState *next;
1149 char *status;
1150 char *token;
1151 char *value;
1152 char *t;
1153 ExternalACLEntryData entryData;
1154 entryData.result = 0;
1155 external_acl_entry *entry = NULL;
1156
1157 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
1158
1159 if (reply) {
1160 status = strwordtok(reply, &t);
1161
1162 if (status && strcmp(status, "OK") == 0)
1163 entryData.result = 1;
1164
1165 while ((token = strwordtok(NULL, &t))) {
1166 value = strchr(token, '=');
1167
1168 if (value) {
1169 *value++ = '\0'; /* terminate the token, and move up to the value */
1170
1171 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
1172 rfc1738_unescape(value);
1173
1174 if (strcmp(token, "user") == 0)
1175 entryData.user = value;
1176 else if (strcmp(token, "message") == 0)
1177 entryData.message = value;
1178 else if (strcmp(token, "error") == 0)
1179 entryData.message = value;
1180 else if (strcmp(token, "tag") == 0)
1181 entryData.tag = value;
1182 else if (strcmp(token, "log") == 0)
1183 entryData.log = value;
1184 else if (strcmp(token, "password") == 0)
1185 entryData.password = value;
1186 else if (strcmp(token, "passwd") == 0)
1187 entryData.password = value;
1188 else if (strcmp(token, "login") == 0)
1189 entryData.user = value;
1190 }
1191 }
1192 }
1193
1194 dlinkDelete(&state->list, &state->def->queue);
1195
1196 if (cbdataReferenceValid(state->def)) {
1197 if (reply)
1198 entry = external_acl_cache_add(state->def, state->key, entryData);
1199 else {
1200 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
1201
1202 if (oldentry)
1203 external_acl_cache_delete(state->def, oldentry);
1204 }
1205 }
1206
1207 do {
1208 void *cbdata;
1209 cbdataReferenceDone(state->def);
1210
1211 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
1212 state->callback(cbdata, entry);
1213
1214 next = state->queue;
1215
1216 cbdataFree(state);
1217
1218 state = next;
1219 } while (state);
1220 }
1221
1222 void
1223 ACLExternal::ExternalAclLookup(ACLChecklist * ch, ACLExternal * me, EAH * callback, void *callback_data)
1224 {
1225 MemBuf buf;
1226 external_acl_data *acl = me->data;
1227 external_acl *def = acl->def;
1228 externalAclState *state;
1229 dlink_node *node;
1230 externalAclState *oldstate = NULL;
1231 bool graceful = 0;
1232
1233 if (acl->def->require_auth) {
1234 int ti;
1235 /* Make sure the user is authenticated */
1236
1237 if ((ti = ch->authenticated()) != 1) {
1238 debugs(82, 1, "externalAclLookup: " << acl->def->name <<
1239 " user authentication failure (" << ti << ", ch=" << ch << ")");
1240 callback(callback_data, NULL);
1241 return;
1242 }
1243 }
1244
1245 const char *key = makeExternalAclKey(ch, acl);
1246
1247 if (!key) {
1248 debugs(82, 1, "externalAclLookup: lookup in '" << def->name <<
1249 "', prerequisit failure (ch=" << ch << ")");
1250 callback(callback_data, NULL);
1251 return;
1252 }
1253
1254 debugs(82, 2, "externalAclLookup: lookup in '" << def->name << "' for '" << key << "'");
1255
1256 external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key));
1257
1258 if (entry && external_acl_entry_expired(def, entry))
1259 entry = NULL;
1260
1261 /* Check for a pending lookup to hook into */
1262 for (node = def->queue.head; node; node = node->next) {
1263 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1264
1265 if (strcmp(key, oldstatetmp->key) == 0) {
1266 oldstate = oldstatetmp;
1267 break;
1268 }
1269 }
1270
1271 if (entry && external_acl_grace_expired(def, entry)) {
1272 if (oldstate) {
1273 debugs(82, 4, "externalAclLookup: in grace period, but already pending lookup ('" << key << "', ch=" << ch << ")");
1274 callback(callback_data, entry);
1275 return;
1276 } else {
1277 graceful = 1; // grace expired, (neg)ttl did not, and we must start a new lookup.
1278 }
1279 }
1280
1281 // The entry is in the cache, grace_ttl did not expired.
1282 if (!graceful && entry && !external_acl_grace_expired(def, entry)) {
1283 /* Should not really happen, but why not.. */
1284 callback(callback_data, entry);
1285 debugs(82, 4, "externalAclLookup: no lookup pending for '" << key << "', and grace not expired");
1286 debugs(82, 4, "externalAclLookup: (what tha' hell?)");
1287 return;
1288 }
1289
1290 /* No pending lookup found. Sumbit to helper */
1291 state = cbdataAlloc(externalAclState);
1292
1293 state->def = cbdataReference(def);
1294
1295 state->key = xstrdup(key);
1296
1297 if (!graceful) {
1298 state->callback = callback;
1299 state->callback_data = cbdataReference(callback_data);
1300 }
1301
1302 if (oldstate) {
1303 /* Hook into pending lookup */
1304 state->queue = oldstate->queue;
1305 oldstate->queue = state;
1306 } else {
1307 /* Check for queue overload */
1308
1309 if (def->theHelper->stats.queue_size >= def->theHelper->n_running) {
1310 debugs(82, 1, "externalAclLookup: '" << def->name << "' queue overload (ch=" << ch << ")");
1311 cbdataFree(state);
1312 callback(callback_data, entry);
1313 return;
1314 }
1315
1316 /* Send it off to the helper */
1317 buf.init();
1318
1319 buf.Printf("%s\n", key);
1320
1321 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1322
1323 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
1324
1325 dlinkAdd(state, &state->list, &def->queue);
1326
1327 buf.clean();
1328 }
1329
1330 if (graceful) {
1331 /* No need to wait during grace period */
1332 debugs(82, 4, "externalAclLookup: no need to wait for the result of '" <<
1333 key << "' in '" << def->name << "' (ch=" << ch << ").");
1334 debugs(82, 4, "externalAclLookup: using cached entry " << entry);
1335
1336 if (entry != NULL) {
1337 debugs(82, 4, "externalAclLookup: entry = { date=" <<
1338 (long unsigned int) entry->date << ", result=" <<
1339 entry->result << ", user=" << entry->user.unsafeBuf() << " tag=" <<
1340 entry->tag.unsafeBuf() << " log=" << entry->log.unsafeBuf() << " }");
1341
1342 }
1343
1344 callback(callback_data, entry);
1345 return;
1346 }
1347
1348 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1349 "' in '" << def->name << "' (ch=" << ch << ").");
1350 }
1351
1352 static void
1353 externalAclStats(StoreEntry * sentry)
1354 {
1355 external_acl *p;
1356
1357 for (p = Config.externalAclHelperList; p; p = p->next) {
1358 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1359 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1360 helperStats(sentry, p->theHelper);
1361 storeAppendPrintf(sentry, "\n");
1362 }
1363 }
1364
1365 static void
1366 externalAclRegisterWithCacheManager(void)
1367 {
1368 CacheManager::GetInstance()->
1369 registerAction("external_acl",
1370 "External ACL stats",
1371 externalAclStats, 0, 1);
1372 }
1373
1374 void
1375 externalAclInit(void)
1376 {
1377 static int firstTimeInit = 1;
1378 external_acl *p;
1379
1380 for (p = Config.externalAclHelperList; p; p = p->next) {
1381 if (!p->cache)
1382 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1383
1384 if (!p->theHelper)
1385 p->theHelper = helperCreate(p->name);
1386
1387 p->theHelper->cmdline = p->cmdline;
1388
1389 p->theHelper->n_to_start = p->children;
1390
1391 p->theHelper->concurrency = p->concurrency;
1392
1393 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1394
1395 p->theHelper->addr = p->local_addr;
1396
1397 helperOpenServers(p->theHelper);
1398 }
1399
1400 if (firstTimeInit) {
1401 firstTimeInit = 0;
1402 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
1403 }
1404
1405 externalAclRegisterWithCacheManager();
1406 }
1407
1408 void
1409 externalAclShutdown(void)
1410 {
1411 external_acl *p;
1412
1413 for (p = Config.externalAclHelperList; p; p = p->next) {
1414 helperShutdown(p->theHelper);
1415 }
1416 }
1417
1418 ExternalACLLookup ExternalACLLookup::instance_;
1419 ExternalACLLookup *
1420 ExternalACLLookup::Instance()
1421 {
1422 return &instance_;
1423 }
1424
1425 void
1426 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1427 {
1428 /* TODO: optimise this - we probably have a pointer to this
1429 * around somewhere */
1430 ACL *acl = ACL::FindByName(AclMatchedName);
1431 assert(acl);
1432 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1433 assert (me);
1434 checklist->asyncInProgress(true);
1435 ACLExternal::ExternalAclLookup(checklist, me, LookupDone, checklist);
1436 }
1437
1438 void
1439 ExternalACLLookup::LookupDone(void *data, void *result)
1440 {
1441 ACLChecklist *checklist = (ACLChecklist *)data;
1442 checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
1443 checklist->asyncInProgress(false);
1444 checklist->changeState (ACLChecklist::NullState::Instance());
1445 checklist->check();
1446 }
1447
1448 /* This registers "external" in the registry. To do dynamic definitions
1449 * of external ACL's, rather than a static prototype, have a Prototype instance
1450 * prototype in the class that defines each external acl 'class'.
1451 * Then, then the external acl instance is created, it self registers under
1452 * it's name.
1453 * Be sure that clone is fully functional for that acl class though!
1454 */
1455 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1456
1457 ACLExternal ACLExternal::RegistryEntry_("external");
1458
1459 ACL *
1460 ACLExternal::clone() const
1461 {
1462 return new ACLExternal(*this);
1463 }
1464
1465 ACLExternal::ACLExternal (char const *theClass) : data (NULL), class_ (xstrdup (theClass))
1466 {}
1467
1468 ACLExternal::ACLExternal (ACLExternal const & old) : data (NULL), class_ (old.class_ ? xstrdup (old.class_) : NULL)
1469 {
1470 /* we don't have copy constructors for the data yet */
1471 assert (!old.data);
1472 }
1473
1474 char const *
1475 ACLExternal::typeString() const
1476 {
1477 return class_;
1478 }
1479
1480 bool
1481 ACLExternal::isProxyAuth() const
1482 {
1483 return data->def->require_auth;
1484 }