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