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