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