]> git.ipfire.org Git - thirdparty/squid.git/blob - src/external_acl.cc
Renamed http_hdr_type to Http::HdrType, fixed some HdrType-int implicit conversions
[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(Http::HdrType::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::HdrType 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 = HeaderLookupTable.lookup(SBuf(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 #if USE_AUTH || defined(USE_OPENSSL) || defined(USE_IDENT)
423 else if (strcmp(token, "%un") == 0)
424 format->type = Format::LFT_USER_NAME;
425 #endif
426 else if (strcmp(token, "%EXT_LOG") == 0 || strcmp(token, "%ea") == 0)
427 format->type = Format::LFT_EXT_LOG;
428 else if (strcmp(token, "%TAG") == 0 || strcmp(token, "%et") == 0)
429 format->type = Format::LFT_TAG;
430 else if (strcmp(token, "%ACL") == 0)
431 format->type = Format::LFT_EXT_ACL_NAME;
432 else if (strcmp(token, "%DATA") == 0)
433 format->type = Format::LFT_EXT_ACL_DATA;
434 else if (strcmp(token, "%%") == 0)
435 format->type = Format::LFT_PERCENT;
436 else {
437 debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
438 self_destruct();
439 }
440
441 *p = format;
442 p = &format->next;
443 token = ConfigParser::NextToken();
444 }
445
446 /* There must be at least one format token */
447 if (!a->format)
448 self_destruct();
449
450 /* helper */
451 if (!token)
452 self_destruct();
453
454 wordlistAdd(&a->cmdline, token);
455
456 /* arguments */
457 parse_wordlist(&a->cmdline);
458
459 while (*list)
460 list = &(*list)->next;
461
462 *list = a;
463 }
464
465 void
466 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
467 {
468 const external_acl *node;
469 const wordlist *word;
470
471 for (node = list; node; node = node->next) {
472 storeAppendPrintf(sentry, "%s %s", name, node->name);
473
474 if (!node->local_addr.isIPv6())
475 storeAppendPrintf(sentry, " ipv4");
476 else
477 storeAppendPrintf(sentry, " ipv6");
478
479 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
480 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
481
482 if (node->negative_ttl != node->ttl)
483 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
484
485 if (node->grace)
486 storeAppendPrintf(sentry, " grace=%d", node->grace);
487
488 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
489 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
490
491 if (node->children.n_startup != 1)
492 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
493
494 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
495 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
496
497 if (node->children.concurrency)
498 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
499
500 if (node->cache)
501 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
502
503 if (node->quote == external_acl::QUOTE_METHOD_SHELL)
504 storeAppendPrintf(sentry, " protocol=2.5");
505
506 for (external_acl_format::Pointer format = node->format; format!= NULL; format = format->next) {
507 switch (format->type) {
508
509 case Format::LFT_ADAPTED_REQUEST_HEADER:
510 storeAppendPrintf(sentry, " %%>ha{%s}", format->header);
511 break;
512
513 case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
514 storeAppendPrintf(sentry, " %%>ha{%s:%s}", format->header, format->member);
515 break;
516
517 case Format::LFT_REPLY_HEADER:
518 storeAppendPrintf(sentry, " %%<h{%s}", format->header);
519 break;
520
521 case Format::LFT_REPLY_HEADER_ELEM:
522 storeAppendPrintf(sentry, " %%<h{%s:%s}", format->header, format->member);
523 break;
524
525 #define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
526 case Format::LFT_##a: \
527 storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
528 break
529 #if USE_AUTH
530 DUMP_EXT_ACL_TYPE_FMT(USER_LOGIN," %%ul");
531 DUMP_EXT_ACL_TYPE_FMT(USER_NAME," %%un");
532 #endif
533 #if USE_IDENT
534
535 DUMP_EXT_ACL_TYPE_FMT(USER_IDENT," %%ui");
536 #endif
537 DUMP_EXT_ACL_TYPE_FMT(CLIENT_IP_ADDRESS," %%>a");
538 DUMP_EXT_ACL_TYPE_FMT(CLIENT_PORT," %%>p");
539 #if USE_SQUID_EUI
540 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI48," %%SRCEUI48");
541 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI64," %%SRCEUI64");
542 #endif
543 DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_IP," %%>la");
544 DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_PORT," %%>lp");
545 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URI," %%>ru");
546 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLDOMAIN," %%>rd");
547 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLSCHEME," %%>rs");
548 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPORT," %%>rP");
549 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPATH," %%>rp");
550 DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_METHOD," %%>rm");
551 #if USE_OPENSSL
552 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT_RAW, " %%USER_CERT_RAW");
553 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERTCHAIN_RAW, " %%USER_CERTCHAIN_RAW");
554 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT, " %%USER_CERT_%s", format->header);
555 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CA_CERT, " %%USER_CA_CERT_%s", format->header);
556 DUMP_EXT_ACL_TYPE_FMT(SSL_CLIENT_SNI, "%%ssl::>sni");
557 DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_SUBJECT, "%%ssl::<cert_subject");
558 DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_ISSUER, "%%ssl::<cert_issuer");
559 #endif
560 #if USE_AUTH
561 DUMP_EXT_ACL_TYPE_FMT(USER_EXTERNAL," %%ue");
562 #endif
563 DUMP_EXT_ACL_TYPE_FMT(EXT_LOG," %%ea");
564 DUMP_EXT_ACL_TYPE_FMT(TAG," %%et");
565 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_NAME," %%ACL");
566 DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_DATA," %%DATA");
567 DUMP_EXT_ACL_TYPE_FMT(PERCENT, " %%%%");
568 default:
569 fatal("unknown external_acl format error");
570 break;
571 }
572 }
573
574 for (word = node->cmdline; word; word = word->next)
575 storeAppendPrintf(sentry, " %s", word->key);
576
577 storeAppendPrintf(sentry, "\n");
578 }
579 }
580
581 void
582 free_externalAclHelper(external_acl ** list)
583 {
584 delete *list;
585 *list = NULL;
586 }
587
588 static external_acl *
589 find_externalAclHelper(const char *name)
590 {
591 external_acl *node;
592
593 for (node = Config.externalAclHelperList; node; node = node->next) {
594 if (strcmp(node->name, name) == 0)
595 return node;
596 }
597
598 return NULL;
599 }
600
601 void
602 external_acl::add(const ExternalACLEntryPointer &anEntry)
603 {
604 trimCache();
605 assert(anEntry != NULL);
606 assert (anEntry->def == NULL);
607 anEntry->def = this;
608 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(anEntry.getRaw()); // XXX: make hash a std::map of Pointer.
609 hash_join(cache, e);
610 dlinkAdd(e, &e->lru, &lru_list);
611 e->lock(); //cbdataReference(e); // lock it on behalf of the hash
612 ++cache_entries;
613 }
614
615 void
616 external_acl::trimCache()
617 {
618 if (cache_size && cache_entries >= cache_size) {
619 ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
620 external_acl_cache_delete(this, e);
621 }
622 }
623
624 /******************************************************************
625 * external acl type
626 */
627
628 class external_acl_data
629 {
630 CBDATA_CLASS(external_acl_data);
631
632 public:
633 explicit external_acl_data(external_acl *aDef) : def(cbdataReference(aDef)), name(NULL), arguments(NULL) {}
634 ~external_acl_data();
635
636 external_acl *def;
637 const char *name;
638 wordlist *arguments;
639 };
640
641 CBDATA_CLASS_INIT(external_acl_data);
642
643 external_acl_data::~external_acl_data()
644 {
645 xfree(name);
646 wordlistDestroy(&arguments);
647 cbdataReferenceDone(def);
648 }
649
650 void
651 ACLExternal::parse()
652 {
653 if (data)
654 self_destruct();
655
656 char *token = ConfigParser::strtokFile();
657
658 if (!token)
659 self_destruct();
660
661 data = new external_acl_data(find_externalAclHelper(token));
662
663 if (!data->def)
664 self_destruct();
665
666 // def->name is the name of the external_acl_type.
667 // this is the name of the 'acl' directive being tested
668 data->name = xstrdup(AclMatchedName);
669
670 while ((token = ConfigParser::strtokFile())) {
671 wordlistAdd(&data->arguments, token);
672 }
673 }
674
675 bool
676 ACLExternal::valid () const
677 {
678 #if USE_AUTH
679 if (data->def->require_auth) {
680 if (authenticateSchemeCount() == 0) {
681 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
682 return false;
683 }
684
685 if (authenticateActiveSchemeCount() == 0) {
686 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
687 return false;
688 }
689 }
690 #endif
691
692 return true;
693 }
694
695 bool
696 ACLExternal::empty () const
697 {
698 return false;
699 }
700
701 ACLExternal::~ACLExternal()
702 {
703 delete data;
704 xfree(class_);
705 }
706
707 static void
708 copyResultsFromEntry(HttpRequest *req, const ExternalACLEntryPointer &entry)
709 {
710 if (req) {
711 #if USE_AUTH
712 if (entry->user.size())
713 req->extacl_user = entry->user;
714
715 if (entry->password.size())
716 req->extacl_passwd = entry->password;
717 #endif
718 if (!req->tag.size())
719 req->tag = entry->tag;
720
721 if (entry->log.size())
722 req->extacl_log = entry->log;
723
724 if (entry->message.size())
725 req->extacl_message = entry->message;
726
727 // attach the helper kv-pair to the transaction
728 UpdateRequestNotes(req->clientConnectionManager.get(), *req, entry->notes);
729 }
730 }
731
732 static allow_t
733 aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
734 {
735 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
736 ExternalACLEntryPointer entry = ch->extacl_entry;
737
738 external_acl_message = "MISSING REQUIRED INFORMATION";
739
740 if (entry != NULL) {
741 if (entry->def == acl->def) {
742 /* Ours, use it.. if the key matches */
743 const char *key = makeExternalAclKey(ch, acl);
744 if (!key)
745 return ACCESS_DUNNO; // insufficent data to continue
746 if (strcmp(key, (char*)entry->key) != 0) {
747 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
748 // too bad. need a new lookup.
749 entry = ch->extacl_entry = NULL;
750 }
751 } else {
752 /* Not ours.. get rid of it */
753 debugs(82, 9, "entry " << entry << " not valid or not ours. Discarded.");
754 if (entry != NULL) {
755 debugs(82, 9, "entry def=" << entry->def << ", our def=" << acl->def);
756 const char *key = makeExternalAclKey(ch, acl); // may be nil
757 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "'");
758 }
759 entry = ch->extacl_entry = NULL;
760 }
761 }
762
763 if (!entry) {
764 debugs(82, 9, HERE << "No helper entry available");
765 #if USE_AUTH
766 if (acl->def->require_auth) {
767 /* Make sure the user is authenticated */
768 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
769 const allow_t ti = AuthenticateAcl(ch);
770 if (ti != ACCESS_ALLOWED) {
771 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
772 return ti;
773 }
774 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
775 }
776 #endif
777 const char *key = makeExternalAclKey(ch, acl);
778
779 if (!key) {
780 /* Not sufficient data to process */
781 return ACCESS_DUNNO;
782 }
783
784 entry = static_cast<ExternalACLEntry *>(hash_lookup(acl->def->cache, key));
785
786 const ExternalACLEntryPointer staleEntry = entry;
787 if (entry != NULL && external_acl_entry_expired(acl->def, entry))
788 entry = NULL;
789
790 if (entry != NULL && external_acl_grace_expired(acl->def, entry)) {
791 // refresh in the background
792 ExternalACLLookup::Start(ch, acl, true);
793 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
794 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
795 }
796
797 if (!entry) {
798 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
799
800 if (!acl->def->theHelper->queueFull()) {
801 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
802 if (!ch->goAsync(ExternalACLLookup::Instance()))
803 debugs(82, 2, "\"" << key << "\": no async support!");
804 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
805 return ACCESS_DUNNO; // expired cached or simply absent entry
806 } else {
807 if (!staleEntry) {
808 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
809 "' queue overload. Request rejected '" << key << "'.");
810 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
811 return ACCESS_DUNNO;
812 } else {
813 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
814 "' queue overload. Using stale result. '" << key << "'.");
815 entry = staleEntry;
816 /* Fall thru to processing below */
817 }
818 }
819 }
820 }
821
822 debugs(82, 4, HERE << "entry = { date=" <<
823 (long unsigned int) entry->date <<
824 ", result=" << entry->result <<
825 " tag=" << entry->tag <<
826 " log=" << entry->log << " }");
827 #if USE_AUTH
828 debugs(82, 4, HERE << "entry user=" << entry->user);
829 #endif
830
831 external_acl_cache_touch(acl->def, entry);
832 external_acl_message = entry->message.termedBuf();
833
834 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
835 copyResultsFromEntry(ch->request, entry);
836 return entry->result;
837 }
838
839 int
840 ACLExternal::match(ACLChecklist *checklist)
841 {
842 allow_t answer = aclMatchExternal(data, Filled(checklist));
843
844 // convert to tri-state ACL match 1,0,-1
845 switch (answer) {
846 case ACCESS_ALLOWED:
847 return 1; // match
848
849 case ACCESS_DENIED:
850 return 0; // non-match
851
852 case ACCESS_DUNNO:
853 case ACCESS_AUTH_REQUIRED:
854 default:
855 // If the answer is not allowed or denied (matches/not matches) and
856 // async authentication is not in progress, then we are done.
857 if (checklist->keepMatching())
858 checklist->markFinished(answer, "aclMatchExternal exception");
859 return -1; // other
860 }
861 }
862
863 SBufList
864 ACLExternal::dump() const
865 {
866 external_acl_data const *acl = data;
867 SBufList rv;
868 rv.push_back(SBuf(acl->def->name));
869
870 for (wordlist *arg = acl->arguments; arg; arg = arg->next) {
871 SBuf s;
872 s.Printf(" %s", arg->key);
873 rv.push_back(s);
874 }
875
876 return rv;
877 }
878
879 /******************************************************************
880 * external_acl cache
881 */
882
883 static void
884 external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry)
885 {
886 // this must not be done when nothing is being cached.
887 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
888 return;
889
890 dlinkDelete(&entry->lru, &def->lru_list);
891 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
892 dlinkAdd(e, &entry->lru, &def->lru_list);
893 }
894
895 #if USE_OPENSSL
896 static const char *
897 external_acl_ssl_get_user_attribute(const ACLFilledChecklist &ch, const char *attr)
898 {
899 if (ch.conn() != NULL && Comm::IsConnOpen(ch.conn()->clientConnection)) {
900 if (SSL *ssl = fd_table[ch.conn()->clientConnection->fd].ssl)
901 return sslGetUserAttribute(ssl, attr);
902 }
903 return NULL;
904 }
905 #endif
906
907 static char *
908 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
909 {
910 static MemBuf mb;
911 char buf[256];
912 int first = 1;
913 wordlist *arg;
914 HttpRequest *request = ch->request;
915 HttpReply *reply = ch->reply;
916 mb.reset();
917 bool data_used = false;
918
919 for (external_acl_format::Pointer format = acl_data->def->format; format != NULL; format = format->next) {
920 const char *str = NULL;
921 String sb;
922
923 switch (format->type) {
924 #if USE_AUTH
925 case Format::LFT_USER_LOGIN:
926 // if this ACL line was the cause of credentials fetch
927 // they may not already be in the checklist
928 if (ch->auth_user_request == NULL && ch->request)
929 ch->auth_user_request = ch->request->auth_user_request;
930
931 if (ch->auth_user_request != NULL)
932 str = ch->auth_user_request->username();
933 break;
934 #endif
935 #if USE_IDENT
936 case Format::LFT_USER_IDENT:
937 str = ch->rfc931;
938
939 if (!str || !*str) {
940 // if we fail to go async, we still return NULL and the caller
941 // will detect the failure in ACLExternal::match().
942 (void)ch->goAsync(IdentLookup::Instance());
943 return NULL;
944 }
945
946 break;
947 #endif
948
949 case Format::LFT_CLIENT_IP_ADDRESS:
950 str = ch->src_addr.toStr(buf,sizeof(buf));
951 break;
952
953 case Format::LFT_CLIENT_PORT:
954 snprintf(buf, sizeof(buf), "%d", request->client_addr.port());
955 str = buf;
956 break;
957
958 #if USE_SQUID_EUI
959 case Format::LFT_EXT_ACL_CLIENT_EUI48:
960 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
961 request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
962 str = buf;
963 break;
964
965 case Format::LFT_EXT_ACL_CLIENT_EUI64:
966 if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
967 request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
968 str = buf;
969 break;
970 #endif
971
972 case Format::LFT_LOCAL_LISTENING_IP:
973 str = request->my_addr.toStr(buf, sizeof(buf));
974 break;
975
976 case Format::LFT_LOCAL_LISTENING_PORT:
977 snprintf(buf, sizeof(buf), "%d", request->my_addr.port());
978 str = buf;
979 break;
980
981 case Format::LFT_CLIENT_REQ_URI:
982 snprintf(buf, sizeof(buf), SQUIDSBUFPH, SQUIDSBUFPRINT(request->effectiveRequestUri()));
983 str = buf;
984 break;
985
986 case Format::LFT_CLIENT_REQ_URLDOMAIN:
987 str = request->url.host();
988 break;
989
990 case Format::LFT_CLIENT_REQ_URLSCHEME:
991 str = request->url.getScheme().c_str();
992 break;
993
994 case Format::LFT_CLIENT_REQ_URLPORT:
995 snprintf(buf, sizeof(buf), "%u", request->url.port());
996 str = buf;
997 break;
998
999 case Format::LFT_CLIENT_REQ_URLPATH: {
1000 SBuf tmp = request->url.path();
1001 str = tmp.c_str();
1002 }
1003 break;
1004
1005 case Format::LFT_CLIENT_REQ_METHOD: {
1006 const SBuf &s = request->method.image();
1007 sb.append(s.rawContent(), s.length());
1008 }
1009 str = sb.termedBuf();
1010 break;
1011
1012 case Format::LFT_ADAPTED_REQUEST_HEADER:
1013 if (format->header_id == Http::HdrType::BAD_HDR)
1014 sb = request->header.getByName(format->header);
1015 else
1016 sb = request->header.getStrOrList(format->header_id);
1017 str = sb.termedBuf();
1018 break;
1019
1020 case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
1021 if (format->header_id == Http::HdrType::BAD_HDR)
1022 sb = request->header.getByNameListMember(format->header, format->member, format->separator);
1023 else
1024 sb = request->header.getListMember(format->header_id, format->member, format->separator);
1025 str = sb.termedBuf();
1026 break;
1027
1028 case Format::LFT_REPLY_HEADER:
1029 if (reply) {
1030 if (format->header_id == Http::HdrType::BAD_HDR)
1031 sb = reply->header.getByName(format->header);
1032 else
1033 sb = reply->header.getStrOrList(format->header_id);
1034 str = sb.termedBuf();
1035 }
1036 break;
1037
1038 case Format::LFT_REPLY_HEADER_ELEM:
1039 if (reply) {
1040 if (format->header_id == Http::HdrType::BAD_HDR)
1041 sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
1042 else
1043 sb = reply->header.getListMember(format->header_id, format->member, format->separator);
1044 str = sb.termedBuf();
1045 }
1046 break;
1047
1048 #if USE_OPENSSL
1049
1050 case Format::LFT_EXT_ACL_USER_CERT_RAW:
1051
1052 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1053 if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
1054 str = sslGetUserCertificatePEM(ssl);
1055 }
1056
1057 break;
1058
1059 case Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW:
1060
1061 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1062 if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
1063 str = sslGetUserCertificateChainPEM(ssl);
1064 }
1065
1066 break;
1067
1068 case Format::LFT_EXT_ACL_USER_CERT:
1069
1070 str = external_acl_ssl_get_user_attribute(*ch, format->header);
1071 break;
1072
1073 case Format::LFT_EXT_ACL_USER_CA_CERT:
1074
1075 if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
1076 if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
1077 str = sslGetCAAttribute(ssl, format->header);
1078 }
1079
1080 break;
1081
1082 case Format::LFT_SSL_CLIENT_SNI:
1083 if (ch->conn() != NULL) {
1084 if (Ssl::ServerBump * srvBump = ch->conn()->serverBump()) {
1085 if (!srvBump->clientSni.isEmpty())
1086 str = srvBump->clientSni.c_str();
1087 }
1088 }
1089 break;
1090
1091 case Format::LFT_SSL_SERVER_CERT_SUBJECT:
1092 case Format::LFT_SSL_SERVER_CERT_ISSUER: {
1093 X509 *serverCert = NULL;
1094 if (ch->serverCert.get())
1095 serverCert = ch->serverCert.get();
1096 else if (ch->conn() && ch->conn()->serverBump())
1097 serverCert = ch->conn()->serverBump()->serverCert.get();
1098
1099 if (serverCert) {
1100 if (format->type == Format::LFT_SSL_SERVER_CERT_SUBJECT)
1101 str = Ssl::GetX509UserAttribute(serverCert, "DN");
1102 else
1103 str = Ssl::GetX509CAAttribute(serverCert, "DN");
1104 }
1105 break;
1106 }
1107
1108 #endif
1109 #if USE_AUTH
1110 case Format::LFT_USER_EXTERNAL:
1111 str = request->extacl_user.termedBuf();
1112 break;
1113 #endif
1114 case Format::LFT_USER_NAME:
1115 /* find the first available name from various sources */
1116 #if USE_AUTH
1117 if (ch->auth_user_request != NULL)
1118 str = ch->auth_user_request->username();
1119 if ((!str || !*str) &&
1120 (request->extacl_user.size() > 0 && request->extacl_user[0] != '-'))
1121 str = request->extacl_user.termedBuf();
1122 #endif
1123 #if USE_OPENSSL
1124 if (!str || !*str)
1125 str = external_acl_ssl_get_user_attribute(*ch, "CN");
1126 #endif
1127 #if USE_IDENT
1128 if (!str || !*str)
1129 str = ch->rfc931;
1130 #endif
1131 break;
1132 case Format::LFT_EXT_LOG:
1133 str = request->extacl_log.termedBuf();
1134 break;
1135 case Format::LFT_TAG:
1136 str = request->tag.termedBuf();
1137 break;
1138 case Format::LFT_EXT_ACL_NAME:
1139 str = acl_data->name;
1140 break;
1141 case Format::LFT_EXT_ACL_DATA:
1142 data_used = true;
1143 for (arg = acl_data->arguments; arg; arg = arg->next) {
1144 if (!first)
1145 sb.append(" ", 1);
1146
1147 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1148 const char *quoted = rfc1738_escape(arg->key);
1149 sb.append(quoted, strlen(quoted));
1150 } else {
1151 static MemBuf mb2;
1152 mb2.init();
1153 strwordquote(&mb2, arg->key);
1154 sb.append(mb2.buf, mb2.size);
1155 mb2.clean();
1156 }
1157
1158 first = 0;
1159 }
1160 break;
1161 case Format::LFT_PERCENT:
1162 str = "%";
1163 break;
1164
1165 default:
1166 // TODO: replace this function with Format::assemble()
1167 // For now die on unsupported logformat codes.
1168 fatalf("ERROR: unknown external_acl_type format %u", (uint8_t)format->type);
1169 break;
1170 }
1171
1172 if (str)
1173 if (!*str)
1174 str = NULL;
1175
1176 if (!str)
1177 str = "-";
1178
1179 if (!first)
1180 mb.append(" ", 1);
1181
1182 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1183 const char *quoted = rfc1738_escape(str);
1184 mb.append(quoted, strlen(quoted));
1185 } else {
1186 strwordquote(&mb, str);
1187 }
1188
1189 sb.clean();
1190
1191 first = 0;
1192 }
1193
1194 if (!data_used) {
1195 for (arg = acl_data->arguments; arg; arg = arg->next) {
1196 if (!first)
1197 mb.append(" ", 1);
1198
1199 if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
1200 const char *quoted = rfc1738_escape(arg->key);
1201 mb.append(quoted, strlen(quoted));
1202 } else {
1203 strwordquote(&mb, arg->key);
1204 }
1205
1206 first = 0;
1207 }
1208 }
1209
1210 return mb.buf;
1211 }
1212
1213 static int
1214 external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry)
1215 {
1216 if (def->cache_size <= 0)
1217 return 1;
1218
1219 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
1220 return 1;
1221 else
1222 return 0;
1223 }
1224
1225 static int
1226 external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry)
1227 {
1228 if (def->cache_size <= 0)
1229 return 1;
1230
1231 int ttl;
1232 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
1233 ttl = (ttl * (100 - def->grace)) / 100;
1234
1235 if (entry->date + ttl <= squid_curtime)
1236 return 1;
1237 else
1238 return 0;
1239 }
1240
1241 static ExternalACLEntryPointer
1242 external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
1243 {
1244 ExternalACLEntryPointer entry;
1245
1246 // do not bother caching this result if TTL is going to expire it immediately
1247 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
1248 debugs(82,6, HERE);
1249 entry = new ExternalACLEntry;
1250 entry->key = xstrdup(key);
1251 entry->update(data);
1252 entry->def = def;
1253 return entry;
1254 }
1255
1256 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
1257 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
1258
1259 if (entry != NULL) {
1260 debugs(82, 3, "updating existing entry");
1261 entry->update(data);
1262 external_acl_cache_touch(def, entry);
1263 return entry;
1264 }
1265
1266 entry = new ExternalACLEntry;
1267 entry->key = xstrdup(key);
1268 entry->update(data);
1269
1270 def->add(entry);
1271
1272 return entry;
1273 }
1274
1275 static void
1276 external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry)
1277 {
1278 assert(entry != NULL);
1279 assert(def->cache_size > 0 && entry->def == def);
1280 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
1281 hash_remove_link(def->cache, e);
1282 dlinkDelete(&e->lru, &def->lru_list);
1283 e->unlock(); // unlock on behalf of the hash
1284 def->cache_entries -= 1;
1285 }
1286
1287 /******************************************************************
1288 * external_acl helpers
1289 */
1290
1291 class externalAclState
1292 {
1293 CBDATA_CLASS(externalAclState);
1294
1295 public:
1296 externalAclState(external_acl* aDef, const char *aKey) :
1297 callback(NULL),
1298 callback_data(NULL),
1299 key(xstrdup(aKey)),
1300 def(cbdataReference(aDef)),
1301 queue(NULL)
1302 {}
1303 ~externalAclState();
1304
1305 EAH *callback;
1306 void *callback_data;
1307 char *key;
1308 external_acl *def;
1309 dlink_node list;
1310 externalAclState *queue;
1311 };
1312
1313 CBDATA_CLASS_INIT(externalAclState);
1314
1315 externalAclState::~externalAclState()
1316 {
1317 xfree(key);
1318 cbdataReferenceDone(callback_data);
1319 cbdataReferenceDone(def);
1320 }
1321
1322 /*
1323 * The helper program receives queries on stdin, one
1324 * per line, and must return the result on on stdout
1325 *
1326 * General result syntax:
1327 *
1328 * OK/ERR keyword=value ...
1329 *
1330 * Keywords:
1331 *
1332 * user= The users name (login)
1333 * message= Message describing the reason
1334 * tag= A string tag to be applied to the request that triggered the acl match.
1335 * applies to both OK and ERR responses.
1336 * Won't override existing request tags.
1337 * log= A string to be used in access logging
1338 *
1339 * Other keywords may be added to the protocol later
1340 *
1341 * value needs to be URL-encoded or enclosed in double quotes (")
1342 * with \-escaping on any whitespace, quotes, or slashes (\).
1343 */
1344 static void
1345 externalAclHandleReply(void *data, const Helper::Reply &reply)
1346 {
1347 externalAclState *state = static_cast<externalAclState *>(data);
1348 externalAclState *next;
1349 ExternalACLEntryData entryData;
1350 entryData.result = ACCESS_DENIED;
1351
1352 debugs(82, 2, HERE << "reply=" << reply);
1353
1354 if (reply.result == Helper::Okay)
1355 entryData.result = ACCESS_ALLOWED;
1356 // XXX: handle other non-DENIED results better
1357
1358 // XXX: make entryData store a proper Helper::Reply object instead of copying.
1359
1360 entryData.notes.append(&reply.notes);
1361
1362 const char *label = reply.notes.findFirst("tag");
1363 if (label != NULL && *label != '\0')
1364 entryData.tag = label;
1365
1366 label = reply.notes.findFirst("message");
1367 if (label != NULL && *label != '\0')
1368 entryData.message = label;
1369
1370 label = reply.notes.findFirst("log");
1371 if (label != NULL && *label != '\0')
1372 entryData.log = label;
1373
1374 #if USE_AUTH
1375 label = reply.notes.findFirst("user");
1376 if (label != NULL && *label != '\0')
1377 entryData.user = label;
1378
1379 label = reply.notes.findFirst("password");
1380 if (label != NULL && *label != '\0')
1381 entryData.password = label;
1382 #endif
1383
1384 dlinkDelete(&state->list, &state->def->queue);
1385
1386 ExternalACLEntryPointer entry;
1387 if (cbdataReferenceValid(state->def)) {
1388 // only cache OK and ERR results.
1389 if (reply.result == Helper::Okay || reply.result == Helper::Error)
1390 entry = external_acl_cache_add(state->def, state->key, entryData);
1391 else {
1392 const ExternalACLEntryPointer oldentry = static_cast<ExternalACLEntry *>(hash_lookup(state->def->cache, state->key));
1393
1394 if (oldentry != NULL)
1395 external_acl_cache_delete(state->def, oldentry);
1396 }
1397 }
1398
1399 do {
1400 void *cbdata;
1401 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
1402 state->callback(cbdata, entry);
1403
1404 next = state->queue;
1405 state->queue = NULL;
1406
1407 delete state;
1408
1409 state = next;
1410 } while (state);
1411 }
1412
1413 void
1414 ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
1415 {
1416 ExternalACLLookup::Start(checklist, me->data, false);
1417 }
1418
1419 void
1420 ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
1421 {
1422 external_acl *def = acl->def;
1423
1424 ACLFilledChecklist *ch = Filled(checklist);
1425 const char *key = makeExternalAclKey(ch, acl);
1426 assert(key); // XXX: will fail if EXT_ACL_IDENT case needs an async lookup
1427
1428 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
1429 def->name << "' for '" << key << "'");
1430
1431 /* Check for a pending lookup to hook into */
1432 // only possible if we are caching results.
1433 externalAclState *oldstate = NULL;
1434 if (def->cache_size > 0) {
1435 for (dlink_node *node = def->queue.head; node; node = node->next) {
1436 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
1437
1438 if (strcmp(key, oldstatetmp->key) == 0) {
1439 oldstate = oldstatetmp;
1440 break;
1441 }
1442 }
1443 }
1444
1445 // A background refresh has no need to piggiback on a pending request:
1446 // When the pending request completes, the cache will be refreshed anyway.
1447 if (oldstate && inBackground) {
1448 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
1449 return;
1450 }
1451
1452 externalAclState *state = new externalAclState(def, key);
1453
1454 if (!inBackground) {
1455 state->callback = &ExternalACLLookup::LookupDone;
1456 state->callback_data = cbdataReference(checklist);
1457 }
1458
1459 if (oldstate) {
1460 /* Hook into pending lookup */
1461 state->queue = oldstate->queue;
1462 oldstate->queue = state;
1463 } else {
1464 /* No pending lookup found. Sumbit to helper */
1465
1466 MemBuf buf;
1467 buf.init();
1468 buf.appendf("%s\n", key);
1469 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
1470
1471 if (!def->theHelper->trySubmit(buf.buf, externalAclHandleReply, state)) {
1472 debugs(82, 7, HERE << "'" << def->name << "' submit to helper failed");
1473 assert(inBackground); // or the caller should have checked
1474 delete state;
1475 return;
1476 }
1477
1478 dlinkAdd(state, &state->list, &def->queue);
1479
1480 buf.clean();
1481 }
1482
1483 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1484 "' in '" << def->name << "' (ch=" << ch << ").");
1485 }
1486
1487 static void
1488 externalAclStats(StoreEntry * sentry)
1489 {
1490 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1491 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1492 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
1493 assert(p->theHelper);
1494 p->theHelper->packStatsInto(sentry);
1495 storeAppendPrintf(sentry, "\n");
1496 }
1497 }
1498
1499 static void
1500 externalAclRegisterWithCacheManager(void)
1501 {
1502 Mgr::RegisterAction("external_acl",
1503 "External ACL stats",
1504 externalAclStats, 0, 1);
1505 }
1506
1507 void
1508 externalAclInit(void)
1509 {
1510 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
1511 if (!p->cache)
1512 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
1513
1514 if (!p->theHelper)
1515 p->theHelper = new helper(p->name);
1516
1517 p->theHelper->cmdline = p->cmdline;
1518
1519 p->theHelper->childs.updateLimits(p->children);
1520
1521 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1522
1523 p->theHelper->addr = p->local_addr;
1524
1525 helperOpenServers(p->theHelper);
1526 }
1527
1528 externalAclRegisterWithCacheManager();
1529 }
1530
1531 void
1532 externalAclShutdown(void)
1533 {
1534 external_acl *p;
1535
1536 for (p = Config.externalAclHelperList; p; p = p->next) {
1537 helperShutdown(p->theHelper);
1538 }
1539 }
1540
1541 ExternalACLLookup ExternalACLLookup::instance_;
1542 ExternalACLLookup *
1543 ExternalACLLookup::Instance()
1544 {
1545 return &instance_;
1546 }
1547
1548 void
1549 ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1550 {
1551 /* TODO: optimise this - we probably have a pointer to this
1552 * around somewhere */
1553 ACL *acl = ACL::FindByName(AclMatchedName);
1554 assert(acl);
1555 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1556 assert (me);
1557 ACLExternal::ExternalAclLookup(checklist, me);
1558 }
1559
1560 /// Called when an async lookup returns
1561 void
1562 ExternalACLLookup::LookupDone(void *data, const ExternalACLEntryPointer &result)
1563 {
1564 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
1565 checklist->extacl_entry = result;
1566 checklist->resumeNonBlockingCheck(ExternalACLLookup::Instance());
1567 }
1568
1569 /* This registers "external" in the registry. To do dynamic definitions
1570 * of external ACL's, rather than a static prototype, have a Prototype instance
1571 * prototype in the class that defines each external acl 'class'.
1572 * Then, then the external acl instance is created, it self registers under
1573 * it's name.
1574 * Be sure that clone is fully functional for that acl class though!
1575 */
1576 ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1577
1578 ACLExternal ACLExternal::RegistryEntry_("external");
1579
1580 ACL *
1581 ACLExternal::clone() const
1582 {
1583 return new ACLExternal(*this);
1584 }
1585
1586 ACLExternal::ACLExternal(char const *theClass) : data(NULL), class_(xstrdup(theClass))
1587 {}
1588
1589 ACLExternal::ACLExternal(ACLExternal const & old) : data(NULL), class_(old.class_ ? xstrdup(old.class_) : NULL)
1590 {
1591 /* we don't have copy constructors for the data yet */
1592 assert(!old.data);
1593 }
1594
1595 char const *
1596 ACLExternal::typeString() const
1597 {
1598 return class_;
1599 }
1600
1601 bool
1602 ACLExternal::isProxyAuth() const
1603 {
1604 #if USE_AUTH
1605 return data->def->require_auth;
1606 #else
1607 return false;
1608 #endif
1609 }
1610