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