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