]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
Language: Greek
[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 } else if (strncmp(token, "%>{", 3) == 0) {
369 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REQUEST);
370 } else if (strncmp(token, "%<{", 3) == 0) {
371 parse_header_token(format, (token+3), _external_acl_format::EXT_ACL_HEADER_REPLY);
372 } else if (strcmp(token, "%LOGIN") == 0) {
373 format->type = _external_acl_format::EXT_ACL_LOGIN;
374 a->require_auth = true;
375 }
376
377 #if USE_IDENT
378 else if (strcmp(token, "%IDENT") == 0)
379 format->type = _external_acl_format::EXT_ACL_IDENT;
380
381 #endif
382
383 else if (strcmp(token, "%SRC") == 0)
384 format->type = _external_acl_format::EXT_ACL_SRC;
385 else if (strcmp(token, "%SRCPORT") == 0)
386 format->type = _external_acl_format::EXT_ACL_SRCPORT;
387 else if (strcmp(token, "%MYADDR") == 0)
388 format->type = _external_acl_format::EXT_ACL_MYADDR;
389 else if (strcmp(token, "%MYPORT") == 0)
390 format->type = _external_acl_format::EXT_ACL_MYPORT;
391 else if (strcmp(token, "%URI") == 0)
392 format->type = _external_acl_format::EXT_ACL_URI;
393 else if (strcmp(token, "%DST") == 0)
394 format->type = _external_acl_format::EXT_ACL_DST;
395 else if (strcmp(token, "%PROTO") == 0)
396 format->type = _external_acl_format::EXT_ACL_PROTO;
397 else if (strcmp(token, "%PORT") == 0)
398 format->type = _external_acl_format::EXT_ACL_PORT;
399 else if (strcmp(token, "%PATH") == 0)
400 format->type = _external_acl_format::EXT_ACL_PATH;
401 else if (strcmp(token, "%METHOD") == 0)
402 format->type = _external_acl_format::EXT_ACL_METHOD;
403
404 #if USE_SSL
405
406 else if (strcmp(token, "%USER_CERT") == 0)
407 format->type = _external_acl_format::EXT_ACL_USER_CERT_RAW;
408 else if (strcmp(token, "%USER_CERTCHAIN") == 0)
409 format->type = _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW;
410 else if (strncmp(token, "%USER_CERT_", 11) == 0) {
411 format->type = _external_acl_format::EXT_ACL_USER_CERT;
412 format->header = xstrdup(token + 11);
413 } else if (strncmp(token, "%CA_CERT_", 11) == 0) {
414 format->type = _external_acl_format::EXT_ACL_USER_CERT;
415 format->header = xstrdup(token + 11);
416 }
417
418 #endif
419 else if (strcmp(token, "%EXT_USER") == 0)
420 format->type = _external_acl_format::EXT_ACL_EXT_USER;
421 else {
422 self_destruct();
423 }
424
425 *p = format;
426 p = &format->next;
427 token = strtok(NULL, w_space);
428 }
429
430 /* There must be at least one format token */
431 if (!a->format)
432 self_destruct();
433
434 /* helper */
435 if (!token)
436 self_destruct();
437
438 wordlistAdd(&a->cmdline, token);
439
440 /* arguments */
441 parse_wordlist(&a->cmdline);
442
443 while (*list)
444 list = &(*list)->next;
445
446 *list = a;
447 }
448
449 void
450 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
451 {
452 const external_acl *node;
453 const external_acl_format *format;
454 const wordlist *word;
455
456 for (node = list; node; node = node->next) {
457 storeAppendPrintf(sentry, "%s %s", name, node->name);
458
459 if (!node->local_addr.IsIPv6())
460 storeAppendPrintf(sentry, " ipv4");
461 else
462 storeAppendPrintf(sentry, " ipv6");
463
464 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
465 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
466
467 if (node->negative_ttl != node->ttl)
468 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
469
470 if (node->grace)
471 storeAppendPrintf(sentry, " grace=%d", node->grace);
472
473 if (node->children != DEFAULT_EXTERNAL_ACL_CHILDREN)
474 storeAppendPrintf(sentry, " children=%d", node->children);
475
476 if (node->concurrency)
477 storeAppendPrintf(sentry, " concurrency=%d", node->concurrency);
478
479 if (node->cache)
480 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
481
482 for (format = node->format; format; format = format->next) {
483 switch (format->type) {
484
485 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
486 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
487 storeAppendPrintf(sentry, " %%>{%s}", format->header);
488 break;
489
490 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
491 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
492 storeAppendPrintf(sentry, " %%>{%s:%s}", format->header, format->member);
493 break;
494
495 case _external_acl_format::EXT_ACL_HEADER_REPLY:
496 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
497 storeAppendPrintf(sentry, " %%<{%s}", format->header);
498 break;
499
500 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
501 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
502 storeAppendPrintf(sentry, " %%<{%s:%s}", format->header, format->member);
503 break;
504 #define DUMP_EXT_ACL_TYPE(a) \
505 case _external_acl_format::EXT_ACL_##a: \
506 storeAppendPrintf(sentry, " %%%s", #a); \
507 break
508
509 DUMP_EXT_ACL_TYPE(LOGIN);
510 #if USE_IDENT
511
512 DUMP_EXT_ACL_TYPE(IDENT);
513 #endif
514
515 DUMP_EXT_ACL_TYPE(SRC);
516 DUMP_EXT_ACL_TYPE(SRCPORT);
517 DUMP_EXT_ACL_TYPE(MYADDR);
518 DUMP_EXT_ACL_TYPE(MYPORT);
519 DUMP_EXT_ACL_TYPE(URI);
520 DUMP_EXT_ACL_TYPE(DST);
521 DUMP_EXT_ACL_TYPE(PROTO);
522 DUMP_EXT_ACL_TYPE(PORT);
523 DUMP_EXT_ACL_TYPE(PATH);
524 DUMP_EXT_ACL_TYPE(METHOD);
525 #if USE_SSL
526
527 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
528 storeAppendPrintf(sentry, " %%USER_CERT");
529 break;
530
531 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
532 storeAppendPrintf(sentry, " %%USER_CERTCHAIN");
533 break;
534
535 case _external_acl_format::EXT_ACL_USER_CERT:
536 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
537 break;
538
539 case _external_acl_format::EXT_ACL_CA_CERT:
540 storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header);
541 break;
542 #endif
543
544 DUMP_EXT_ACL_TYPE(EXT_USER);
545
546 default:
547 fatal("unknown external_acl format error");
548 break;
549 }
550 }
551
552 for (word = node->cmdline; word; word = word->next)
553 storeAppendPrintf(sentry, " %s", word->key);
554
555 storeAppendPrintf(sentry, "\n");
556 }
557 }
558
559 void
560 free_externalAclHelper(external_acl ** list)
561 {
562 while (*list) {
563 external_acl *node = *list;
564 *list = node->next;
565 node->next = NULL;
566 cbdataFree(node);
567 }
568 }
569
570 static external_acl *
571 find_externalAclHelper(const char *name)
572 {
573 external_acl *node;
574
575 for (node = Config.externalAclHelperList; node; node = node->next) {
576 if (strcmp(node->name, name) == 0)
577 return node;
578 }
579
580 return NULL;
581 }
582
583 void
584
585 external_acl::add
586 (ExternalACLEntry *anEntry)
587 {
588 trimCache();
589 assert (anEntry->def == NULL);
590 anEntry->def = this;
591 hash_join(cache, anEntry);
592 dlinkAdd(anEntry, &anEntry->lru, &lru_list);
593 cache_entries++;
594 }
595
596 void
597 external_acl::trimCache()
598 {
599 if (cache_size && cache_entries >= cache_size)
600 external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
601 }
602
603
604 /******************************************************************
605 * external acl type
606 */
607
608 struct _external_acl_data {
609 external_acl *def;
610 wordlist *arguments;
611 };
612
613 CBDATA_TYPE(external_acl_data);
614 static void
615 free_external_acl_data(void *data)
616 {
617 external_acl_data *p = static_cast<external_acl_data *>(data);
618 wordlistDestroy(&p->arguments);
619 cbdataReferenceDone(p->def);
620 }
621
622 void
623 ACLExternal::parse()
624 {
625 char *token;
626
627 if (data)
628 self_destruct();
629
630 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
631
632 data = cbdataAlloc(external_acl_data);
633
634 token = strtok(NULL, w_space);
635
636 if (!token)
637 self_destruct();
638
639 data->def = cbdataReference(find_externalAclHelper(token));
640
641 if (!data->def)
642 self_destruct();
643
644 while ((token = strtokFile())) {
645 wordlistAdd(&data->arguments, token);
646 }
647 }
648
649 bool
650 ACLExternal::valid () const
651 {
652 if (data->def->require_auth) {
653 if (authenticateSchemeCount() == 0) {
654 debugs(28, 0, "Can't use proxy auth because no authentication schemes were compiled.");
655 return false;
656 }
657
658 if (authenticateActiveSchemeCount() == 0) {
659 debugs(28, 0, "Can't use proxy auth because no authentication schemes are fully configured.");
660 return false;
661 }
662 }
663
664 return true;
665 }
666
667 bool
668 ACLExternal::empty () const
669 {
670 return false;
671 }
672
673 ACLExternal::~ACLExternal()
674 {
675 cbdataFree(data);
676 safe_free (class_);
677 }
678
679 static int
680 aclMatchExternal(external_acl_data *acl, ACLChecklist * ch);
681 static int
682 aclMatchExternal(external_acl_data *acl, ACLChecklist * ch)
683 {
684 int result;
685 external_acl_entry *entry;
686 const char *key = "";
687 debugs(82, 9, "aclMatchExternal: acl=\"" << acl->def->name << "\"");
688 entry = ch->extacl_entry;
689
690 if (entry) {
691 if (cbdataReferenceValid(entry) && entry->def == acl->def &&
692 strcmp((char *)entry->key, key) == 0) {
693 /* Ours, use it.. */
694 } else {
695 /* Not valid, or not ours.. get rid of it */
696 cbdataReferenceDone(ch->extacl_entry);
697 entry = NULL;
698 }
699 }
700
701 external_acl_message = "MISSING REQUIRED INFORMATION";
702
703 if (!entry) {
704 if (acl->def->require_auth) {
705 int ti;
706 /* Make sure the user is authenticated */
707
708 if ((ti = ch->authenticated()) != 1) {
709 debugs(82, 2, "aclMatchExternal: " << acl->def->name << " user not authenticated (" << ti << ")");
710 return ti;
711 }
712 }
713
714 key = makeExternalAclKey(ch, acl);
715
716 if (acl->def->require_auth)
717 AUTHUSERREQUESTUNLOCK(ch->auth_user_request, "ACLChecklist via aclMatchExternal");
718
719 if (!key) {
720 /* Not sufficient data to process */
721 return -1;
722 }
723
724 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
725
726 if (!entry || external_acl_grace_expired(acl->def, entry)) {
727 debugs(82, 2, "aclMatchExternal: " << acl->def->name << "(\"" << key << "\") = lookup needed");
728 debugs(82, 2, "aclMatchExternal: \"" << key << "\": entry=@" <<
729 entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
730
731 if (acl->def->theHelper->stats.queue_size <= acl->def->theHelper->n_running) {
732 debugs(82, 2, "aclMatchExternal: \"" << key << "\": queueing a call.");
733 ch->changeState (ExternalACLLookup::Instance());
734
735 if (entry == NULL) {
736 debugs(82, 2, "aclMatchExternal: \"" << key << "\": return -1.");
737 return -1;
738 }
739 } else {
740 if (!entry) {
741 debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
742 "' queue overload. Request rejected '" << key << "'.");
743 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
744 return -1;
745 } else {
746 debugs(82, 1, "aclMatchExternal: '" << acl->def->name <<
747 "' queue overload. Using stale result. '" << key << "'.");
748 /* Fall thru to processing below */
749 }
750 }
751 }
752 }
753
754 external_acl_cache_touch(acl->def, entry);
755 result = entry->result;
756 external_acl_message = entry->message.termedBuf();
757
758 debugs(82, 2, "aclMatchExternal: " << acl->def->name << " = " << result);
759
760 if (ch->request) {
761 if (entry->user.size())
762 ch->request->extacl_user = entry->user;
763
764 if (entry->password.size())
765 ch->request->extacl_passwd = entry->password;
766
767 if (!ch->request->tag.size())
768 ch->request->tag = entry->tag;
769
770 if (entry->log.size())
771 ch->request->extacl_log = entry->log;
772 }
773
774 return result;
775 }
776
777 int
778 ACLExternal::match(ACLChecklist *checklist)
779 {
780 return aclMatchExternal (data, checklist);
781 }
782
783 wordlist *
784 ACLExternal::dump() const
785 {
786 external_acl_data const *acl = data;
787 wordlist *result = NULL;
788 wordlist *arg;
789 MemBuf mb;
790 mb.init();
791 mb.Printf("%s", acl->def->name);
792
793 for (arg = acl->arguments; arg; arg = arg->next) {
794 mb.Printf(" %s", arg->key);
795 }
796
797 wordlistAdd(&result, mb.buf);
798 mb.clean();
799 return result;
800 }
801
802 /******************************************************************
803 * external_acl cache
804 */
805
806 static void
807 external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
808 {
809 dlinkDelete(&entry->lru, &def->lru_list);
810 dlinkAdd(entry, &entry->lru, &def->lru_list);
811 }
812
813 static char *
814 makeExternalAclKey(ACLChecklist * ch, external_acl_data * acl_data)
815 {
816 static MemBuf mb;
817 char buf[256];
818 int first = 1;
819 wordlist *arg;
820 external_acl_format *format;
821 HttpRequest *request = ch->request;
822 HttpReply *reply = ch->reply;
823 mb.reset();
824
825 for (format = acl_data->def->format; format; format = format->next) {
826 const char *str = NULL;
827 String sb;
828
829 switch (format->type) {
830
831 case _external_acl_format::EXT_ACL_LOGIN:
832 assert (ch->auth_user_request);
833 str = ch->auth_user_request->username();
834 break;
835 #if USE_IDENT
836
837 case _external_acl_format::EXT_ACL_IDENT:
838 str = ch->rfc931;
839
840 if (!str || !*str) {
841 ch->changeState(IdentLookup::Instance());
842 return NULL;
843 }
844
845 break;
846 #endif
847
848 case _external_acl_format::EXT_ACL_SRC:
849 str = ch->src_addr.NtoA(buf,sizeof(buf));
850 break;
851
852 case _external_acl_format::EXT_ACL_SRCPORT:
853 snprintf(buf, sizeof(buf), "%d", request->client_addr.GetPort());
854 str = buf;
855 break;
856
857 case _external_acl_format::EXT_ACL_MYADDR:
858 str = request->my_addr.NtoA(buf, sizeof(buf));
859 break;
860
861 case _external_acl_format::EXT_ACL_MYPORT:
862 snprintf(buf, sizeof(buf), "%d", request->my_addr.GetPort());
863 str = buf;
864 break;
865
866 case _external_acl_format::EXT_ACL_URI:
867 str = urlCanonical(request);
868 break;
869
870 case _external_acl_format::EXT_ACL_DST:
871 str = request->GetHost();
872 break;
873
874 case _external_acl_format::EXT_ACL_PROTO:
875 str = ProtocolStr[request->protocol];
876 break;
877
878 case _external_acl_format::EXT_ACL_PORT:
879 snprintf(buf, sizeof(buf), "%d", request->port);
880 str = buf;
881 break;
882
883 case _external_acl_format::EXT_ACL_PATH:
884 str = request->urlpath.termedBuf();
885 break;
886
887 case _external_acl_format::EXT_ACL_METHOD:
888 str = RequestMethodStr(request->method);
889 break;
890
891 case _external_acl_format::EXT_ACL_HEADER_REQUEST:
892 sb = request->header.getByName(format->header);
893 str = sb.termedBuf();
894 break;
895
896 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID:
897 sb = request->header.getStrOrList(format->header_id);
898 str = sb.termedBuf();
899 break;
900
901 case _external_acl_format::EXT_ACL_HEADER_REQUEST_MEMBER:
902 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
903 str = sb.termedBuf();
904 break;
905
906 case _external_acl_format::EXT_ACL_HEADER_REQUEST_ID_MEMBER:
907 sb = request->header.getListMember(format->header_id, format->member, format->separator);
908 str = sb.termedBuf();
909 break;
910
911 case _external_acl_format::EXT_ACL_HEADER_REPLY:
912 if (reply) {
913 sb = reply->header.getByName(format->header);
914 str = sb.termedBuf();
915 }
916 break;
917
918 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID:
919 if (reply) {
920 sb = reply->header.getStrOrList(format->header_id);
921 str = sb.termedBuf();
922 }
923 break;
924
925 case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER:
926 if (reply) {
927 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
928 str = sb.termedBuf();
929 }
930 break;
931
932 case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER:
933 if (reply) {
934 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
935 str = sb.termedBuf();
936 }
937 break;
938 #if USE_SSL
939
940 case _external_acl_format::EXT_ACL_USER_CERT_RAW:
941
942 if (ch->conn() != NULL) {
943 SSL *ssl = fd_table[ch->conn()->fd].ssl;
944
945 if (ssl)
946 str = sslGetUserCertificatePEM(ssl);
947 }
948
949 break;
950
951 case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW:
952
953 if (ch->conn() != NULL) {
954 SSL *ssl = fd_table[ch->conn()->fd].ssl;
955
956 if (ssl)
957 str = sslGetUserCertificateChainPEM(ssl);
958 }
959
960 break;
961
962 case _external_acl_format::EXT_ACL_USER_CERT:
963
964 if (ch->conn() != NULL) {
965 SSL *ssl = fd_table[ch->conn()->fd].ssl;
966
967 if (ssl)
968 str = sslGetUserAttribute(ssl, format->header);
969 }
970
971 break;
972
973 case _external_acl_format::EXT_ACL_CA_CERT:
974
975 if (ch->conn() != NULL) {
976 SSL *ssl = fd_table[ch->conn()->fd].ssl;
977
978 if (ssl)
979 str = sslGetCAAttribute(ssl, format->header);
980 }
981
982 break;
983 #endif
984
985 case _external_acl_format::EXT_ACL_EXT_USER:
986 str = request->extacl_user.termedBuf();
987 break;
988
989 case _external_acl_format::EXT_ACL_UNKNOWN:
990
991 case _external_acl_format::EXT_ACL_END:
992 fatal("unknown external_acl format error");
993 break;
994 }
995
996 if (str)
997 if (!*str)
998 str = NULL;
999
1000 if (!str)
1001 str = "-";
1002
1003 if (!first)
1004 mb.append(" ", 1);
1005
1006 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1007 const char *quoted = rfc1738_escape(str);
1008 mb.append(quoted, strlen(quoted));
1009 } else {
1010 strwordquote(&mb, str);
1011 }
1012
1013 sb.clean();
1014
1015 first = 0;
1016 }
1017
1018 for (arg = acl_data->arguments; arg; arg = arg->next) {
1019 if (!first)
1020 mb.append(" ", 1);
1021
1022 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1023 const char *quoted = rfc1738_escape(arg->key);
1024 mb.append(quoted, strlen(quoted));
1025 } else {
1026 strwordquote(&mb, arg->key);
1027 }
1028
1029 first = 0;
1030 }
1031
1032 return mb.buf;
1033 }
1034
1035 static int
1036 external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1037 {
1038 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
1039 return 1;
1040 else
1041 return 0;
1042 }
1043
1044 static int
1045 external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1046 {
1047 int ttl;
1048 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1049 ttl = (ttl * (100 - def->grace)) / 100;
1050
1051 if (entry->date + ttl < squid_curtime)
1052 return 1;
1053 else
1054 return 0;
1055 }
1056
1057 static external_acl_entry *
1058 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
1059 {
1060 ExternalACLEntry *entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
1061 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
1062
1063 if (entry) {
1064 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1065 entry->update (data);
1066 external_acl_cache_touch(def, entry);
1067
1068 return entry;
1069 }
1070
1071 entry = new ExternalACLEntry;
1072 entry->key = xstrdup(key);
1073 entry->update (data);
1074
1075 def->add
1076 (entry);
1077
1078 return entry;
1079 }
1080
1081 static void
1082 external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1083 {
1084 assert (entry->def == def);
1085 hash_remove_link(def->cache, entry);
1086 dlinkDelete(&entry->lru, &def->lru_list);
1087 def->cache_entries -= 1;
1088 delete entry;
1089 }
1090
1091 /******************************************************************
1092 * external_acl helpers
1093 */
1094
1095 typedef struct _externalAclState externalAclState;
1096
1097 struct _externalAclState {
1098 EAH *callback;
1099 void *callback_data;
1100 char *key;
1101 external_acl *def;
1102 dlink_node list;
1103 externalAclState *queue;
1104 };
1105
1106 CBDATA_TYPE(externalAclState);
1107 static void
1108 free_externalAclState(void *data)
1109 {
1110 externalAclState *state = static_cast<externalAclState *>(data);
1111 safe_free(state->key);
1112 cbdataReferenceDone(state->callback_data);
1113 cbdataReferenceDone(state->def);
1114 }
1115
1116 /*
1117 * The helper program receives queries on stdin, one
1118 * per line, and must return the result on on stdout
1119 *
1120 * General result syntax:
1121 *
1122 * OK/ERR keyword=value ...
1123 *
1124 * Keywords:
1125 *
1126 * user= The users name (login)
1127 * message= Message describing the reason
1128 * tag= A string tag to be applied to the request that triggered the acl match.
1129 * applies to both OK and ERR responses.
1130 * Won't override existing request tags.
1131 * log= A string to be used in access logging
1132 *
1133 * Other keywords may be added to the protocol later
1134 *
1135 * value needs to be enclosed in quotes if it may contain whitespace, or
1136 * the whitespace escaped using \ (\ escaping obviously also applies to
1137 * any " characters)
1138 */
1139
1140 static void
1141 externalAclHandleReply(void *data, char *reply)
1142 {
1143 externalAclState *state = static_cast<externalAclState *>(data);
1144 externalAclState *next;
1145 char *status;
1146 char *token;
1147 char *value;
1148 char *t;
1149 ExternalACLEntryData entryData;
1150 entryData.result = 0;
1151 external_acl_entry *entry = NULL;
1152
1153 debugs(82, 2, "externalAclHandleReply: reply=\"" << reply << "\"");
1154
1155 if (reply) {
1156 status = strwordtok(reply, &t);
1157
1158 if (status && strcmp(status, "OK") == 0)
1159 entryData.result = 1;
1160
1161 while ((token = strwordtok(NULL, &t))) {
1162 value = strchr(token, '=');
1163
1164 if (value) {
1165 *value++ = '\0'; /* terminate the token, and move up to the value */
1166
1167 if (state->def->quote == external_acl::QUOTE_METHOD_URL)
1168 rfc1738_unescape(value);
1169
1170 if (strcmp(token, "user") == 0)
1171 entryData.user = value;
1172 else if (strcmp(token, "message") == 0)
1173 entryData.message = value;
1174 else if (strcmp(token, "error") == 0)
1175 entryData.message = value;
1176 else if (strcmp(token, "tag") == 0)
1177 entryData.tag = value;
1178 else if (strcmp(token, "log") == 0)
1179 entryData.log = value;
1180 else if (strcmp(token, "password") == 0)
1181 entryData.password = value;
1182 else if (strcmp(token, "passwd") == 0)
1183 entryData.password = value;
1184 else if (strcmp(token, "login") == 0)
1185 entryData.user = value;
1186 }
1187 }
1188 }
1189
1190 dlinkDelete(&state->list, &state->def->queue);
1191
1192 if (cbdataReferenceValid(state->def)) {
1193 if (reply)
1194 entry = external_acl_cache_add(state->def, state->key, entryData);
1195 else {
1196 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
1197
1198 if (oldentry)
1199 external_acl_cache_delete(state->def, oldentry);
1200 }
1201 }
1202
1203 do {
1204 void *cbdata;
1205 cbdataReferenceDone(state->def);
1206
1207 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
1208 state->callback(cbdata, entry);
1209
1210 next = state->queue;
1211
1212 cbdataFree(state);
1213
1214 state = next;
1215 } while (state);
1216 }
1217
1218 void
1219 ACLExternal::ExternalAclLookup(ACLChecklist * ch, ACLExternal * me, EAH * callback, void *callback_data)
1220 {
1221 MemBuf buf;
1222 external_acl_data *acl = me->data;
1223 external_acl *def = acl->def;
1224 externalAclState *state;
1225 dlink_node *node;
1226 externalAclState *oldstate = NULL;
1227 bool graceful = 0;
1228
1229 if (acl->def->require_auth) {
1230 int ti;
1231 /* Make sure the user is authenticated */
1232
1233 if ((ti = ch->authenticated()) != 1) {
1234 debugs(82, 1, "externalAclLookup: " << acl->def->name <<
1235 " user authentication failure (" << ti << ", ch=" << ch << ")");
1236 callback(callback_data, NULL);
1237 return;
1238 }
1239 }
1240
1241 const char *key = makeExternalAclKey(ch, acl);
1242
1243 if (!key) {
1244 debugs(82, 1, "externalAclLookup: lookup in '" << def->name <<
1245 "', prerequisit failure (ch=" << ch << ")");
1246 callback(callback_data, NULL);
1247 return;
1248 }
1249
1250 debugs(82, 2, "externalAclLookup: lookup in '" << def->name << "' for '" << key << "'");
1251
1252 external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key));
1253
1254 if (entry && external_acl_entry_expired(def, entry))
1255 entry = NULL;
1256
1257 /* Check for a pending lookup to hook into */
1258 for (node = def->queue.head; node; node = node->next) {
1259 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1260
1261 if (strcmp(key, oldstatetmp->key) == 0) {
1262 oldstate = oldstatetmp;
1263 break;
1264 }
1265 }
1266
1267 if (entry && external_acl_grace_expired(def, entry)) {
1268 if (oldstate) {
1269 debugs(82, 4, "externalAclLookup: in grace period, but already pending lookup ('" << key << "', ch=" << ch << ")");
1270 callback(callback_data, entry);
1271 return;
1272 } else {
1273 graceful = 1; // grace expired, (neg)ttl did not, and we must start a new lookup.
1274 }
1275 }
1276
1277 // The entry is in the cache, grace_ttl did not expired.
1278 if (!graceful && entry && !external_acl_grace_expired(def, entry)) {
1279 /* Should not really happen, but why not.. */
1280 callback(callback_data, entry);
1281 debugs(82, 4, "externalAclLookup: no lookup pending for '" << key << "', and grace not expired");
1282 debugs(82, 4, "externalAclLookup: (what tha' hell?)");
1283 return;
1284 }
1285
1286 /* No pending lookup found. Sumbit to helper */
1287 state = cbdataAlloc(externalAclState);
1288
1289 state->def = cbdataReference(def);
1290
1291 state->key = xstrdup(key);
1292
1293 if (!graceful) {
1294 state->callback = callback;
1295 state->callback_data = cbdataReference(callback_data);
1296 }
1297
1298 if (oldstate) {
1299 /* Hook into pending lookup */
1300 state->queue = oldstate->queue;
1301 oldstate->queue = state;
1302 } else {
1303 /* Check for queue overload */
1304
1305 if (def->theHelper->stats.queue_size >= def->theHelper->n_running) {
1306 debugs(82, 1, "externalAclLookup: '" << def->name << "' queue overload (ch=" << ch << ")");
1307 cbdataFree(state);
1308 callback(callback_data, entry);
1309 return;
1310 }
1311
1312 /* Send it off to the helper */
1313 buf.init();
1314
1315 buf.Printf("%s\n", key);
1316
1317 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1318
1319 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
1320
1321 dlinkAdd(state, &state->list, &def->queue);
1322
1323 buf.clean();
1324 }
1325
1326 if (graceful) {
1327 /* No need to wait during grace period */
1328 debugs(82, 4, "externalAclLookup: no need to wait for the result of '" <<
1329 key << "' in '" << def->name << "' (ch=" << ch << ").");
1330 debugs(82, 4, "externalAclLookup: using cached entry " << entry);
1331
1332 if (entry != NULL) {
1333 debugs(82, 4, "externalAclLookup: entry = { date=" <<
1334 (long unsigned int) entry->date << ", result=" <<
1335 entry->result << ", user=" << entry->user << " tag=" <<
1336 entry->tag << " log=" << entry->log << " }");
1337
1338 }
1339
1340 callback(callback_data, entry);
1341 return;
1342 }
1343
1344 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1345 "' in '" << def->name << "' (ch=" << ch << ").");
1346 }
1347
1348 static void
1349 externalAclStats(StoreEntry * sentry)
1350 {
1351 external_acl *p;
1352
1353 for (p = Config.externalAclHelperList; p; p = p->next) {
1354 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1355 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1356 helperStats(sentry, p->theHelper);
1357 storeAppendPrintf(sentry, "\n");
1358 }
1359 }
1360
1361 static void
1362 externalAclRegisterWithCacheManager(void)
1363 {
1364 CacheManager::GetInstance()->
1365 registerAction("external_acl",
1366 "External ACL stats",
1367 externalAclStats, 0, 1);
1368 }
1369
1370 void
1371 externalAclInit(void)
1372 {
1373 static int firstTimeInit = 1;
1374 external_acl *p;
1375
1376 for (p = Config.externalAclHelperList; p; p = p->next) {
1377 if (!p->cache)
1378 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1379
1380 if (!p->theHelper)
1381 p->theHelper = helperCreate(p->name);
1382
1383 p->theHelper->cmdline = p->cmdline;
1384
1385 p->theHelper->n_to_start = p->children;
1386
1387 p->theHelper->concurrency = p->concurrency;
1388
1389 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1390
1391 p->theHelper->addr = p->local_addr;
1392
1393 helperOpenServers(p->theHelper);
1394 }
1395
1396 if (firstTimeInit) {
1397 firstTimeInit = 0;
1398 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
1399 }
1400
1401 externalAclRegisterWithCacheManager();
1402 }
1403
1404 void
1405 externalAclShutdown(void)
1406 {
1407 external_acl *p;
1408
1409 for (p = Config.externalAclHelperList; p; p = p->next) {
1410 helperShutdown(p->theHelper);
1411 }
1412 }
1413
1414 ExternalACLLookup ExternalACLLookup::instance_;
1415 ExternalACLLookup *
1416 ExternalACLLookup::Instance()
1417 {
1418 return &instance_;
1419 }
1420
1421 void
1422 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1423 {
1424 /* TODO: optimise this - we probably have a pointer to this
1425 * around somewhere */
1426 ACL *acl = ACL::FindByName(AclMatchedName);
1427 assert(acl);
1428 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1429 assert (me);
1430 checklist->asyncInProgress(true);
1431 ACLExternal::ExternalAclLookup(checklist, me, LookupDone, checklist);
1432 }
1433
1434 void
1435 ExternalACLLookup::LookupDone(void *data, void *result)
1436 {
1437 ACLChecklist *checklist = (ACLChecklist *)data;
1438 checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
1439 checklist->asyncInProgress(false);
1440 checklist->changeState (ACLChecklist::NullState::Instance());
1441 checklist->check();
1442 }
1443
1444 /* This registers "external" in the registry. To do dynamic definitions
1445 * of external ACL's, rather than a static prototype, have a Prototype instance
1446 * prototype in the class that defines each external acl 'class'.
1447 * Then, then the external acl instance is created, it self registers under
1448 * it's name.
1449 * Be sure that clone is fully functional for that acl class though!
1450 */
1451 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1452
1453 ACLExternal ACLExternal::RegistryEntry_("external");
1454
1455 ACL *
1456 ACLExternal::clone() const
1457 {
1458 return new ACLExternal(*this);
1459 }
1460
1461 ACLExternal::ACLExternal (char const *theClass) : data (NULL), class_ (xstrdup (theClass))
1462 {}
1463
1464 ACLExternal::ACLExternal (ACLExternal const & old) : data (NULL), class_ (old.class_ ? xstrdup (old.class_) : NULL)
1465 {
1466 /* we don't have copy constructors for the data yet */
1467 assert (!old.data);
1468 }
1469
1470 char const *
1471 ACLExternal::typeString() const
1472 {
1473 return class_;
1474 }
1475
1476 bool
1477 ACLExternal::isProxyAuth() const
1478 {
1479 return data->def->require_auth;
1480 }