]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
Boilerplate: update copyright blurbs on src/
[thirdparty/squid.git] / src / external_acl.cc
1 /*
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 82 External ACL */
10
11 #include "squid.h"
12 #include "acl/Acl.h"
13 #include "acl/FilledChecklist.h"
14 #include "cache_cf.h"
15 #include "client_side.h"
16 #include "comm/Connection.h"
17 #include "ConfigParser.h"
18 #include "ExternalACL.h"
19 #include "ExternalACLEntry.h"
20 #include "fde.h"
21 #include "format/ByteCode.h"
22 #include "helper.h"
23 #include "HttpHeaderTools.h"
24 #include "HttpReply.h"
25 #include "HttpRequest.h"
26 #include "ip/tools.h"
27 #include "MemBuf.h"
28 #include "mgr/Registration.h"
29 #include "rfc1738.h"
30 #include "SquidConfig.h"
31 #include "SquidString.h"
32 #include "SquidTime.h"
33 #include "Store.h"
34 #include "tools.h"
35 #include "URL.h"
36 #include "wordlist.h"
37 #if USE_OPENSSL
38 #include "ssl/ServerBump.h"
39 #include "ssl/support.h"
40 #endif
41 #if USE_AUTH
42 #include "auth/Acl.h"
43 #include "auth/Gadgets.h"
44 #include "auth/UserRequest.h"
45 #endif
46 #if USE_IDENT
47 #include "ident/AclIdent.h"
48 #endif
49
50 #ifndef DEFAULT_EXTERNAL_ACL_TTL
51 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
52 #endif
53 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
54 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
55 #endif
56
57 typedef struct _external_acl_format external_acl_format;
58
59 static char *makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data);
60 static void external_acl_cache_delete(external_acl * def, external_acl_entry * entry);
61 static int external_acl_entry_expired(external_acl * def, external_acl_entry * entry);
62 static int external_acl_grace_expired(external_acl * def, external_acl_entry * entry);
63 static void external_acl_cache_touch(external_acl * def, external_acl_entry * entry);
64 static external_acl_entry *external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
65
66 /******************************************************************
67 * external_acl directive
68 */
69
70 class external_acl
71 {
72
73 public:
74 external_acl *next;
75
76 void add(ExternalACLEntry *);
77
78 void trimCache();
79
80 int ttl;
81
82 int negative_ttl;
83
84 int grace;
85
86 char *name;
87
88 external_acl_format *format;
89
90 wordlist *cmdline;
91
92 HelperChildConfig children;
93
94 helper *theHelper;
95
96 hash_table *cache;
97
98 dlink_list lru_list;
99
100 int cache_size;
101
102 int cache_entries;
103
104 dlink_list queue;
105
106 #if USE_AUTH
107 /**
108 * Configuration flag. May only be altered by the configuration parser.
109 *
110 * Indicates that all uses of this external_acl_type helper require authentication
111 * details to be processed. If none are available its a fail match.
112 */
113 bool require_auth;
114 #endif
115
116 enum {
117 QUOTE_METHOD_SHELL = 1,
118 QUOTE_METHOD_URL
119 } quote;
120
121 Ip::Address local_addr;
122 };
123
124 struct _external_acl_format {
125 Format::ByteCode_t type;
126 external_acl_format *next;
127 char *header;
128 char *member;
129 char separator;
130 http_hdr_type header_id;
131 };
132
133 /* FIXME: These are not really cbdata, but it is an easy way
134 * to get them pooled, refcounted, accounted and freed properly...
135 */
136 CBDATA_TYPE(external_acl);
137 CBDATA_TYPE(external_acl_format);
138
139 static void
140 free_external_acl_format(void *data)
141 {
142 external_acl_format *p = static_cast<external_acl_format *>(data);
143 safe_free(p->header);
144 }
145
146 static void
147 free_external_acl(void *data)
148 {
149 external_acl *p = static_cast<external_acl *>(data);
150 safe_free(p->name);
151
152 while (p->format) {
153 external_acl_format *f = p->format;
154 p->format = f->next;
155 cbdataFree(f);
156 }
157
158 wordlistDestroy(&p->cmdline);
159
160 if (p->theHelper) {
161 helperShutdown(p->theHelper);
162 delete p->theHelper;
163 p->theHelper = NULL;
164 }
165
166 while (p->lru_list.tail)
167 external_acl_cache_delete(p, static_cast<external_acl_entry *>(p->lru_list.tail->data));
168 if (p->cache)
169 hashFreeMemory(p->cache);
170 }
171
172 /**
173 * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
174 * request or reply header to external helper.
175 *
176 \param header - the token being parsed (without the identifying prefix)
177 \param type - format enum identifier for this element, pulled from identifying prefix
178 \param format - structure to contain all the info about this format element.
179 */
180 void
181 parse_header_token(external_acl_format *format, char *header, const Format::ByteCode_t type)
182 {
183 /* header format */
184 char *member, *end;
185
186 /** Cut away the closing brace */
187 end = strchr(header, '}');
188 if (end && strlen(end) == 1)
189 *end = '\0';
190 else
191 self_destruct();
192
193 member = strchr(header, ':');
194
195 if (member) {
196 /* Split in header and member */
197 *member = '\0';
198 ++member;
199
200 if (!xisalnum(*member)) {
201 format->separator = *member;
202 ++member;
203 } else {
204 format->separator = ',';
205 }
206
207 format->member = xstrdup(member);
208
209 if (type == Format::LFT_ADAPTED_REQUEST_HEADER)
210 format->type = Format::LFT_ADAPTED_REQUEST_HEADER_ELEM;
211 else
212 format->type = Format::LFT_REPLY_HEADER_ELEM;
213
214 } else {
215 format->type = type;
216 }
217
218 format->header = xstrdup(header);
219 format->header_id = httpHeaderIdByNameDef(header, strlen(header));
220 }
221
222 void
223 parse_externalAclHelper(external_acl ** list)
224 {
225 external_acl *a;
226 char *token;
227 external_acl_format **p;
228
229 CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl);
230 CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format);
231
232 a = cbdataAlloc(external_acl);
233
234 /* set defaults */
235 a->ttl = DEFAULT_EXTERNAL_ACL_TTL;
236 a->negative_ttl = -1;
237 a->cache_size = 256*1024;
238 a->children.n_max = DEFAULT_EXTERNAL_ACL_CHILDREN;
239 a->children.n_startup = a->children.n_max;
240 a->children.n_idle = 1;
241 a->local_addr.setLocalhost();
242 a->quote = external_acl::QUOTE_METHOD_URL;
243
244 token = ConfigParser::NextToken();
245
246 if (!token)
247 self_destruct();
248
249 a->name = xstrdup(token);
250
251 // Allow supported %macros inside quoted tokens
252 ConfigParser::EnableMacros();
253 token = ConfigParser::NextToken();
254
255 /* Parse options */
256 while (token) {
257 if (strncmp(token, "ttl=", 4) == 0) {
258 a->ttl = atoi(token + 4);
259 } else if (strncmp(token, "negative_ttl=", 13) == 0) {
260 a->negative_ttl = atoi(token + 13);
261 } else if (strncmp(token, "children=", 9) == 0) {
262 a->children.n_max = atoi(token + 9);
263 debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
264 } else if (strncmp(token, "children-max=", 13) == 0) {
265 a->children.n_max = atoi(token + 13);
266 } else if (strncmp(token, "children-startup=", 17) == 0) {
267 a->children.n_startup = atoi(token + 17);
268 } else if (strncmp(token, "children-idle=", 14) == 0) {
269 a->children.n_idle = atoi(token + 14);
270 } else if (strncmp(token, "concurrency=", 12) == 0) {
271 a->children.concurrency = atoi(token + 12);
272 } else if (strncmp(token, "cache=", 6) == 0) {
273 a->cache_size = atoi(token + 6);
274 } else if (strncmp(token, "grace=", 6) == 0) {
275 a->grace = atoi(token + 6);
276 } else if (strcmp(token, "protocol=2.5") == 0) {
277 a->quote = external_acl::QUOTE_METHOD_SHELL;
278 } else if (strcmp(token, "protocol=3.0") == 0) {
279 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option protocol=3.0 is deprecated. Remove this from your config.");
280 a->quote = external_acl::QUOTE_METHOD_URL;
281 } else if (strcmp(token, "quote=url") == 0) {
282 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
283 a->quote = external_acl::QUOTE_METHOD_URL;
284 } else if (strcmp(token, "quote=shell") == 0) {
285 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
286 a->quote = external_acl::QUOTE_METHOD_SHELL;
287
288 /* INET6: allow admin to configure some helpers explicitly to
289 bind to IPv4/v6 localhost port. */
290 } else if (strcmp(token, "ipv4") == 0) {
291 if ( !a->local_addr.setIPv4() ) {
292 debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
293 }
294 } else if (strcmp(token, "ipv6") == 0) {
295 if (!Ip::EnableIpv6)
296 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
297 // else nothing to do.
298 } else {
299 break;
300 }
301
302 token = ConfigParser::NextToken();
303 }
304 ConfigParser::DisableMacros();
305
306 /* check that child startup value is sane. */
307 if (a->children.n_startup > a->children.n_max)
308 a->children.n_startup = a->children.n_max;
309
310 /* check that child idle value is sane. */
311 if (a->children.n_idle > a->children.n_max)
312 a->children.n_idle = a->children.n_max;
313 if (a->children.n_idle < 1)
314 a->children.n_idle = 1;
315
316 if (a->negative_ttl == -1)
317 a->negative_ttl = a->ttl;
318
319 /* Parse format */
320 p = &a->format;
321
322 while (token) {
323 external_acl_format *format;
324
325 /* stop on first non-format token found */
326
327 if (*token != '%')
328 break;
329
330 format = cbdataAlloc(external_acl_format);
331
332 if (strncmp(token, "%{", 2) == 0) {
333 // deprecated. but assume the old configs all referred to request headers.
334 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %{...} is being replaced by %>ha{...} for : " << token);
335 parse_header_token(format, (token+2), Format::LFT_ADAPTED_REQUEST_HEADER);
336 } else if (strncmp(token, "%>{", 3) == 0) {
337 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %>{...} is being replaced by %>ha{...} for : " << token);
338 parse_header_token(format, (token+3), Format::LFT_ADAPTED_REQUEST_HEADER);
339 } else if (strncmp(token, "%>ha{", 5) == 0) {
340 parse_header_token(format, (token+3), Format::LFT_ADAPTED_REQUEST_HEADER);
341 } else if (strncmp(token, "%<{", 3) == 0) {
342 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %<{...} is being replaced by %<h{...} for : " << token);
343 parse_header_token(format, (token+3), Format::LFT_REPLY_HEADER);
344 } else if (strncmp(token, "%<h{", 4) == 0) {
345 parse_header_token(format, (token+3), Format::LFT_REPLY_HEADER);
346 #if USE_AUTH
347 } else if (strcmp(token, "%LOGIN") == 0 || strcmp(token, "%ul") == 0) {
348 format->type = Format::LFT_USER_LOGIN;
349 a->require_auth = true;
350 #endif
351 }
352 #if USE_IDENT
353 else if (strcmp(token, "%IDENT") == 0 || strcmp(token, "%ui") == 0)
354 format->type = Format::LFT_USER_IDENT;
355 #endif
356 else if (strcmp(token, "%SRC") == 0 || strcmp(token, "%>a") == 0)
357 format->type = Format::LFT_CLIENT_IP_ADDRESS;
358 else if (strcmp(token, "%SRCPORT") == 0 || strcmp(token, "%>p") == 0)
359 format->type = Format::LFT_CLIENT_PORT;
360 #if USE_SQUID_EUI
361 else if (strcmp(token, "%SRCEUI48") == 0)
362 format->type = Format::LFT_EXT_ACL_CLIENT_EUI48;
363 else if (strcmp(token, "%SRCEUI64") == 0)
364 format->type = Format::LFT_EXT_ACL_CLIENT_EUI64;
365 #endif
366 else if (strcmp(token, "%MYADDR") == 0 || strcmp(token, "%la") == 0)
367 format->type = Format::LFT_LOCAL_LISTENING_IP;
368 else if (strcmp(token, "%MYPORT") == 0 || strcmp(token, "%lp") == 0)
369 format->type = Format::LFT_LOCAL_LISTENING_PORT;
370 else if (strcmp(token, "%URI") == 0 || strcmp(token, "%>ru") == 0)
371 format->type = Format::LFT_CLIENT_REQ_URI;
372 else if (strcmp(token, "%DST") == 0 || strcmp(token, "%>rd") == 0)
373 format->type = Format::LFT_CLIENT_REQ_URLDOMAIN;
374 else if (strcmp(token, "%PROTO") == 0 || strcmp(token, "%>rs") == 0)
375 format->type = Format::LFT_CLIENT_REQ_URLSCHEME;
376 else if (strcmp(token, "%PORT") == 0) // XXX: add a logformat token
377 format->type = Format::LFT_CLIENT_REQ_URLPORT;
378 else if (strcmp(token, "%PATH") == 0 || strcmp(token, "%>rp") == 0)
379 format->type = Format::LFT_CLIENT_REQ_URLPATH;
380 else if (strcmp(token, "%METHOD") == 0 || strcmp(token, "%>rm") == 0)
381 format->type = Format::LFT_CLIENT_REQ_METHOD;
382 #if USE_OPENSSL
383 else if (strcmp(token, "%USER_CERT") == 0)
384 format->type = Format::LFT_EXT_ACL_USER_CERT_RAW;
385 else if (strcmp(token, "%USER_CERTCHAIN") == 0)
386 format->type = Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW;
387 else if (strncmp(token, "%USER_CERT_", 11) == 0) {
388 format->type = Format::LFT_EXT_ACL_USER_CERT;
389 format->header = xstrdup(token + 11);
390 } else if (strncmp(token, "%USER_CA_CERT_", 14) == 0) {
391 format->type = Format::LFT_EXT_ACL_USER_CA_CERT;
392 format->header = xstrdup(token + 14);
393 } else if (strncmp(token, "%CA_CERT_", 9) == 0) {
394 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead");
395 format->type = Format::LFT_EXT_ACL_USER_CA_CERT;
396 format->header = xstrdup(token + 9);
397 } else if (strcmp(token, "%ssl::>sni") == 0)
398 format->type = Format::LFT_SSL_CLIENT_SNI;
399 else if (strcmp(token, "%ssl::<cert_subject") == 0)
400 format->type = Format::LFT_SSL_SERVER_CERT_SUBJECT;
401 else if (strcmp(token, "%ssl::<cert_issuer") == 0)
402 format->type = Format::LFT_SSL_SERVER_CERT_ISSUER;
403 #endif
404 #if USE_AUTH
405 else if (strcmp(token, "%EXT_USER") == 0 || strcmp(token, "%ue") == 0)
406 format->type = Format::LFT_USER_EXTERNAL;
407 #endif
408 else if (strcmp(token, "%EXT_LOG") == 0 || strcmp(token, "%ea") == 0)
409 format->type = Format::LFT_EXT_LOG;
410 else if (strcmp(token, "%TAG") == 0 || strcmp(token, "%et") == 0)
411 format->type = Format::LFT_TAG;
412 else if (strcmp(token, "%ACL") == 0)
413 format->type = Format::LFT_EXT_ACL_NAME;
414 else if (strcmp(token, "%DATA") == 0)
415 format->type = Format::LFT_EXT_ACL_DATA;
416 else if (strcmp(token, "%%") == 0)
417 format->type = Format::LFT_PERCENT;
418 else {
419 debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
420 self_destruct();
421 }
422
423 *p = format;
424 p = &format->next;
425 token = ConfigParser::NextToken();
426 }
427
428 /* There must be at least one format token */
429 if (!a->format)
430 self_destruct();
431
432 /* helper */
433 if (!token)
434 self_destruct();
435
436 wordlistAdd(&a->cmdline, token);
437
438 /* arguments */
439 parse_wordlist(&a->cmdline);
440
441 while (*list)
442 list = &(*list)->next;
443
444 *list = a;
445 }
446
447 void
448 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
449 {
450 const external_acl *node;
451 const external_acl_format *format;
452 const wordlist *word;
453
454 for (node = list; node; node = node->next) {
455 storeAppendPrintf(sentry, "%s %s", name, node->name);
456
457 if (!node->local_addr.isIPv6())
458 storeAppendPrintf(sentry, " ipv4");
459 else
460 storeAppendPrintf(sentry, " ipv6");
461
462 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
463 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
464
465 if (node->negative_ttl != node->ttl)
466 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
467
468 if (node->grace)
469 storeAppendPrintf(sentry, " grace=%d", node->grace);
470
471 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
472 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
473
474 if (node->children.n_startup != 1)
475 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
476
477 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
478 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
479
480 if (node->children.concurrency)
481 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
482
483 if (node->cache)
484 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
485
486 if (node->quote == external_acl::QUOTE_METHOD_SHELL)
487 storeAppendPrintf(sentry, " protocol=2.5");
488
489 for (format = node->format; format; format = format->next) {
490 switch (format->type) {
491
492 case Format::LFT_ADAPTED_REQUEST_HEADER:
493 storeAppendPrintf(sentry, " %%>ha{%s}", format->header);
494 break;
495
496 case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
497 storeAppendPrintf(sentry, " %%>ha{%s:%s}", format->header, format->member);
498 break;
499
500 case Format::LFT_REPLY_HEADER:
501 storeAppendPrintf(sentry, " %%<h{%s}", format->header);
502 break;
503
504 case Format::LFT_REPLY_HEADER_ELEM:
505 storeAppendPrintf(sentry, " %%<h{%s:%s}", format->header, format->member);
506 break;
507
508 #define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
509 case Format::LFT_##a: \
510 storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
511 break
512 #if USE_AUTH
513 DUMP_EXT_ACL_TYPE_FMT(USER_LOGIN," %%ul");
514 #endif
515 #if USE_IDENT
516
517 DUMP_EXT_ACL_TYPE_FMT(USER_IDENT," %%ui");
518 #endif
519 DUMP_EXT_ACL_TYPE_FMT(CLIENT_IP_ADDRESS," %%>a");
520 DUMP_EXT_ACL_TYPE_FMT(CLIENT_PORT," %%>p");
521 #if USE_SQUID_EUI
522 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI48," %%SRCEUI48");
523 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI64," %%SRCEUI64");
524 #endif
525 DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_IP," %%>la");
526 DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_PORT," %%>lp");
527 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URI," %%>ru");
528 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLDOMAIN," %%>rd");
529 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLSCHEME," %%>rs");
530 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPORT," %%>rP");
531 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPATH," %%>rp");
532 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_METHOD," %%>rm");
533 #if USE_OPENSSL
534 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT_RAW, " %%USER_CERT_RAW");
535 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERTCHAIN_RAW, " %%USER_CERTCHAIN_RAW");
536 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT, " %%USER_CERT_%s", format->header);
537 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CA_CERT, " %%USER_CA_CERT_%s", format->header);
538 DUMP_EXT_ACL_TYPE_FMT(SSL_CLIENT_SNI, "%%ssl::>sni");
539 DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_SUBJECT, "%%ssl::<cert_subject");
540 DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_ISSUER, "%%ssl::<cert_issuer");
541 #endif
542 #if USE_AUTH
543 DUMP_EXT_ACL_TYPE_FMT(USER_EXTERNAL," %%ue");
544 #endif
545 DUMP_EXT_ACL_TYPE_FMT(EXT_LOG," %%ea");
546 DUMP_EXT_ACL_TYPE_FMT(TAG," %%et");
547 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_NAME," %%ACL");
548 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_DATA," %%DATA");
549 DUMP_EXT_ACL_TYPE_FMT(PERCENT, " %%%%");
550 default:
551 fatal("unknown external_acl format error");
552 break;
553 }
554 }
555
556 for (word = node->cmdline; word; word = word->next)
557 storeAppendPrintf(sentry, " %s", word->key);
558
559 storeAppendPrintf(sentry, "\n");
560 }
561 }
562
563 void
564 free_externalAclHelper(external_acl ** list)
565 {
566 while (*list) {
567 external_acl *node = *list;
568 *list = node->next;
569 node->next = NULL;
570 cbdataFree(node);
571 }
572 }
573
574 static external_acl *
575 find_externalAclHelper(const char *name)
576 {
577 external_acl *node;
578
579 for (node = Config.externalAclHelperList; node; node = node->next) {
580 if (strcmp(node->name, name) == 0)
581 return node;
582 }
583
584 return NULL;
585 }
586
587 void
588 external_acl::add(ExternalACLEntry *anEntry)
589 {
590 trimCache();
591 assert (anEntry->def == NULL);
592 anEntry->def = this;
593 hash_join(cache, anEntry);
594 dlinkAdd(anEntry, &anEntry->lru, &lru_list);
595 ++cache_entries;
596 }
597
598 void
599 external_acl::trimCache()
600 {
601 if (cache_size && cache_entries >= cache_size)
602 external_acl_cache_delete(this, static_cast<external_acl_entry *>(lru_list.tail->data));
603 }
604
605 /******************************************************************
606 * external acl type
607 */
608
609 struct _external_acl_data {
610 external_acl *def;
611 const char *name;
612 wordlist *arguments;
613 };
614
615 CBDATA_TYPE(external_acl_data);
616 static void
617 free_external_acl_data(void *data)
618 {
619 external_acl_data *p = static_cast<external_acl_data *>(data);
620 safe_free(p->name);
621 wordlistDestroy(&p->arguments);
622 cbdataReferenceDone(p->def);
623 }
624
625 void
626 ACLExternal::parse()
627 {
628 char *token;
629
630 if (data)
631 self_destruct();
632
633 CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data);
634
635 data = cbdataAlloc(external_acl_data);
636
637 token = strtokFile();
638
639 if (!token)
640 self_destruct();
641
642 data->def = cbdataReference(find_externalAclHelper(token));
643
644 if (!data->def)
645 self_destruct();
646
647 // def->name is the name of the external_acl_type.
648 // this is the name of the 'acl' directive being tested
649 data->name = xstrdup(AclMatchedName);
650
651 while ((token = strtokFile())) {
652 wordlistAdd(&data->arguments, token);
653 }
654 }
655
656 bool
657 ACLExternal::valid () const
658 {
659 #if USE_AUTH
660 if (data->def->require_auth) {
661 if (authenticateSchemeCount() == 0) {
662 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
663 return false;
664 }
665
666 if (authenticateActiveSchemeCount() == 0) {
667 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
668 return false;
669 }
670 }
671 #endif
672
673 return true;
674 }
675
676 bool
677 ACLExternal::empty () const
678 {
679 return false;
680 }
681
682 ACLExternal::~ACLExternal()
683 {
684 cbdataFree(data);
685 safe_free (class_);
686 }
687
688 static void
689 copyResultsFromEntry(HttpRequest *req, external_acl_entry *entry)
690 {
691 if (req) {
692 #if USE_AUTH
693 if (entry->user.size())
694 req->extacl_user = entry->user;
695
696 if (entry->password.size())
697 req->extacl_passwd = entry->password;
698 #endif
699 if (!req->tag.size())
700 req->tag = entry->tag;
701
702 if (entry->log.size())
703 req->extacl_log = entry->log;
704
705 if (entry->message.size())
706 req->extacl_message = entry->message;
707 }
708 }
709
710 static allow_t
711 aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
712 {
713 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
714 external_acl_entry *entry = ch->extacl_entry;
715
716 external_acl_message = "MISSING REQUIRED INFORMATION";
717
718 if (entry) {
719 if (cbdataReferenceValid(entry) && entry->def == acl->def) {
720 /* Ours, use it.. if the key matches */
721 const char *key = makeExternalAclKey(ch, acl);
722 if (!key)
723 return ACCESS_DUNNO; // insufficent data to continue
724 if (strcmp(key, (char*)entry->key) != 0) {
725 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
726 // too bad. need a new lookup.
727 cbdataReferenceDone(ch->extacl_entry);
728 entry = NULL;
729 }
730 } else {
731 /* Not valid, or not ours.. get rid of it */
732 debugs(82, 9, HERE << "entry " << entry << " not valid or not ours. Discarded.");
733 if (entry) {
734 debugs(82, 9, HERE << "entry def=" << entry->def << ", our def=" << acl->def);
735 const char *key = makeExternalAclKey(ch, acl); // may be nil
736 debugs(82, 9, HERE << "entry key='" << (char *)entry->key << "', our key='" << key << "'");
737 }
738 cbdataReferenceDone(ch->extacl_entry);
739 entry = NULL;
740 }
741 }
742
743 if (!entry) {
744 debugs(82, 9, HERE << "No helper entry available");
745 #if USE_AUTH
746 if (acl->def->require_auth) {
747 /* Make sure the user is authenticated */
748 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
749 const allow_t ti = AuthenticateAcl(ch);
750 if (ti != ACCESS_ALLOWED) {
751 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
752 return ti;
753 }
754 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
755 }
756 #endif
757 const char *key = makeExternalAclKey(ch, acl);
758
759 if (!key) {
760 /* Not sufficient data to process */
761 return ACCESS_DUNNO;
762 }
763
764 entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key));
765
766 external_acl_entry *staleEntry = entry;
767 if (entry && external_acl_entry_expired(acl->def, entry))
768 entry = NULL;
769
770 if (entry && external_acl_grace_expired(acl->def, entry)) {
771 // refresh in the background
772 ExternalACLLookup::Start(ch, acl, true);
773 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
774 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
775 }
776
777 if (!entry) {
778 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
779 debugs(82, 2, HERE << "\"" << key << "\": entry=@" <<
780 entry << ", age=" << (entry ? (long int) squid_curtime - entry->date : 0));
781
782 if (acl->def->theHelper->stats.queue_size < (int)acl->def->theHelper->childs.n_active) {
783 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
784 if (!ch->goAsync(ExternalACLLookup::Instance()))
785 debugs(82, 2, "\"" << key << "\": no async support!");
786 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
787 return ACCESS_DUNNO; // expired cached or simply absent entry
788 } else {
789 if (!staleEntry) {
790 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
791 "' queue overload. Request rejected '" << key << "'.");
792 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
793 return ACCESS_DUNNO;
794 } else {
795 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
796 "' queue overload. Using stale result. '" << key << "'.");
797 entry = staleEntry;
798 /* Fall thru to processing below */
799 }
800 }
801 }
802 }
803
804 debugs(82, 4, HERE << "entry = { date=" <<
805 (long unsigned int) entry->date <<
806 ", result=" << entry->result <<
807 " tag=" << entry->tag <<
808 " log=" << entry->log << " }");
809 #if USE_AUTH
810 debugs(82, 4, HERE << "entry user=" << entry->user);
811 #endif
812
813 external_acl_cache_touch(acl->def, entry);
814 external_acl_message = entry->message.termedBuf();
815
816 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
817 copyResultsFromEntry(ch->request, entry);
818 return entry->result;
819 }
820
821 int
822 ACLExternal::match(ACLChecklist *checklist)
823 {
824 allow_t answer = aclMatchExternal(data, Filled(checklist));
825
826 // convert to tri-state ACL match 1,0,-1
827 switch (answer) {
828 case ACCESS_ALLOWED:
829 return 1; // match
830
831 case ACCESS_DENIED:
832 return 0; // non-match
833
834 case ACCESS_DUNNO:
835 case ACCESS_AUTH_REQUIRED:
836 default:
837 // If the answer is not allowed or denied (matches/not matches) and
838 // async authentication is not in progress, then we are done.
839 if (checklist->keepMatching())
840 checklist->markFinished(answer, "aclMatchExternal exception");
841 return -1; // other
842 }
843 }
844
845 SBufList
846 ACLExternal::dump() const
847 {
848 external_acl_data const *acl = data;
849 SBufList rv;
850 rv.push_back(SBuf(acl->def->name));
851
852 for (wordlist *arg = acl->arguments; arg; arg = arg->next) {
853 SBuf s;
854 s.Printf(" %s", arg->key);
855 rv.push_back(s);
856 }
857
858 return rv;
859 }
860
861 /******************************************************************
862 * external_acl cache
863 */
864
865 static void
866 external_acl_cache_touch(external_acl * def, external_acl_entry * entry)
867 {
868 // this must not be done when nothing is being cached.
869 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
870 return;
871
872 dlinkDelete(&entry->lru, &def->lru_list);
873 dlinkAdd(entry, &entry->lru, &def->lru_list);
874 }
875
876 static char *
877 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
878 {
879 static MemBuf mb;
880 char buf[256];
881 int first = 1;
882 wordlist *arg;
883 external_acl_format *format;
884 HttpRequest *request = ch->request;
885 HttpReply *reply = ch->reply;
886 mb.reset();
887 bool data_used = false;
888
889 for (format = acl_data->def->format; format; format = format->next) {
890 const char *str = NULL;
891 String sb;
892
893 switch (format->type) {
894 #if USE_AUTH
895 case Format::LFT_USER_LOGIN:
896 // if this ACL line was the cause of credentials fetch
897 // they may not already be in the checklist
898 if (ch->auth_user_request == NULL && ch->request)
899 ch->auth_user_request = ch->request->auth_user_request;
900
901 if (ch->auth_user_request != NULL)
902 str = ch->auth_user_request->username();
903 break;
904 #endif
905 #if USE_IDENT
906 case Format::LFT_USER_IDENT:
907 str = ch->rfc931;
908
909 if (!str || !*str) {
910 // if we fail to go async, we still return NULL and the caller
911 // will detect the failure in ACLExternal::match().
912 (void)ch->goAsync(IdentLookup::Instance());
913 return NULL;
914 }
915
916 break;
917 #endif
918
919 case Format::LFT_CLIENT_IP_ADDRESS:
920 str = ch->src_addr.toStr(buf,sizeof(buf));
921 break;
922
923 case Format::LFT_CLIENT_PORT:
924 snprintf(buf, sizeof(buf), "%d", request->client_addr.port());
925 str = buf;
926 break;
927
928 #if USE_SQUID_EUI
929 case Format::LFT_EXT_ACL_CLIENT_EUI48:
930 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
931 request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
932 str = buf;
933 break;
934
935 case Format::LFT_EXT_ACL_CLIENT_EUI64:
936 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
937 request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
938 str = buf;
939 break;
940 #endif
941
942 case Format::LFT_LOCAL_LISTENING_IP:
943 str = request->my_addr.toStr(buf, sizeof(buf));
944 break;
945
946 case Format::LFT_LOCAL_LISTENING_PORT:
947 snprintf(buf, sizeof(buf), "%d", request->my_addr.port());
948 str = buf;
949 break;
950
951 case Format::LFT_CLIENT_REQ_URI:
952 str = urlCanonical(request);
953 break;
954
955 case Format::LFT_CLIENT_REQ_URLDOMAIN:
956 str = request->GetHost();
957 break;
958
959 case Format::LFT_CLIENT_REQ_URLSCHEME:
960 str = request->url.getScheme().c_str();
961 break;
962
963 case Format::LFT_CLIENT_REQ_URLPORT:
964 snprintf(buf, sizeof(buf), "%d", request->port);
965 str = buf;
966 break;
967
968 case Format::LFT_CLIENT_REQ_URLPATH:
969 str = request->urlpath.termedBuf();
970 break;
971
972 case Format::LFT_CLIENT_REQ_METHOD: {
973 const SBuf &s = request->method.image();
974 sb.append(s.rawContent(), s.length());
975 }
976 str = sb.termedBuf();
977 break;
978
979 case Format::LFT_ADAPTED_REQUEST_HEADER:
980 if (format->header_id == -1)
981 sb = request->header.getByName(format->header);
982 else
983 sb = request->header.getStrOrList(format->header_id);
984 str = sb.termedBuf();
985 break;
986
987 case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
988 if (format->header_id == -1)
989 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
990 else
991 sb = request->header.getListMember(format->header_id, format->member, format->separator);
992 str = sb.termedBuf();
993 break;
994
995 case Format::LFT_REPLY_HEADER:
996 if (reply) {
997 if (format->header_id == -1)
998 sb = reply->header.getByName(format->header);
999 else
1000 sb = reply->header.getStrOrList(format->header_id);
1001 str = sb.termedBuf();
1002 }
1003 break;
1004
1005 case Format::LFT_REPLY_HEADER_ELEM:
1006 if (reply) {
1007 if (format->header_id == -1)
1008 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
1009 else
1010 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
1011 str = sb.termedBuf();
1012 }
1013 break;
1014
1015 #if USE_OPENSSL
1016
1017 case Format::LFT_EXT_ACL_USER_CERT_RAW:
1018
1019 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1020 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1021
1022 if (ssl)
1023 str = sslGetUserCertificatePEM(ssl);
1024 }
1025
1026 break;
1027
1028 case Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW:
1029
1030 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1031 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1032
1033 if (ssl)
1034 str = sslGetUserCertificateChainPEM(ssl);
1035 }
1036
1037 break;
1038
1039 case Format::LFT_EXT_ACL_USER_CERT:
1040
1041 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1042 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1043
1044 if (ssl)
1045 str = sslGetUserAttribute(ssl, format->header);
1046 }
1047
1048 break;
1049
1050 case Format::LFT_EXT_ACL_USER_CA_CERT:
1051
1052 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1053 SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl;
1054
1055 if (ssl)
1056 str = sslGetCAAttribute(ssl, format->header);
1057 }
1058
1059 break;
1060
1061 case Format::LFT_SSL_CLIENT_SNI:
1062 if (ch->conn() != NULL) {
1063 if (Ssl::ServerBump * srvBump = ch->conn()->serverBump()) {
1064 if (!srvBump->clientSni.isEmpty())
1065 str = srvBump->clientSni.c_str();
1066 }
1067 }
1068 break;
1069
1070 case Format::LFT_SSL_SERVER_CERT_SUBJECT:
1071 case Format::LFT_SSL_SERVER_CERT_ISSUER: {
1072 X509 *serverCert = NULL;
1073 if (ch->serverCert.get())
1074 serverCert = ch->serverCert.get();
1075 else if (ch->conn()->serverBump())
1076 serverCert = ch->conn()->serverBump()->serverCert.get();
1077
1078 if (serverCert) {
1079 if (format->type == Format::LFT_SSL_SERVER_CERT_SUBJECT)
1080 str = Ssl::GetX509UserAttribute(serverCert, "DN");
1081 else
1082 str = Ssl::GetX509CAAttribute(serverCert, "DN");
1083 }
1084 break;
1085 }
1086
1087 #endif
1088 #if USE_AUTH
1089 case Format::LFT_USER_EXTERNAL:
1090 str = request->extacl_user.termedBuf();
1091 break;
1092 #endif
1093 case Format::LFT_EXT_LOG:
1094 str = request->extacl_log.termedBuf();
1095 break;
1096 case Format::LFT_TAG:
1097 str = request->tag.termedBuf();
1098 break;
1099 case Format::LFT_EXT_ACL_NAME:
1100 str = acl_data->name;
1101 break;
1102 case Format::LFT_EXT_ACL_DATA:
1103 data_used = true;
1104 for (arg = acl_data->arguments; arg; arg = arg->next) {
1105 if (!first)
1106 sb.append(" ", 1);
1107
1108 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1109 const char *quoted = rfc1738_escape(arg->key);
1110 sb.append(quoted, strlen(quoted));
1111 } else {
1112 static MemBuf mb2;
1113 mb2.init();
1114 strwordquote(&mb2, arg->key);
1115 sb.append(mb2.buf, mb2.size);
1116 mb2.clean();
1117 }
1118
1119 first = 0;
1120 }
1121 break;
1122 case Format::LFT_PERCENT:
1123 str = "%";
1124 break;
1125
1126 default:
1127 // TODO: replace this function with Format::assemble()
1128 // For now die on unsupported logformat codes.
1129 fatalf("ERROR: unknown external_acl_type format %u", (uint8_t)format->type);
1130 break;
1131 }
1132
1133 if (str)
1134 if (!*str)
1135 str = NULL;
1136
1137 if (!str)
1138 str = "-";
1139
1140 if (!first)
1141 mb.append(" ", 1);
1142
1143 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1144 const char *quoted = rfc1738_escape(str);
1145 mb.append(quoted, strlen(quoted));
1146 } else {
1147 strwordquote(&mb, str);
1148 }
1149
1150 sb.clean();
1151
1152 first = 0;
1153 }
1154
1155 if (!data_used) {
1156 for (arg = acl_data->arguments; arg; arg = arg->next) {
1157 if (!first)
1158 mb.append(" ", 1);
1159
1160 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1161 const char *quoted = rfc1738_escape(arg->key);
1162 mb.append(quoted, strlen(quoted));
1163 } else {
1164 strwordquote(&mb, arg->key);
1165 }
1166
1167 first = 0;
1168 }
1169 }
1170
1171 return mb.buf;
1172 }
1173
1174 static int
1175 external_acl_entry_expired(external_acl * def, external_acl_entry * entry)
1176 {
1177 if (def->cache_size <= 0)
1178 return 1;
1179
1180 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
1181 return 1;
1182 else
1183 return 0;
1184 }
1185
1186 static int
1187 external_acl_grace_expired(external_acl * def, external_acl_entry * entry)
1188 {
1189 if (def->cache_size <= 0)
1190 return 1;
1191
1192 int ttl;
1193 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1194 ttl = (ttl * (100 - def->grace)) / 100;
1195
1196 if (entry->date + ttl <= squid_curtime)
1197 return 1;
1198 else
1199 return 0;
1200 }
1201
1202 static external_acl_entry *
1203 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
1204 {
1205 ExternalACLEntry *entry;
1206
1207 // do not bother caching this result if TTL is going to expire it immediately
1208 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
1209 debugs(82,6, HERE);
1210 entry = new ExternalACLEntry;
1211 entry->key = xstrdup(key);
1212 entry->update(data);
1213 entry->def = def;
1214 return entry;
1215 }
1216
1217 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
1218 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
1219
1220 if (entry) {
1221 debugs(82, 3, "ExternalACLEntry::update: updating existing entry");
1222 entry->update(data);
1223 external_acl_cache_touch(def, entry);
1224
1225 return entry;
1226 }
1227
1228 entry = new ExternalACLEntry;
1229 entry->key = xstrdup(key);
1230 entry->update(data);
1231
1232 def->add(entry);
1233
1234 return entry;
1235 }
1236
1237 static void
1238 external_acl_cache_delete(external_acl * def, external_acl_entry * entry)
1239 {
1240 assert(def->cache_size > 0 && entry->def == def);
1241 hash_remove_link(def->cache, entry);
1242 dlinkDelete(&entry->lru, &def->lru_list);
1243 def->cache_entries -= 1;
1244 delete entry;
1245 }
1246
1247 /******************************************************************
1248 * external_acl helpers
1249 */
1250
1251 typedef struct _externalAclState externalAclState;
1252
1253 struct _externalAclState {
1254 EAH *callback;
1255 void *callback_data;
1256 char *key;
1257 external_acl *def;
1258 dlink_node list;
1259 externalAclState *queue;
1260 };
1261
1262 CBDATA_TYPE(externalAclState);
1263 static void
1264 free_externalAclState(void *data)
1265 {
1266 externalAclState *state = static_cast<externalAclState *>(data);
1267 safe_free(state->key);
1268 cbdataReferenceDone(state->callback_data);
1269 cbdataReferenceDone(state->def);
1270 }
1271
1272 /*
1273 * The helper program receives queries on stdin, one
1274 * per line, and must return the result on on stdout
1275 *
1276 * General result syntax:
1277 *
1278 * OK/ERR keyword=value ...
1279 *
1280 * Keywords:
1281 *
1282 * user= The users name (login)
1283 * message= Message describing the reason
1284 * tag= A string tag to be applied to the request that triggered the acl match.
1285 * applies to both OK and ERR responses.
1286 * Won't override existing request tags.
1287 * log= A string to be used in access logging
1288 *
1289 * Other keywords may be added to the protocol later
1290 *
1291 * value needs to be URL-encoded or enclosed in double quotes (")
1292 * with \-escaping on any whitespace, quotes, or slashes (\).
1293 */
1294 static void
1295 externalAclHandleReply(void *data, const HelperReply &reply)
1296 {
1297 externalAclState *state = static_cast<externalAclState *>(data);
1298 externalAclState *next;
1299 ExternalACLEntryData entryData;
1300 entryData.result = ACCESS_DENIED;
1301 external_acl_entry *entry = NULL;
1302
1303 debugs(82, 2, HERE << "reply=" << reply);
1304
1305 if (reply.result == HelperReply::Okay)
1306 entryData.result = ACCESS_ALLOWED;
1307 // XXX: handle other non-DENIED results better
1308
1309 // XXX: make entryData store a proper HelperReply object instead of copying.
1310
1311 entryData.notes.append(&reply.notes);
1312
1313 const char *label = reply.notes.findFirst("tag");
1314 if (label != NULL && *label != '\0')
1315 entryData.tag = label;
1316
1317 label = reply.notes.findFirst("message");
1318 if (label != NULL && *label != '\0')
1319 entryData.message = label;
1320
1321 label = reply.notes.findFirst("log");
1322 if (label != NULL && *label != '\0')
1323 entryData.log = label;
1324
1325 #if USE_AUTH
1326 label = reply.notes.findFirst("user");
1327 if (label != NULL && *label != '\0')
1328 entryData.user = label;
1329
1330 label = reply.notes.findFirst("password");
1331 if (label != NULL && *label != '\0')
1332 entryData.password = label;
1333 #endif
1334
1335 dlinkDelete(&state->list, &state->def->queue);
1336
1337 if (cbdataReferenceValid(state->def)) {
1338 // only cache OK and ERR results.
1339 if (reply.result == HelperReply::Okay || reply.result == HelperReply::Error)
1340 entry = external_acl_cache_add(state->def, state->key, entryData);
1341 else {
1342 external_acl_entry *oldentry = (external_acl_entry *)hash_lookup(state->def->cache, state->key);
1343
1344 if (oldentry)
1345 external_acl_cache_delete(state->def, oldentry);
1346 }
1347 }
1348
1349 do {
1350 void *cbdata;
1351 cbdataReferenceDone(state->def);
1352
1353 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
1354 state->callback(cbdata, entry);
1355
1356 next = state->queue;
1357
1358 cbdataFree(state);
1359
1360 state = next;
1361 } while (state);
1362 }
1363
1364 void
1365 ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
1366 {
1367 ExternalACLLookup::Start(checklist, me->data, false);
1368 }
1369
1370 void
1371 ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
1372 {
1373 external_acl *def = acl->def;
1374
1375 ACLFilledChecklist *ch = Filled(checklist);
1376 const char *key = makeExternalAclKey(ch, acl);
1377 assert(key); // XXX: will fail if EXT_ACL_IDENT case needs an async lookup
1378
1379 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
1380 def->name << "' for '" << key << "'");
1381
1382 /* Check for a pending lookup to hook into */
1383 // only possible if we are caching results.
1384 externalAclState *oldstate = NULL;
1385 if (def->cache_size > 0) {
1386 for (dlink_node *node = def->queue.head; node; node = node->next) {
1387 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1388
1389 if (strcmp(key, oldstatetmp->key) == 0) {
1390 oldstate = oldstatetmp;
1391 break;
1392 }
1393 }
1394 }
1395
1396 // A background refresh has no need to piggiback on a pending request:
1397 // When the pending request completes, the cache will be refreshed anyway.
1398 if (oldstate && inBackground) {
1399 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
1400 return;
1401 }
1402
1403 externalAclState *state = cbdataAlloc(externalAclState);
1404 state->def = cbdataReference(def);
1405
1406 state->key = xstrdup(key);
1407
1408 if (!inBackground) {
1409 state->callback = &ExternalACLLookup::LookupDone;
1410 state->callback_data = cbdataReference(checklist);
1411 }
1412
1413 if (oldstate) {
1414 /* Hook into pending lookup */
1415 state->queue = oldstate->queue;
1416 oldstate->queue = state;
1417 } else {
1418 /* No pending lookup found. Sumbit to helper */
1419
1420 /* Check for queue overload */
1421
1422 if (def->theHelper->stats.queue_size >= (int)def->theHelper->childs.n_running) {
1423 debugs(82, 7, HERE << "'" << def->name << "' queue is too long");
1424 assert(inBackground); // or the caller should have checked
1425 cbdataFree(state);
1426 return;
1427 }
1428
1429 /* Send it off to the helper */
1430 MemBuf buf;
1431 buf.init();
1432
1433 buf.Printf("%s\n", key);
1434
1435 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1436
1437 helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state);
1438
1439 dlinkAdd(state, &state->list, &def->queue);
1440
1441 buf.clean();
1442 }
1443
1444 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1445 "' in '" << def->name << "' (ch=" << ch << ").");
1446 }
1447
1448 static void
1449 externalAclStats(StoreEntry * sentry)
1450 {
1451 external_acl *p;
1452
1453 for (p = Config.externalAclHelperList; p; p = p->next) {
1454 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1455 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1456 helperStats(sentry, p->theHelper);
1457 storeAppendPrintf(sentry, "\n");
1458 }
1459 }
1460
1461 static void
1462 externalAclRegisterWithCacheManager(void)
1463 {
1464 Mgr::RegisterAction("external_acl",
1465 "External ACL stats",
1466 externalAclStats, 0, 1);
1467 }
1468
1469 void
1470 externalAclInit(void)
1471 {
1472 static int firstTimeInit = 1;
1473 external_acl *p;
1474
1475 for (p = Config.externalAclHelperList; p; p = p->next) {
1476 if (!p->cache)
1477 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1478
1479 if (!p->theHelper)
1480 p->theHelper = new helper(p->name);
1481
1482 p->theHelper->cmdline = p->cmdline;
1483
1484 p->theHelper->childs.updateLimits(p->children);
1485
1486 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1487
1488 p->theHelper->addr = p->local_addr;
1489
1490 helperOpenServers(p->theHelper);
1491 }
1492
1493 if (firstTimeInit) {
1494 firstTimeInit = 0;
1495 CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState);
1496 }
1497
1498 externalAclRegisterWithCacheManager();
1499 }
1500
1501 void
1502 externalAclShutdown(void)
1503 {
1504 external_acl *p;
1505
1506 for (p = Config.externalAclHelperList; p; p = p->next) {
1507 helperShutdown(p->theHelper);
1508 }
1509 }
1510
1511 ExternalACLLookup ExternalACLLookup::instance_;
1512 ExternalACLLookup *
1513 ExternalACLLookup::Instance()
1514 {
1515 return &instance_;
1516 }
1517
1518 void
1519 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1520 {
1521 /* TODO: optimise this - we probably have a pointer to this
1522 * around somewhere */
1523 ACL *acl = ACL::FindByName(AclMatchedName);
1524 assert(acl);
1525 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1526 assert (me);
1527 ACLExternal::ExternalAclLookup(checklist, me);
1528 }
1529
1530 /// Called when an async lookup returns
1531 void
1532 ExternalACLLookup::LookupDone(void *data, void *result)
1533 {
1534 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
1535 checklist->extacl_entry = cbdataReference((external_acl_entry *)result);
1536
1537 // attach the helper kv-pair to the transaction
1538 if (checklist->extacl_entry) {
1539 if (HttpRequest * req = checklist->request) {
1540 // XXX: we have no access to the transaction / AccessLogEntry so cant SyncNotes().
1541 // workaround by using anything already set in HttpRequest
1542 // OR use new and rely on a later Sync copying these to AccessLogEntry
1543
1544 UpdateRequestNotes(checklist->conn(), *req, checklist->extacl_entry->notes);
1545 }
1546 }
1547
1548 checklist->resumeNonBlockingCheck(ExternalACLLookup::Instance());
1549 }
1550
1551 /* This registers "external" in the registry. To do dynamic definitions
1552 * of external ACL's, rather than a static prototype, have a Prototype instance
1553 * prototype in the class that defines each external acl 'class'.
1554 * Then, then the external acl instance is created, it self registers under
1555 * it's name.
1556 * Be sure that clone is fully functional for that acl class though!
1557 */
1558 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1559
1560 ACLExternal ACLExternal::RegistryEntry_("external");
1561
1562 ACL *
1563 ACLExternal::clone() const
1564 {
1565 return new ACLExternal(*this);
1566 }
1567
1568 ACLExternal::ACLExternal(char const *theClass) : data(NULL), class_(xstrdup(theClass))
1569 {}
1570
1571 ACLExternal::ACLExternal(ACLExternal const & old) : data(NULL), class_(old.class_ ? xstrdup(old.class_) : NULL)
1572 {
1573 /* we don't have copy constructors for the data yet */
1574 assert(!old.data);
1575 }
1576
1577 char const *
1578 ACLExternal::typeString() const
1579 {
1580 return class_;
1581 }
1582
1583 bool
1584 ACLExternal::isProxyAuth() const
1585 {
1586 #if USE_AUTH
1587 return data->def->require_auth;
1588 #else
1589 return false;
1590 #endif
1591 }