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